diff --git a/web/src/ar/frameworks/RepositoryStep/RegistryRefContext.tsx b/web/src/ar/frameworks/RepositoryStep/RegistryRefContext.tsx new file mode 100644 index 0000000000..514e80304e --- /dev/null +++ b/web/src/ar/frameworks/RepositoryStep/RegistryRefContext.tsx @@ -0,0 +1,39 @@ +/* + * Copyright 2024 Harness, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { createContext, useContext } from 'react' + +/** + * Pre-computed registry ref (e.g. from list path). Set by RepositoryActionsWidget when + * opened from the registry list; consumed by RepositoryActions/UpstreamProxyActions for + * the Setup Client flow. When not set (e.g. on details page), useRegistryRef() returns + * undefined and SetupClientContent falls back to scope-based ref. + */ +const RegistryRefContext = createContext(undefined) + +export function RegistryRefProvider({ + value, + children +}: { + value: string | undefined + children: React.ReactNode +}): React.ReactElement { + return {children} +} + +export function useRegistryRef(): string | undefined { + return useContext(RegistryRefContext) +} diff --git a/web/src/ar/frameworks/RepositoryStep/RepositoryActionsWidget.tsx b/web/src/ar/frameworks/RepositoryStep/RepositoryActionsWidget.tsx index ef18e95591..78d8d226e5 100644 --- a/web/src/ar/frameworks/RepositoryStep/RepositoryActionsWidget.tsx +++ b/web/src/ar/frameworks/RepositoryStep/RepositoryActionsWidget.tsx @@ -22,23 +22,30 @@ import type { RepositoryPackageType } from '@ar/common/types' import repositoryFactory from './RepositoryFactory' import type { RepositoryAbstractFactory } from './RepositoryAbstractFactory' import type { RepositoryActionsProps } from './Repository' +import { RegistryRefProvider } from './RegistryRefContext' interface RepositoryActionsWidgetProps extends RepositoryActionsProps { factory?: RepositoryAbstractFactory packageType: RepositoryPackageType + /** Pre-computed registry ref from list (path). Provided via context to actions for Setup Client. */ + registryRef?: string } export default function RepositoryActionsWidget(props: RepositoryActionsWidgetProps): JSX.Element { - const { factory = repositoryFactory, packageType, type, data, readonly, pageType } = props + const { factory = repositoryFactory, packageType, type, data, readonly, pageType, registryRef } = props const { getString } = useStrings() const repositoryType = factory?.getRepositoryType(packageType) if (!repositoryType) { return {getString('stepNotFound')} } - return repositoryType.renderActions({ - data, - readonly, - type, - pageType - }) + return ( + + {repositoryType.renderActions({ + data, + readonly, + type, + pageType + })} + + ) } diff --git a/web/src/ar/frameworks/RepositoryStep/RepositorySetupClientWidget.tsx b/web/src/ar/frameworks/RepositoryStep/RepositorySetupClientWidget.tsx index 50d96c9950..abf3d6ec9b 100644 --- a/web/src/ar/frameworks/RepositoryStep/RepositorySetupClientWidget.tsx +++ b/web/src/ar/frameworks/RepositoryStep/RepositorySetupClientWidget.tsx @@ -27,19 +27,27 @@ import type { RepositoryAbstractFactory } from './RepositoryAbstractFactory' interface RepositorySetupClientWidgetProps extends RepositoySetupClientProps { factory?: RepositoryAbstractFactory type: RepositoryPackageType + /** Pre-computed registry ref (e.g. from list). When set, used for client-setup-details API. Injected here so types don't prop-drill. */ + registryRef?: string } export default function RepositorySetupClientWidget(props: RepositorySetupClientWidgetProps): JSX.Element { - const { factory = repositoryFactory, type, onClose, repoKey, artifactKey, versionKey } = props + const { factory = repositoryFactory, type, onClose, repoKey, artifactKey, versionKey, registryRef } = props const { getString } = useStrings() const repositoryType = factory?.getRepositoryType(type) if (!repositoryType) { return {getString('stepNotFound')} } - return repositoryType.renderSetupClient({ + const content = repositoryType.renderSetupClient({ onClose, repoKey, artifactKey, versionKey }) + if (!content || !React.isValidElement(content)) { + return content + } + return React.cloneElement(content, { + registryRef + } as React.Attributes & { registryRef?: string }) } diff --git a/web/src/ar/hooks/index.ts b/web/src/ar/hooks/index.ts index 05e839063a..1eb9e108f5 100644 --- a/web/src/ar/hooks/index.ts +++ b/web/src/ar/hooks/index.ts @@ -21,7 +21,7 @@ export { useDecodedParams } from './useDecodedParams' export { useLocalStorage } from './useLocalStorage' export { useRoutes } from './useRoutes' export { useDeepCompareEffect, useDeepCompareMemo } from './useDeepCompareEffect' -export { useGetSpaceRef } from './useGetSpaceRef' +export { useGetSpaceRef, encodeRef } from './useGetSpaceRef' export { useParentContextObj } from './useParentContextObj' export { useLicenseStore } from './useLicenseStore' export { useFeatureFlags, useFeatureFlag } from './useFeatureFlag' diff --git a/web/src/ar/pages/repository-details/components/Actions/RepositoryActions.tsx b/web/src/ar/pages/repository-details/components/Actions/RepositoryActions.tsx index 56481ce3eb..2032e0eaec 100644 --- a/web/src/ar/pages/repository-details/components/Actions/RepositoryActions.tsx +++ b/web/src/ar/pages/repository-details/components/Actions/RepositoryActions.tsx @@ -19,6 +19,7 @@ import React, { useState } from 'react' import { useAllowSoftDelete } from '@ar/hooks' import { PageType } from '@ar/common/types' import ActionButton from '@ar/components/ActionButton/ActionButton' +import { useRegistryRef } from '@ar/frameworks/RepositoryStep/RegistryRefContext' import SetupClientMenuItem from './SetupClient' import type { RepositoryActionsProps } from './types' @@ -28,6 +29,7 @@ import SoftDeleteRepositoryMenuItem from './SoftDeleteRepository' export default function RepositoryActions({ data, readonly, pageType }: RepositoryActionsProps): JSX.Element { const [open, setOpen] = useState(false) const allowSoftDelete = useAllowSoftDelete() + const registryRef = useRegistryRef() return ( {allowSoftDelete && ( @@ -40,7 +42,13 @@ export default function RepositoryActions({ data, readonly, pageType }: Reposito )} setOpen(false)} /> {pageType === PageType.Table && ( - setOpen(false)} /> + setOpen(false)} + /> )} ) diff --git a/web/src/ar/pages/repository-details/components/Actions/SetupClient.tsx b/web/src/ar/pages/repository-details/components/Actions/SetupClient.tsx index e6e4f40448..fd69f9ccac 100644 --- a/web/src/ar/pages/repository-details/components/Actions/SetupClient.tsx +++ b/web/src/ar/pages/repository-details/components/Actions/SetupClient.tsx @@ -23,13 +23,14 @@ import { PermissionIdentifier, ResourceType } from '@ar/common/permissionTypes' import { useSetupClientModal } from '@ar/pages/repository-details/hooks/useSetupClientModal/useSetupClientModal' import type { RepositoryActionsProps } from './types' -export default function SetupClientMenuItem({ data, onClose }: RepositoryActionsProps): JSX.Element { +export default function SetupClientMenuItem({ data, onClose, registryRef }: RepositoryActionsProps): JSX.Element { const { getString } = useStrings() const { RbacMenuItem } = useParentComponents() const [showSetupClientModal] = useSetupClientModal({ repoKey: data.identifier, packageType: data.packageType as RepositoryPackageType, - onClose + onClose, + registryRef }) return ( <> diff --git a/web/src/ar/pages/repository-details/components/Actions/types.ts b/web/src/ar/pages/repository-details/components/Actions/types.ts index 4a130e69b5..58f8f24565 100644 --- a/web/src/ar/pages/repository-details/components/Actions/types.ts +++ b/web/src/ar/pages/repository-details/components/Actions/types.ts @@ -22,4 +22,6 @@ export interface RepositoryActionsProps { readonly: boolean pageType: PageType onClose?: () => void + /** Pre-computed registry ref from list (path + /+). When set, used for client-setup-details API. */ + registryRef?: string } diff --git a/web/src/ar/pages/repository-details/components/SetupClientContent/SetupClientContent.tsx b/web/src/ar/pages/repository-details/components/SetupClientContent/SetupClientContent.tsx index 0dbf780f0f..1101bcb33d 100644 --- a/web/src/ar/pages/repository-details/components/SetupClientContent/SetupClientContent.tsx +++ b/web/src/ar/pages/repository-details/components/SetupClientContent/SetupClientContent.tsx @@ -34,12 +34,15 @@ interface SetupClientContentProps { artifactKey?: string versionKey?: string packageType: PackageType + /** Pre-computed registry ref (e.g. from list). When set, used for client-setup-details API; otherwise use scope + repoKey. */ + registryRef?: string } export default function SetupClientContent(props: SetupClientContentProps): JSX.Element { - const { onClose, packageType, repoKey } = props + const { onClose, packageType, repoKey, registryRef: registryRefProp } = props const { getString } = useStrings() - const spaceRef = useGetSpaceRef(repoKey) + const spaceRefFromScope = useGetSpaceRef(repoKey) + const registryRef = registryRefProp ?? spaceRefFromScope const { isFetching: loading, @@ -47,7 +50,7 @@ export default function SetupClientContent(props: SetupClientContentProps): JSX. error, refetch } = useGetClientSetupDetailsQuery({ - registry_ref: spaceRef, + registry_ref: registryRef, queryParams: { artifact: props.artifactKey, version: props.versionKey diff --git a/web/src/ar/pages/repository-details/hooks/useSetupClientModal/useSetupClientModal.tsx b/web/src/ar/pages/repository-details/hooks/useSetupClientModal/useSetupClientModal.tsx index c20d05eeea..772b123652 100644 --- a/web/src/ar/pages/repository-details/hooks/useSetupClientModal/useSetupClientModal.tsx +++ b/web/src/ar/pages/repository-details/hooks/useSetupClientModal/useSetupClientModal.tsx @@ -27,10 +27,12 @@ import css from './useSetupClientModal.module.scss' export interface useSetupClientModalProps extends Omit { packageType: RepositoryPackageType onClose?: () => void + /** Pre-computed registry ref (e.g. from list). When set, used for client-setup-details API. */ + registryRef?: string } export function useSetupClientModal(props: useSetupClientModalProps) { - const { packageType, repoKey, artifactKey, versionKey, onClose } = props + const { packageType, repoKey, artifactKey, versionKey, onClose, registryRef } = props const { useModalHook } = useParentHooks() const [showModal, hideModal] = useModalHook(() => { @@ -58,6 +60,7 @@ export function useSetupClientModal(props: useSetupClientModalProps) { versionKey={versionKey} onClose={handleCloseModal} type={packageType as RepositoryPackageType} + registryRef={registryRef} /> ) diff --git a/web/src/ar/pages/repository-list/components/RepositoryListTable/RepositoryListCells.tsx b/web/src/ar/pages/repository-list/components/RepositoryListTable/RepositoryListCells.tsx index a9a280ab6b..b53be75a1b 100644 --- a/web/src/ar/pages/repository-list/components/RepositoryListTable/RepositoryListCells.tsx +++ b/web/src/ar/pages/repository-list/components/RepositoryListTable/RepositoryListCells.tsx @@ -21,6 +21,7 @@ import type { RegistryMetadata } from '@harnessio/react-har-service-client' import type { Cell, CellValue, ColumnInstance, Renderer, Row, TableInstance } from 'react-table' import ScopeBadge from '@ar/components/Badge/ScopeBadge' +import { encodeRef } from '@ar/hooks' import { useStrings } from '@ar/frameworks/strings/String' import TableCells from '@ar/components/TableCells/TableCells' import { getEntityScopeType } from '@ar/hooks/useGetPageScope' @@ -102,6 +103,8 @@ export const RepositoryDownloadsCell: CellType = ({ value }) => { } export const RepositoryActionsCell: CellType = ({ row }) => { + const { path } = row.original + const registryRef = path ? encodeRef(path) : undefined return ( { data={row.original} readonly={false} pageType={PageType.Table} + registryRef={registryRef} /> ) } diff --git a/web/src/ar/pages/repository-list/components/RepositoryListTreeView/RepositoryListTreeView.tsx b/web/src/ar/pages/repository-list/components/RepositoryListTreeView/RepositoryListTreeView.tsx index 4bd7fee3a6..d251a59320 100644 --- a/web/src/ar/pages/repository-list/components/RepositoryListTreeView/RepositoryListTreeView.tsx +++ b/web/src/ar/pages/repository-list/components/RepositoryListTreeView/RepositoryListTreeView.tsx @@ -19,7 +19,7 @@ import { compact as lodashCompact } from 'lodash-es' import { Switch } from 'react-router-dom' import { Container } from '@harnessio/uicore' -import { useAppStore, useParentHooks, useRoutes } from '@ar/hooks' +import { useAppStore, useParentHooks, useRoutes, encodeRef } from '@ar/hooks' import { PageType } from '@ar/common/types' import RouteProvider from '@ar/components/RouteProvider/RouteProvider' import RepositoryProvider from '@ar/pages/repository-details/context/RepositoryProvider' @@ -151,7 +151,9 @@ export default function RepositoryListTreeView() { const { metadata } = node const { entityType, repositoryIdentifier, artifactIdentifier, versionIdentifier } = metadata || {} switch (entityType) { - case TreeNodeEntityEnum.REGISTRY: + case TreeNodeEntityEnum.REGISTRY: { + const path = (metadata as { path?: string })?.path + const registryRef = path ? encodeRef(path) : undefined return ( ) + } case TreeNodeEntityEnum.ARTIFACT: return ( {allowSoftDelete && ( @@ -40,7 +42,13 @@ export default function UpstreamProxyActions({ data, readonly, pageType }: Upstr )} setOpen(false)} /> {pageType === PageType.Table && ( - setOpen(false)} /> + setOpen(false)} + /> )} )