diff --git a/src/pages/GetStarted/GetStartedTab/SamplesListGallery.tsx b/src/pages/GetStarted/GetStartedTab/SamplesListGallery.tsx index 3bb2b18ff..b6fc4d592 100644 --- a/src/pages/GetStarted/GetStartedTab/SamplesListGallery.tsx +++ b/src/pages/GetStarted/GetStartedTab/SamplesListGallery.tsx @@ -32,6 +32,9 @@ import * as DevfileRegistriesStore from '../../../store/DevfileRegistries'; import { SampleCard } from './SampleCard'; import { AlertItem } from '../../../services/helpers/types'; import { selectMetadataFiltered } from '../../../store/DevfileRegistries/selectors'; +import { selectWorkspacesSettings } from '../../../store/Workspaces/Settings/selectors'; +import * as FactoryResolverStore from '../../../store/FactoryResolver'; +import stringify from '../../../services/helpers/editor'; type Props = MappedProps @@ -85,8 +88,17 @@ export class SamplesListGallery extends React.PureComponent { private async fetchDevfile(meta: che.DevfileMetaData): Promise { try { - const devfile = await this.props.requestDevfile(meta.links.self) as string; - this.props.onCardClick(devfile, meta.displayName); + const cheDevworkspaceEnabled = this.props.workspacesSettings['che.devworkspaces.enabled'] === 'true'; + let devfileContent; + if (cheDevworkspaceEnabled) { + const link = meta.links.v2; + await this.props.requestFactoryResolver(link); + const { devfile } = this.props.factoryResolver.resolver; + devfileContent = stringify(devfile); + } else { + devfileContent = await this.props.requestDevfile(meta.links.self) as string; + } + this.props.onCardClick(devfileContent, meta.displayName); } catch (e) { console.warn('Failed to load devfile.', e); @@ -134,12 +146,15 @@ export class SamplesListGallery extends React.PureComponent { const mapStateToProps = (state: AppState) => ({ metadataFiltered: selectMetadataFiltered(state), + workspacesSettings: selectWorkspacesSettings(state), + factoryResolver: state.factoryResolver, }); const connector = connect( mapStateToProps, { ...DevfileRegistriesStore.actionCreators, + ...FactoryResolverStore.actionCreators, } ); diff --git a/src/pages/GetStarted/GetStartedTab/__tests__/SamplesListGallery.spec.tsx b/src/pages/GetStarted/GetStartedTab/__tests__/SamplesListGallery.spec.tsx index f6cdeea33..6413b43d3 100644 --- a/src/pages/GetStarted/GetStartedTab/__tests__/SamplesListGallery.spec.tsx +++ b/src/pages/GetStarted/GetStartedTab/__tests__/SamplesListGallery.spec.tsx @@ -11,17 +11,47 @@ */ import React from 'react'; -import { Store } from 'redux'; -import { render, screen, RenderResult, fireEvent } from '@testing-library/react'; +import { Action, Store } from 'redux'; +import { render, screen, RenderResult, fireEvent, waitFor } from '@testing-library/react'; import mockAxios from 'axios'; import SamplesListGallery from '../SamplesListGallery'; import { Provider } from 'react-redux'; import mockMetadata from '../../__tests__/devfileMetadata.json'; import { FakeStoreBuilder } from '../../../../store/__mocks__/storeBuilder'; import { BrandingData } from '../../../../services/bootstrap/branding.constant'; +import { WorkspaceSettings } from 'che'; +import * as FactoryResolverStore from '../../../../store/FactoryResolver'; +import { AppThunk } from '../../../../store'; + +const requestFactoryResolverMock = jest.fn().mockResolvedValue(undefined); + +jest.mock('../../../../store/FactoryResolver', () => { + return { + actionCreators: { + requestFactoryResolver: (location: string, overrideParams?: { + [params: string]: string + }) => async (): Promise => { + if (!overrideParams) { + requestFactoryResolverMock(location); + } else { + requestFactoryResolverMock(location, overrideParams); + } + } + } + }; +}); describe('Samples List Gallery', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.runOnlyPendingTimers(); + jest.useRealTimers(); + }); + function renderGallery( store: Store, onCardClicked: () => void = (): void => undefined @@ -42,6 +72,17 @@ describe('Samples List Gallery', () => { expect(cards.length).toEqual(26); }); + it('should render cards with v2 metadata only', () => { + // eslint-disable-next-line + const store = createFakeStoreWithMetadata(true); + renderGallery(store); + + const cards = screen.getAllByRole('article'); + // only one link is with devfile v2 format + expect(cards.length).toEqual(1); + + }); + it('should handle "onCardClick" event', async () => { let resolveFn: { @@ -66,6 +107,33 @@ describe('Samples List Gallery', () => { }); + it('should handle "onCardClick" event for v2 metadata', async () => { + + let resolveFn: { + (value?: unknown): void; + }; + const onCardClickedPromise = new Promise(resolve => resolveFn = resolve); + const onCardClicked = jest.fn(() => resolveFn()); + + // eslint-disable-next-line + const store = createFakeStoreWithMetadata(true); + renderGallery(store, onCardClicked); + + (mockAxios.get as any).mockResolvedValueOnce({ + data: {}, + }); + + const cardHeader = screen.getByText('Java with Spring Boot and MySQL'); + fireEvent.click(cardHeader); + + await onCardClickedPromise; + expect(onCardClicked).toHaveBeenCalled(); + jest.runOnlyPendingTimers(); + // should have been called with the v2 link + await waitFor(() => expect(requestFactoryResolverMock).toHaveBeenCalledWith('http://my-fake-repository.com/')); + + }); + it('should render empty state', () => { // eslint-disable-next-line const store = createFakeStoreWithoutMetadata(); @@ -77,19 +145,30 @@ describe('Samples List Gallery', () => { }); -function createFakeStore(metadata?: che.DevfileMetaData[]): Store { +function createFakeStore(metadata?: che.DevfileMetaData[], devWorkspaceEnabled?: boolean): Store { const registries = {}; if (metadata) { registries['registry-location'] = { metadata, }; } + const workspaceSettings = {}; + if (devWorkspaceEnabled) { + workspaceSettings['che.devworkspaces.enabled'] = 'true'; + } return new FakeStoreBuilder() .withBranding({ docs: { storageTypes: 'https://docs.location' } } as BrandingData) + .withWorkspacesSettings(workspaceSettings as WorkspaceSettings) + .withFactoryResolver({ + v: '4.0', + source: 'devfile.yaml', + devfile: {}, + location: 'http://fake-location', + }) .withDevfileRegistries({ registries }) .build(); } @@ -98,6 +177,6 @@ function createFakeStoreWithoutMetadata(): Store { return createFakeStore(); } -function createFakeStoreWithMetadata(): Store { - return createFakeStore(mockMetadata); +function createFakeStoreWithMetadata(devWorkspaceEnabled?: boolean): Store { + return createFakeStore(mockMetadata, devWorkspaceEnabled); } diff --git a/src/pages/GetStarted/__tests__/devfileMetadata.json b/src/pages/GetStarted/__tests__/devfileMetadata.json index 0eca93011..f73f45eca 100644 --- a/src/pages/GetStarted/__tests__/devfileMetadata.json +++ b/src/pages/GetStarted/__tests__/devfileMetadata.json @@ -185,7 +185,8 @@ "icon": "/images/springboot.svg", "globalMemoryLimit": "3372Mi", "links": { - "self": "/devfiles/java-mysql/devfile.yaml" + "self": "/devfiles/java-mysql/devfile.yaml", + "v2": "http://my-fake-repository.com/" } }, { diff --git a/src/store/DevfileRegistries/selectors.ts b/src/store/DevfileRegistries/selectors.ts index af2680033..7b85be8c3 100644 --- a/src/store/DevfileRegistries/selectors.ts +++ b/src/store/DevfileRegistries/selectors.ts @@ -13,15 +13,23 @@ import { createSelector } from 'reselect'; import { AppState } from '../'; import match from '../../services/helpers/filter'; +import { selectWorkspacesSettingsState } from '../Workspaces/Settings/selectors'; const selectState = (state: AppState) => state.devfileRegistries; export const selectRegistriesMetadata = createSelector( selectState, - state => { - const registriesMetadata = Object.values(state.registries) - .map(registryMetadata => registryMetadata.metadata || []); - return mergeRegistriesMetadata(registriesMetadata); + selectWorkspacesSettingsState, + (devfileRegistriesState, workspacesSettingsState) => { + const registriesMetadata = Object.values(devfileRegistriesState.registries).map(registryMetadata => registryMetadata.metadata || []); + const metadata = mergeRegistriesMetadata(registriesMetadata); + const cheDevworkspaceEnabled = workspacesSettingsState.settings['che.devworkspaces.enabled'] === 'true'; + if (cheDevworkspaceEnabled) { + return filterDevfileV2Metadata(metadata); + } else { + return metadata; + } + } ); @@ -69,6 +77,10 @@ function mergeRegistriesMetadata(registriesMetadata: Array): Array { + return metadata.filter(metadata => metadata.links?.v2); +} + export const selectDevfileSchema = createSelector( selectState, state => state.schema.schema,