diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index 752aee5fa..634c4a78a 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -4,7 +4,7 @@ on: [pull_request] jobs: test-and-build: - runs-on: it + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: @@ -35,7 +35,7 @@ jobs: run: | docker build . --tag=nexus-web:fresh - name: Start services - run: docker-compose -f ci/docker-compose.yml up -d && sleep 60 + run: docker compose -f ci/docker-compose.yml up -d && sleep 60 - name: Copy nexus-web into Cypress container # avoids permission issue where cypress writes screenshots to host with root as user # which we can't then delete easily @@ -55,4 +55,4 @@ jobs: # --key ${{ secrets.CYPRESS_RECORD_KEY }} - name: Cleanup Docker Containers if: ${{ always() }} - run: docker-compose -f ci/docker-compose.yml down --rmi "local" --volumes + run: docker compose -f ci/docker-compose.yml down --rmi "local" --volumes diff --git a/src/shared/components/IDResolution/ResponseViewer.tsx b/src/shared/components/IDResolution/ResponseViewer.tsx index e147dc15d..e3936061e 100644 --- a/src/shared/components/IDResolution/ResponseViewer.tsx +++ b/src/shared/components/IDResolution/ResponseViewer.tsx @@ -4,12 +4,20 @@ type Props = { data: any; header?: string; showHeader?: boolean; + collapsed?: boolean; + style?: React.CSSProperties; }; -const ResponseViewer = ({ data, showHeader = false, header = '' }: Props) => { +const ResponseViewer = ({ + data, + showHeader = false, + header = '', + collapsed = false, + style = {}, +}: Props) => { return ( { enableClipboard={false} displayObjectSize={false} displayDataTypes={false} - style={{ maxWidth: 'inherit', width: '600px' }} + style={{ maxWidth: 'inherit', width: '600px', ...style }} /> ); }; diff --git a/src/shared/components/Preview/PDFPreview.tsx b/src/shared/components/Preview/PDFPreview.tsx index b7c76dfa5..c6631874f 100644 --- a/src/shared/components/Preview/PDFPreview.tsx +++ b/src/shared/components/Preview/PDFPreview.tsx @@ -56,6 +56,7 @@ const PDFThumbnail = ({ file={url} onLoadError={console.error} renderMode="svg" + options={{ isEvalSupported: false }} > { const queryClient = new QueryClient(); @@ -421,6 +422,172 @@ describe('DataTableContainer - Selection', () => { }); }); +describe('DataTableContainer - Row Click', () => { + const queryClient = new QueryClient(); + let dataTableContainer: JSX.Element; + let container: HTMLElement; + let rerender: (ui: React.ReactElement) => void; + let user: UserEvent; + let server: ReturnType; + let component: RenderResult; + let nexus: ReturnType; + let nexusSpy: jest.SpyInstance; + + beforeAll(() => { + server = setupServer( + dashboardResource, + dashboardVocabulary, + fetchResourceForDownload + ); + + server.listen(); + }); + + beforeEach(async () => { + const history = createMemoryHistory({}); + + nexus = createNexusClient({ + fetch, + uri: deltaPath(), + }); + const store = configureStore(history, { nexus }, {}); + dataTableContainer = ( + + + + + + + + + + ); + + component = render(dataTableContainer); + + container = component.container; + rerender = component.rerender; + user = userEvent.setup(); + + nexusSpy = jest + .spyOn(nexus, 'httpGet') + .mockImplementation(request => + request.path.toString().includes('format') + ? Promise.resolve([getMockResource('doesnt-matter', {}, 'agents')]) + : Promise.resolve(getMockResource('doesnt-matter', {}, 'agents')) + ); + }); + + // reset any request handlers that are declared as a part of our tests + // (i.e. for testing one-time error scenarios) + afterEach(() => { + cleanup(); + queryClient.clear(); + localStorage.clear(); + nexusSpy.mockClear(); + }); + + afterAll(() => { + server.resetHandlers(); + server.close(); + }); + + const visibleTableRows = () => { + return container.querySelectorAll('table tbody tr.data-table-row'); + }; + + const waitForTableRows = async (expectedRowsCount: number) => { + return await waitFor(() => { + const rows = visibleTableRows(); + expect(rows.length).toEqual(expectedRowsCount); + return rows; + }); + }; + + it('requests correct resource from delta when user clicks on row with revision in self', async () => { + const selfWithRevision = + 'https://localhost:3000/resources/bbp/agents/_/persons%2Fc3358e61-7650-4954-99b7-f7572cbf5d5g?rev=30'; + + const resources = [getMockStudioResource('Malory', `${selfWithRevision}`)]; + + server.use(sparqlViewResultHandler(resources)); + component.unmount(); + rerender(dataTableContainer); + await waitForTableRows(1); + + await user.click(screen.getByText('Malory')); + expect(nexusSpy).toHaveBeenLastCalledWith({ + path: `${selfWithRevision}&format=expanded`, + headers: { Accept: 'application/json' }, + }); + }); + + it('requests correct resource from delta when user clicks on row with tag in self', async () => { + const selfWithTag = + 'https://localhost:3000/resources/bbp/agents/_/persons%2Fc3358e61-7650-4954-99b7-f7572cbf5d5g?tag=30'; + + const resources = [getMockStudioResource('Malory', `${selfWithTag}`)]; + + server.use(sparqlViewResultHandler(resources)); + component.unmount(); + rerender(dataTableContainer); + await waitForTableRows(1); + + await user.click(screen.getByText('Malory')); + expect(nexusSpy).toHaveBeenLastCalledWith({ + path: `${selfWithTag}&format=expanded`, + headers: { Accept: 'application/json' }, + }); + }); + + it('requests correct resource from delta when user clicks on row with tag and revision in self', async () => { + const selfWithTagAndRev = + 'https://localhost:3000/resources/bbp/agents/_/persons%2Fc3358e61-7650-4954-99b7-f7572cbf5d5g?tag=30&rev=2-'; + + const resources = [getMockStudioResource('Malory', `${selfWithTagAndRev}`)]; + + server.use(sparqlViewResultHandler(resources)); + component.unmount(); + rerender(dataTableContainer); + await waitForTableRows(1); + + await user.click(screen.getByText('Malory')); + expect(nexusSpy).toHaveBeenLastCalledWith({ + path: `${selfWithTagAndRev}&format=expanded`, + headers: { Accept: 'application/json' }, + }); + }); + + it('requests correct resource from delta when user clicks on row with no tag or revision in self', async () => { + const selfWithoutTagOrRev = + 'https://localhost:3000/resources/bbp/agents/_/persons%2Fc3358e61-7650-4954-99b7-f7572cbf5d5g'; + + const resources = [ + getMockStudioResource('Malory', `${selfWithoutTagOrRev}`), + ]; + + server.use(sparqlViewResultHandler(resources)); + component.unmount(); + rerender(dataTableContainer); + await waitForTableRows(1); + + await user.click(screen.getByText('Malory')); + expect(nexusSpy).toHaveBeenLastCalledWith({ + path: `${selfWithoutTagOrRev}?format=expanded`, + headers: { Accept: 'application/json' }, + }); + }); +}); + const dashboardErrorResponse = { '@context': 'https://bluebrain.github.io/nexus/contexts/error.json', '@type': 'ResourceNotFound', diff --git a/src/shared/containers/DataTableContainer.tsx b/src/shared/containers/DataTableContainer.tsx index 69a85f2a4..64bea81a9 100644 --- a/src/shared/containers/DataTableContainer.tsx +++ b/src/shared/containers/DataTableContainer.tsx @@ -219,9 +219,11 @@ const DataTableContainer: React.FC = ({ if (resource['@type'] === 'Project') { return; } + const url = new URL(selfUrl); + url.searchParams.set('format', 'expanded'); nexus .httpGet({ - path: `${selfUrl}?format=expanded`, + path: `${url.toString()}`, headers: { Accept: 'application/json' }, }) .then((fullIdResponse: Resource) => { diff --git a/src/shared/containers/ResourceViewContainer.tsx b/src/shared/containers/ResourceViewContainer.tsx index 12d2dbb82..6f7c0f756 100644 --- a/src/shared/containers/ResourceViewContainer.tsx +++ b/src/shared/containers/ResourceViewContainer.tsx @@ -43,6 +43,7 @@ import VideoPluginContainer from './VideoPluginContainer/VideoPluginContainer'; import { useMutation } from 'react-query'; export const DEFAULT_ACTIVE_TAB_KEY = '#JSON'; +import ResponseViewer from '../../shared/components/IDResolution/ResponseViewer'; export type PluginMapping = { [pluginKey: string]: object; @@ -707,48 +708,67 @@ const ResourceViewContainer: FC<{ {!!error ? ( - - - {error.message} - - {error.rejections && ( - - - <> -
    - {error.rejections.map((el, ix) => ( -
  • {el.reason}
  • - ))} -
- -

- For further information please refer to the API - documentation,{' '} - - https://bluebrainnexus.io/docs/delta/api - -

- -
-
- )} - - } - /> + <> + + + {error.message} + + {error.rejections && ( + + + <> +
    + {error.rejections.map((el, ix) => ( +
  • {el.reason}
  • + ))} +
+ +

+ For further information please refer to the API + documentation,{' '} + + https://bluebrainnexus.io/docs/delta/api + +

+ +
+
+ )} + + } + /> +
+ +
+ ) : ( <> {resource && ( diff --git a/src/subapps/studioLegacy/containers/StudioContainer.tsx b/src/subapps/studioLegacy/containers/StudioContainer.tsx index f886c408c..e1a43549f 100644 --- a/src/subapps/studioLegacy/containers/StudioContainer.tsx +++ b/src/subapps/studioLegacy/containers/StudioContainer.tsx @@ -2,9 +2,8 @@ import * as React from 'react'; import { Resource } from '@bbp/nexus-sdk'; import { useNexusContext, AccessControl } from '@bbp/react-nexus'; import { Empty, message } from 'antd'; -import { omitBy } from 'lodash'; import { useHistory } from 'react-router'; - +import { omitBy } from 'lodash'; import EditStudio from '../components/EditStudio'; import StudioHeader from '../components/StudioHeader'; import StudioReactContext from '../contexts/StudioContext';