From ae3d09cbe7d99ba8a9a7fefbf012905c33911827 Mon Sep 17 00:00:00 2001 From: Oleksii Kurinnyi Date: Fri, 30 Aug 2024 15:40:21 +0300 Subject: [PATCH] Do not show modal for trusted repositories (#1172) * fix: unexpected modal for untrusted repo Signed-off-by: Oleksii Kurinnyi * fix: disable the Logs and Events tabs until the workspace is created Signed-off-by: Oleksii Kurinnyi * chore: set dockerimage tag expiration date Signed-off-by: Oleksii Kurinnyi * fixup! fix: unexpected modal for untrusted repo * fixup! fix: disable the Logs and Events tabs until the workspace is created * fix: configmap samples are trusted Signed-off-by: Oleksii Kurinnyi * fix: two browser tabs when starting a factory flow Signed-off-by: Oleksii Kurinnyi * fixup! fix: two browser tabs when starting a factory flow * fix: unexpected modal for trusted source Signed-off-by: Oleksii Kurinnyi --------- Signed-off-by: Oleksii Kurinnyi --- build/dockerfiles/skaffold.Dockerfile | 2 + .../ImportFromGit/__tests__/index.spec.tsx | 121 +++++++----------- .../__tests__/index.spec.tsx | 40 +++++- .../components/UntrustedSourceModal/index.tsx | 68 +++++----- .../CreatingSteps/Apply/Devfile/index.tsx | 5 +- .../CreatingSteps/Apply/Resources/index.tsx | 5 +- .../components/WorkspaceProgress/index.tsx | 19 ++- .../src/pages/Loader/index.tsx | 7 + .../__tests__/selectors.spec.ts | 34 ++++- .../src/store/DevfileRegistries/selectors.ts | 15 ++- 10 files changed, 199 insertions(+), 117 deletions(-) diff --git a/build/dockerfiles/skaffold.Dockerfile b/build/dockerfiles/skaffold.Dockerfile index cdc26e02e..7a172831b 100644 --- a/build/dockerfiles/skaffold.Dockerfile +++ b/build/dockerfiles/skaffold.Dockerfile @@ -10,6 +10,8 @@ FROM docker.io/node:18.19.1-alpine3.19 +LABEL quay.expires-after=1w + ENV FRONTEND_LIB=../../packages/dashboard-frontend/lib/public ENV BACKEND_LIB=../../packages/dashboard-backend/lib ENV BACKEND_NODE_MODULES=../../packages/dashboard-backend/node_modules diff --git a/packages/dashboard-frontend/src/components/ImportFromGit/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/ImportFromGit/__tests__/index.spec.tsx index a974a80ac..b57c6be7f 100644 --- a/packages/dashboard-frontend/src/components/ImportFromGit/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/components/ImportFromGit/__tests__/index.spec.tsx @@ -22,6 +22,8 @@ import { FakeStoreBuilder } from '@/store/__mocks__/storeBuilder'; const { createSnapshot, renderComponent } = getComponentRenderer(getComponent); +jest.mock('@/components/UntrustedSourceModal'); + const history = createMemoryHistory({ initialEntries: ['/'], }); @@ -81,63 +83,6 @@ describe('GitRepoLocationInput', () => { expect(window.open).not.toHaveBeenCalled(); }); - describe('trusted/untrusted source', () => { - jest.mock('@/components/UntrustedSourceModal'); - - test('untrusted source', () => { - const store = new FakeStoreBuilder() - .withDwServerConfig({ - defaults: { - editor: defaultEditorId, - components: [], - plugins: [], - pvcStrategy: 'per-workspace', - }, - }) - .withWorkspacePreferences({ - 'trusted-sources': ['repo1', 'repo2'], - }) - .build(); - renderComponent(store); - - const input = screen.getByRole('textbox'); - expect(input).toBeValid(); - - userEvent.paste(input, 'http://test-location'); - - expect(input).toHaveValue('http://test-location'); - - const button = screen.getByRole('button', { name: 'Create & Open' }); - userEvent.click(button); - - const untrustedSourceModal = screen.queryByRole('dialog', { - name: /Do you trust the authors of this repository/i, - }); - expect(untrustedSourceModal).not.toBeNull(); - - expect(window.open).not.toHaveBeenCalled(); - }); - - test('trusted source', () => { - renderComponent(store); - - const input = screen.getByRole('textbox'); - expect(input).toBeValid(); - - userEvent.paste(input, 'http://test-location'); - - expect(input).toHaveValue('http://test-location'); - - const button = screen.getByRole('button', { name: 'Create & Open' }); - userEvent.click(button); - - const untrustedSourceModal = screen.queryByRole('dialog', { name: /untrusted source/i }); - expect(untrustedSourceModal).toBeNull(); - - expect(window.open).toHaveBeenCalledTimes(1); - }); - }); - describe('valid HTTP location', () => { describe('factory URL w/o other parameters', () => { test('trim spaces from the input value', () => { @@ -156,16 +101,19 @@ describe('GitRepoLocationInput', () => { expect(button).toBeEnabled(); userEvent.click(button); + + // trust the resource + const continueButton = screen.getByRole('button', { name: 'Continue' }); + userEvent.click(continueButton); + // the selected editor ID should be added to the URL expect(window.open).toHaveBeenLastCalledWith( 'http://localhost/#http://test-location/', '_blank', ); expect(window.open).toHaveBeenCalledTimes(1); - - userEvent.type(input, '{enter}'); - expect(window.open).toHaveBeenCalledTimes(2); }); + test('editor definition and image are empty', () => { renderComponent(store); @@ -181,15 +129,17 @@ describe('GitRepoLocationInput', () => { expect(button).toBeEnabled(); userEvent.click(button); + + // trust the resource + const continueButton = screen.getByRole('button', { name: 'Continue' }); + userEvent.click(continueButton); + // the selected editor ID should be added to the URL expect(window.open).toHaveBeenLastCalledWith( 'http://localhost/#http://test-location/', '_blank', ); expect(window.open).toHaveBeenCalledTimes(1); - - userEvent.type(input, '{enter}'); - expect(window.open).toHaveBeenCalledTimes(2); }); test('editor definition is defined, editor image is empty', () => { @@ -207,15 +157,17 @@ describe('GitRepoLocationInput', () => { expect(button).toBeEnabled(); userEvent.click(button); + + // trust the resource + const continueButton = screen.getByRole('button', { name: 'Continue' }); + userEvent.click(continueButton); + // the selected editor ID should be added to the URL expect(window.open).toHaveBeenLastCalledWith( 'http://localhost/#http://test-location/?che-editor=che-incubator%2Fche-code%2Finsiders', '_blank', ); expect(window.open).toHaveBeenCalledTimes(1); - - userEvent.type(input, '{enter}'); - expect(window.open).toHaveBeenCalledTimes(2); }); test('editor definition is empty, editor image is defined', () => { @@ -233,15 +185,17 @@ describe('GitRepoLocationInput', () => { expect(button).toBeEnabled(); userEvent.click(button); + + // trust the resource + const continueButton = screen.getByRole('button', { name: 'Continue' }); + userEvent.click(continueButton); + // the selected editor ID should be added to the URL expect(window.open).toHaveBeenLastCalledWith( 'http://localhost/#http://test-location/?editor-image=custom-editor-image', '_blank', ); expect(window.open).toHaveBeenCalledTimes(1); - - userEvent.type(input, '{enter}'); - expect(window.open).toHaveBeenCalledTimes(2); }); test('editor definition and editor image are defined', () => { @@ -259,15 +213,17 @@ describe('GitRepoLocationInput', () => { expect(button).toBeEnabled(); userEvent.click(button); + + // trust the resource + const continueButton = screen.getByRole('button', { name: 'Continue' }); + userEvent.click(continueButton); + // the selected editor ID should be added to the URL expect(window.open).toHaveBeenLastCalledWith( 'http://localhost/#http://test-location/?che-editor=che-incubator%2Fche-code%2Finsiders&editor-image=custom-editor-image', '_blank', ); expect(window.open).toHaveBeenCalledTimes(1); - - userEvent.type(input, '{enter}'); - expect(window.open).toHaveBeenCalledTimes(2); }); }); @@ -287,15 +243,17 @@ describe('GitRepoLocationInput', () => { expect(button).toBeEnabled(); userEvent.click(button); + + // trust the resource + const continueButton = screen.getByRole('button', { name: 'Continue' }); + userEvent.click(continueButton); + // the selected editor ID should NOT be added to the URL, as the URL parameter has higher priority expect(window.open).toHaveBeenLastCalledWith( 'http://localhost/#http://test-location/?che-editor=other-editor-id', '_blank', ); expect(window.open).toHaveBeenCalledTimes(1); - - userEvent.type(input, '{enter}'); - expect(window.open).toHaveBeenCalledTimes(2); }); test('editor definition and editor image are defined, and `editor-image` is provided', () => { @@ -313,6 +271,11 @@ describe('GitRepoLocationInput', () => { expect(button).toBeEnabled(); userEvent.click(button); + + // trust the resource + const continueButton = screen.getByRole('button', { name: 'Continue' }); + userEvent.click(continueButton); + // the selected editor ID should be added to the URL expect(window.open).toHaveBeenLastCalledWith( 'http://localhost/#http://test-location/?editor-image=custom-editor-image', @@ -369,6 +332,10 @@ describe('GitRepoLocationInput', () => { userEvent.click(buttonCreate); + // trust the resource + const continueButton = screen.getByRole('button', { name: 'Continue' }); + userEvent.click(continueButton); + expect(window.open).toHaveBeenCalledTimes(1); expect(window.open).toHaveBeenLastCalledWith( 'http://localhost/#git@github.com:user/repo.git?che-editor=che-incubator%2Fche-code%2Finsiders&editor-image=custom-editor-image', @@ -398,6 +365,10 @@ describe('GitRepoLocationInput', () => { userEvent.click(buttonCreate); + // trust the resource + const continueButton = screen.getByRole('button', { name: 'Continue' }); + userEvent.click(continueButton); + expect(window.open).toHaveBeenCalledTimes(1); expect(window.open).toHaveBeenLastCalledWith( 'http://localhost/#git@github.com:user/repo.git?che-editor=other-editor-id', diff --git a/packages/dashboard-frontend/src/components/UntrustedSourceModal/__tests__/index.spec.tsx b/packages/dashboard-frontend/src/components/UntrustedSourceModal/__tests__/index.spec.tsx index 89a385cb8..b65cf90e6 100644 --- a/packages/dashboard-frontend/src/components/UntrustedSourceModal/__tests__/index.spec.tsx +++ b/packages/dashboard-frontend/src/components/UntrustedSourceModal/__tests__/index.spec.tsx @@ -15,7 +15,7 @@ import { Provider } from 'react-redux'; import { Action, Store } from 'redux'; import UntrustedSourceModal from '@/components/UntrustedSourceModal'; -import getComponentRenderer, { screen, waitFor } from '@/services/__mocks__/getComponentRenderer'; +import getComponentRenderer, { screen } from '@/services/__mocks__/getComponentRenderer'; import { AppThunk } from '@/store'; import { FakeStoreBuilder } from '@/store/__mocks__/storeBuilder'; import { WorkspacePreferencesActionCreators } from '@/store/Workspaces/Preferences'; @@ -108,12 +108,14 @@ describe('Untrusted Repo Warning Modal', () => { }); test('click the continue button', async () => { + jest.useFakeTimers(); + const store = storeBuilder .withWorkspacePreferences({ 'trusted-sources': ['repo1', 'repo2'], }) .build(); - renderComponent(store, 'source-location'); + const { reRenderComponent } = renderComponent(store, 'source-location'); const continueButton = screen.getByRole('button', { name: 'Continue' }); @@ -122,10 +124,20 @@ describe('Untrusted Repo Warning Modal', () => { continueButton.click(); - await waitFor(() => expect(mockOnContinue).toHaveBeenCalled()); + const nextStore = new FakeStoreBuilder() + .withWorkspacePreferences({ + 'trusted-sources': ['repo1', 'repo2', 'source-location'], + }) + .build(); + reRenderComponent(nextStore, 'source-location'); + + await jest.advanceTimersByTimeAsync(5000); expect(mockAddTrustedSource).toHaveBeenCalledTimes(1); expect(mockAddTrustedSource).toHaveBeenCalledWith('source-location'); + expect(mockOnContinue).toHaveBeenCalledTimes(1); + + jest.useRealTimers(); }); test('trust all checkbox is clicked', () => { @@ -163,6 +175,28 @@ describe('Untrusted Repo Warning Modal', () => { expect(mockOnContinue).toHaveBeenCalledTimes(1); }); + + test('re-check if source is trusted', () => { + const store = storeBuilder + .withWorkspacePreferences({ + 'trusted-sources': ['source-location'], + }) + .build(); + const { reRenderComponent } = renderComponent(store, 'source-location', false); + + // no warning window + expect(screen.queryByRole('dialog')).toBeNull(); + + // should not call onContinue + expect(mockOnContinue).not.toHaveBeenCalled(); + + // open the modal + reRenderComponent(store, 'source-location', true); + + // should call mockOnContinue + expect(mockOnContinue).toHaveBeenCalledTimes(1); + expect(screen.queryByRole('dialog')).toBeNull(); + }); }); function getComponent(store: Store, location: string, isOpen = true): React.ReactElement { diff --git a/packages/dashboard-frontend/src/components/UntrustedSourceModal/index.tsx b/packages/dashboard-frontend/src/components/UntrustedSourceModal/index.tsx index 1bfc1dca9..bfbb4918d 100644 --- a/packages/dashboard-frontend/src/components/UntrustedSourceModal/index.tsx +++ b/packages/dashboard-frontend/src/components/UntrustedSourceModal/index.tsx @@ -39,7 +39,9 @@ export type Props = MappedProps & { onContinue: () => void; }; export type State = { - continueDisabled: boolean; + // true if `onContinue` can be called + canContinue: boolean; + continueButtonDisabled: boolean; isTrusted: boolean; trustAllCheckbox: boolean; }; @@ -52,20 +54,21 @@ class UntrustedSourceModal extends React.Component { super(props); this.state = { - continueDisabled: false, - isTrusted: this.props.isTrusted(props.location), + canContinue: true, + continueButtonDisabled: false, + isTrusted: this.props.isTrustedSource(props.location), trustAllCheckbox: false, }; } public shouldComponentUpdate(nextProps: Readonly, nextState: Readonly): boolean { - const isTrusted = this.props.isTrusted(this.props.location); - const nextIsTrusted = nextProps.isTrusted(nextProps.location); - if (isTrusted !== nextIsTrusted || this.state.isTrusted !== nextState.isTrusted) { + const isTrusted = this.props.isTrustedSource(this.props.location); + const nextIsTrusted = nextProps.isTrustedSource(nextProps.location); + if (isTrusted !== nextIsTrusted) { return true; } - if (this.state.continueDisabled !== nextState.continueDisabled) { + if (this.state.continueButtonDisabled !== nextState.continueButtonDisabled) { return true; } @@ -85,27 +88,31 @@ class UntrustedSourceModal extends React.Component { } public componentDidMount(): void { - this.init(); + if (this.props.isOpen && this.state.isTrusted) { + this.setState({ + canContinue: false, + }); + this.props.onContinue(); + } } - public componentDidUpdate(): void { - this.init(); - } + public componentDidUpdate(prevProps: Readonly): void { + const isTrusted = this.props.isTrustedSource(this.props.location); - private init() { - const isTrusted = this.props.isTrusted(this.props.location); - if (this.props.isOpen && isTrusted) { - this.setState({ - continueDisabled: false, - isTrusted, - trustAllCheckbox: false, - }); + this.setState({ + isTrusted, + }); - this.props.onContinue(); - } else { + if ( + prevProps.isOpen === false && + this.props.isOpen === true && + isTrusted === true && + this.state.canContinue === true + ) { this.setState({ - isTrusted, + canContinue: false, }); + this.props.onContinue(); } } @@ -115,7 +122,8 @@ class UntrustedSourceModal extends React.Component { private handleClose(): void { this.setState({ - continueDisabled: false, + canContinue: true, + continueButtonDisabled: false, trustAllCheckbox: false, }); @@ -124,7 +132,10 @@ class UntrustedSourceModal extends React.Component { private async handleContinue(): Promise { try { - this.setState({ continueDisabled: true }); + this.setState({ + canContinue: false, + continueButtonDisabled: true, + }); await this.updateTrustedSources(); @@ -137,8 +148,7 @@ class UntrustedSourceModal extends React.Component { }); } - this.setState({ continueDisabled: false, trustAllCheckbox: false }); - this.props.onClose?.(); + this.handleClose(); } private async updateTrustedSources(): Promise { @@ -155,7 +165,7 @@ class UntrustedSourceModal extends React.Component { } private buildModalFooter(): React.ReactNode { - const { continueDisabled } = this.state; + const { continueButtonDisabled } = this.state; return ( @@ -163,7 +173,7 @@ class UntrustedSourceModal extends React.Component { key="continue" variant={ButtonVariant.primary} onClick={() => this.handleContinue()} - isDisabled={continueDisabled} + isDisabled={continueButtonDisabled} > Continue @@ -222,7 +232,7 @@ class UntrustedSourceModal extends React.Component { const mapStateToProps = (state: AppState) => ({ trustedSources: selectPreferencesTrustedSources(state), - isTrusted: selectPreferencesIsTrustedSource(state), + isTrustedSource: selectPreferencesIsTrustedSource(state), }); const connector = connect(mapStateToProps, workspacePreferencesActionCreators, null, { diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/index.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/index.tsx index a388078a1..df0796427 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/index.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Devfile/index.tsx @@ -244,10 +244,13 @@ class CreatingStepApplyDevfile extends ProgressStep { const workspace = this.findTargetWorkspace(this.props, this.state); if (workspace !== undefined) { + // preserve the current active tab + const tabName = new URLSearchParams(this.props.history.location.search).get('tab'); + // the workspace has been created, go to the next step const nextLocation = buildIdeLoaderLocation(workspace); this.props.history.location.pathname = nextLocation.pathname; - this.props.history.location.search = ''; + this.props.history.location.search = tabName ? `?tab=${tabName}` : ''; const url = toHref(this.props.history, nextLocation); this.tabManager.rename(url); diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Resources/index.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Resources/index.tsx index 4e0a4004b..c9195f395 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Resources/index.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/CreatingSteps/Apply/Resources/index.tsx @@ -190,10 +190,13 @@ class CreatingStepApplyResources extends ProgressStep { const targetWorkspace = this.findTargetWorkspace(this.props, this.state); if (targetWorkspace) { + // preserve the current active tab + const tabName = new URLSearchParams(this.props.history.location.search).get('tab'); + // the workspace has been created, go to the next step const nextLocation = buildIdeLoaderLocation(targetWorkspace); this.props.history.location.pathname = nextLocation.pathname; - this.props.history.location.search = ''; + this.props.history.location.search = tabName ? `?tab=${tabName}` : ''; const url = toHref(this.props.history, nextLocation); this.tabManager.rename(url); diff --git a/packages/dashboard-frontend/src/components/WorkspaceProgress/index.tsx b/packages/dashboard-frontend/src/components/WorkspaceProgress/index.tsx index 57229de0a..12ebf070f 100644 --- a/packages/dashboard-frontend/src/components/WorkspaceProgress/index.tsx +++ b/packages/dashboard-frontend/src/components/WorkspaceProgress/index.tsx @@ -144,14 +144,23 @@ class Progress extends React.Component { private init(props: Props, state: State, prevProps: Props | undefined): void { if (this.state.isSourceTrustedWarningOpen === true) { - const { sourceUrl } = this.state.factoryParams; - const isTrustedSource = this.props.isTrustedSource(sourceUrl); - const isRegistryDevfile = this.props.isRegistryDevfile(sourceUrl); - // trust source if it is taken from the registry or it is in the list of trusted sources - if (isRegistryDevfile || isTrustedSource) { + const workspace = this.findTargetWorkspace(props); + if (workspace !== undefined) { + // if workspace is created, close the warning this.setState({ isSourceTrustedWarningOpen: false, }); + } else { + // if workspace is not created yet, check if source is trusted + const { sourceUrl } = this.state.factoryParams; + const isTrustedSource = this.props.isTrustedSource(sourceUrl); + const isRegistryDevfile = this.props.isRegistryDevfile(sourceUrl); + // trust source if it is taken from the registry or it is in the list of trusted sources + if (isRegistryDevfile || isTrustedSource) { + this.setState({ + isSourceTrustedWarningOpen: false, + }); + } } } diff --git a/packages/dashboard-frontend/src/pages/Loader/index.tsx b/packages/dashboard-frontend/src/pages/Loader/index.tsx index c6a04f5c9..c88efc95c 100644 --- a/packages/dashboard-frontend/src/pages/Loader/index.tsx +++ b/packages/dashboard-frontend/src/pages/Loader/index.tsx @@ -91,6 +91,9 @@ export class LoaderPage extends React.PureComponent { } const showToastAlert = activeTabKey !== LoaderTab.Progress; + const isLogsTabDisabled = workspace === undefined; + const isEventsTabDisabled = workspace === undefined; + return ( @@ -126,6 +129,8 @@ export class LoaderPage extends React.PureComponent { title={LoaderTab.Logs} data-testid="loader-logs-tab" id="loader-logs-tab" + isDisabled={isLogsTabDisabled} + isAriaDisabled={isLogsTabDisabled} > @@ -134,6 +139,8 @@ export class LoaderPage extends React.PureComponent { title={LoaderTab.Events} data-testid="loader-events-tab" id="loader-events-tab" + isDisabled={isEventsTabDisabled} + isAriaDisabled={isEventsTabDisabled} > diff --git a/packages/dashboard-frontend/src/store/DevfileRegistries/__tests__/selectors.spec.ts b/packages/dashboard-frontend/src/store/DevfileRegistries/__tests__/selectors.spec.ts index 85f80643a..78f307680 100644 --- a/packages/dashboard-frontend/src/store/DevfileRegistries/__tests__/selectors.spec.ts +++ b/packages/dashboard-frontend/src/store/DevfileRegistries/__tests__/selectors.spec.ts @@ -20,11 +20,15 @@ import devfileApi from '@/services/devfileApi'; import { che } from '@/services/models'; import { AppState } from '@/store'; import { FakeStoreBuilder } from '@/store/__mocks__/storeBuilder'; -import { selectDefaultDevfile, selectIsRegistryDevfile } from '@/store/DevfileRegistries/selectors'; +import { + selectDefaultDevfile, + selectIsRegistryDevfile, + selectRegistriesErrors, +} from '@/store/DevfileRegistries/selectors'; describe('devfileRegistries selectors', () => { const registryUrl = 'https://registry-url'; - const sampleResourceUrl = 'https://resources-url'; + const sampleResourceUrl = 'https://resources-url/devfile.yaml'; const registryMetadata = { displayName: 'Empty Workspace', description: 'Start an empty remote development environment', @@ -115,7 +119,33 @@ describe('devfileRegistries selectors', () => { const registryDevfileUrl = `${registryUrl}/devfile.yaml`; expect(ifRegistryDevfileFn(registryDevfileUrl)).toBeTruthy(); + const registryDevfileUrl2 = sampleResourceUrl; + expect(ifRegistryDevfileFn(registryDevfileUrl2)).toBeTruthy(); + const otherDevfileUrl = 'https://other-url/devfile.yaml'; expect(ifRegistryDevfileFn(otherDevfileUrl)).toBeFalsy(); }); + + it('should return error', () => { + const error = `Failed to fetch registry metadata.`; + const fakeStore = new FakeStoreBuilder() + .withDevfileRegistries({ + registries: { + [registryUrl]: { + error, + }, + }, + }) + .withDwServerConfig({ + defaults: { + components: defaultComponents, + }, + } as api.IServerConfig) + .build() as MockStoreEnhanced>; + const state = fakeStore.getState(); + + expect(selectRegistriesErrors(state)).toStrictEqual([ + { url: registryUrl, errorMessage: error }, + ]); + }); }); diff --git a/packages/dashboard-frontend/src/store/DevfileRegistries/selectors.ts b/packages/dashboard-frontend/src/store/DevfileRegistries/selectors.ts index 1f13e9bb9..6ab562d15 100644 --- a/packages/dashboard-frontend/src/store/DevfileRegistries/selectors.ts +++ b/packages/dashboard-frontend/src/store/DevfileRegistries/selectors.ts @@ -35,7 +35,20 @@ export const selectRegistriesMetadata = createSelector(selectState, devfileRegis export const selectIsRegistryDevfile = createSelector(selectState, state => { const registriesUrls = Object.keys(state.registries); - return (url: string) => registriesUrls.some(registryUrl => url.startsWith(registryUrl)); + + return (url: string) => + registriesUrls.some(registryUrl => { + const matchRegistryUrl = url.startsWith(registryUrl); + if (matchRegistryUrl === true) { + return true; + } + + // if the url is not a subpath of the registry url, + // check if it equals to a sample source url + const registryMetadata = state.registries[registryUrl].metadata || []; + + return registryMetadata.some(meta => meta.links?.v2 === url); + }); }); export const selectRegistriesErrors = createSelector(selectState, state => {