From e725672ef0d51ec47355966c4f329a756a1ea55d Mon Sep 17 00:00:00 2001 From: Tobias Kampmann Date: Thu, 8 Feb 2024 17:23:40 +0100 Subject: [PATCH] feat(website): query metadata and errors and warnings via get-data-to-edit * add data use terms to response of get-sequences * add data use terms icon to overview --- .../loculus/backend/api/SubmissionTypes.kt | 2 +- .../controller/DataUseTermsController.kt | 2 +- .../controller/SubmissionController.kt | 2 +- .../DataUseTermsDatabaseService.kt | 2 +- .../submission/SubmissionDatabaseService.kt | 20 +- .../submission/UploadDatabaseService.kt | 15 +- .../src/main/resources/application.properties | 3 + .../datasetcitations/CitationEndpointsTest.kt | 2 + .../submission/RevokeEndpointTest.kt | 2 - .../submission/SubmissionConvenienceClient.kt | 4 +- website/package-lock.json | 34 +- website/package.json | 1 + .../src/components/ReviewPage/ReviewCard.tsx | 308 ++++++++++++++---- .../components/ReviewPage/ReviewPage.spec.tsx | 13 +- .../src/components/ReviewPage/ReviewPage.tsx | 5 +- .../Submission/SubmissionForm.spec.tsx | 15 +- .../UserSequenceList/SequenceTable.spec.tsx | 3 + ...rations.tsx => useSubmissionOperations.ts} | 0 website/src/services/backendApi.ts | 2 +- website/src/types/backend.ts | 22 +- website/tests/e2e.fixture.ts | 6 +- .../pages/user/userSequencePage/index.spec.ts | 6 +- 22 files changed, 341 insertions(+), 128 deletions(-) rename website/src/hooks/{UseSubmissionOperations.tsx => useSubmissionOperations.ts} (100%) diff --git a/backend/src/main/kotlin/org/loculus/backend/api/SubmissionTypes.kt b/backend/src/main/kotlin/org/loculus/backend/api/SubmissionTypes.kt index d0cc06a23f..77a7f72b2d 100644 --- a/backend/src/main/kotlin/org/loculus/backend/api/SubmissionTypes.kt +++ b/backend/src/main/kotlin/org/loculus/backend/api/SubmissionTypes.kt @@ -31,7 +31,6 @@ data class SubmissionIdMapping( override val accession: Accession, override val version: Version, val submissionId: String, - val dataUseTerms: DataUseTerms? = null, ) : AccessionVersionInterface fun List.toPairs() = map { Pair(it.accession, it.version) } @@ -154,6 +153,7 @@ data class SequenceEntryStatus( val group: String, val isRevocation: Boolean = false, val submissionId: String, + val dataUseTerms: DataUseTerms, ) : AccessionVersionInterface data class UnprocessedData( diff --git a/backend/src/main/kotlin/org/loculus/backend/controller/DataUseTermsController.kt b/backend/src/main/kotlin/org/loculus/backend/controller/DataUseTermsController.kt index 96d9696301..ae525163c8 100644 --- a/backend/src/main/kotlin/org/loculus/backend/controller/DataUseTermsController.kt +++ b/backend/src/main/kotlin/org/loculus/backend/controller/DataUseTermsController.kt @@ -44,5 +44,5 @@ class DataUseTermsController( description = "The accession of the sequence entry " + "for which the data use terms should be retrieved", ) @PathVariable accession: Accession, - ) = dataUseTermsDatabaseService.getDataUseTerms(accession) + ) = dataUseTermsDatabaseService.getDataUseTermsHistory(accession) } diff --git a/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionController.kt b/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionController.kt index 8d391fa0e9..c4b21841b9 100644 --- a/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionController.kt +++ b/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionController.kt @@ -274,7 +274,7 @@ class SubmissionController( organism: Organism, @RequestBody body: Accessions, @UsernameFromJwt username: String, - ): List = submissionDatabaseService.revoke(body.accessions, username, organism) + ): List = submissionDatabaseService.revoke(body.accessions, username, organism) @Operation(description = CONFIRM_REVOCATION_DESCRIPTION) @ResponseStatus(HttpStatus.NO_CONTENT) diff --git a/backend/src/main/kotlin/org/loculus/backend/service/datauseterms/DataUseTermsDatabaseService.kt b/backend/src/main/kotlin/org/loculus/backend/service/datauseterms/DataUseTermsDatabaseService.kt index 32e05459e6..1f1b49a3c7 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/datauseterms/DataUseTermsDatabaseService.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/datauseterms/DataUseTermsDatabaseService.kt @@ -44,7 +44,7 @@ class DataUseTermsDatabaseService( } } - fun getDataUseTerms(accession: Accession): List { + fun getDataUseTermsHistory(accession: Accession): List { val accessionDataUseTermsHistory = DataUseTermsTable .select { DataUseTermsTable.accessionColumn eq accession } .sortedBy { it[DataUseTermsTable.changeDateColumn] } diff --git a/backend/src/main/kotlin/org/loculus/backend/service/submission/SubmissionDatabaseService.kt b/backend/src/main/kotlin/org/loculus/backend/service/submission/SubmissionDatabaseService.kt index 57843faf74..c5340c861c 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/submission/SubmissionDatabaseService.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/submission/SubmissionDatabaseService.kt @@ -38,6 +38,7 @@ import org.loculus.backend.api.Status.AWAITING_APPROVAL_FOR_REVOCATION import org.loculus.backend.api.Status.HAS_ERRORS import org.loculus.backend.api.Status.IN_PROCESSING import org.loculus.backend.api.Status.RECEIVED +import org.loculus.backend.api.SubmissionIdMapping import org.loculus.backend.api.SubmittedProcessedData import org.loculus.backend.api.UnprocessedData import org.loculus.backend.controller.BadRequestException @@ -375,7 +376,11 @@ class SubmissionDatabaseService( val listOfStatuses = statusesFilter ?: Status.entries sequenceEntriesTableProvider.get(organism).let { table -> - var query = table + val query = table + .join(DataUseTermsTable, JoinType.LEFT, additionalConstraint = { + (table.accessionColumn eq DataUseTermsTable.accessionColumn) and + (DataUseTermsTable.isNewestDataUseTerms) + }) .slice( table.accessionColumn, table.versionColumn, @@ -385,6 +390,8 @@ class SubmissionDatabaseService( table.groupNameColumn, table.organismColumn, table.submittedAtColumn, + DataUseTermsTable.dataUseTermsTypeColumn, + DataUseTermsTable.restrictedUntilColumn, ) .select( where = { @@ -407,12 +414,16 @@ class SubmissionDatabaseService( row[table.groupNameColumn], row[table.isRevocationColumn], row[table.submissionIdColumn], + dataUseTerms = DataUseTerms.fromParameters( + DataUseTermsType.fromString(row[DataUseTermsTable.dataUseTermsTypeColumn]), + row[DataUseTermsTable.restrictedUntilColumn], + ), ) } } } - fun revoke(accessions: List, username: String, organism: Organism): List { + fun revoke(accessions: List, username: String, organism: Organism): List { log.info { "revoking ${accessions.size} sequences" } accessionPreconditionValidator.validateAccessions( @@ -471,12 +482,9 @@ class SubmissionDatabaseService( table.statusIs(AWAITING_APPROVAL_FOR_REVOCATION) }, ).map { - SequenceEntryStatus( + SubmissionIdMapping( it[table.accessionColumn], it[table.versionColumn], - AWAITING_APPROVAL_FOR_REVOCATION, - it[table.groupNameColumn], - it[table.isRevocationColumn], it[table.submissionIdColumn], ) }.sortedBy { it.accession } diff --git a/backend/src/main/kotlin/org/loculus/backend/service/submission/UploadDatabaseService.kt b/backend/src/main/kotlin/org/loculus/backend/service/submission/UploadDatabaseService.kt index 1411c5cb3b..2294cc7580 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/submission/UploadDatabaseService.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/submission/UploadDatabaseService.kt @@ -138,26 +138,15 @@ class UploadDatabaseService( result.toList() } ?: emptyList() - val result = if (submissionParams is SubmissionParams.OriginalSubmissionParams) { + if (submissionParams is SubmissionParams.OriginalSubmissionParams) { dataUseTermsDatabaseService.setNewDataUseTerms( submissionParams.username, insertionResult.map { it.accession }, submissionParams.dataUseTerms, ) - - insertionResult.map { - SubmissionIdMapping( - it.accession, - it.version, - it.submissionId, - submissionParams.dataUseTerms, - ) - } - } else { - insertionResult } - return@transaction result + return@transaction insertionResult } fun deleteUploadData(uploadId: String) { diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 9fa18d8902..722d6df0b5 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -9,6 +9,9 @@ server.forward-headers-strategy=framework spring.servlet.multipart.max-file-size=5000MB spring.servlet.multipart.max-request-size=5000MB + +server.compression.enabled=true + spring.datasource.driverClassName=org.postgresql.Driver springdoc.show-actuator=true diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/datasetcitations/CitationEndpointsTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/datasetcitations/CitationEndpointsTest.kt index cda6244367..bd974a23f8 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/datasetcitations/CitationEndpointsTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/datasetcitations/CitationEndpointsTest.kt @@ -6,6 +6,7 @@ import io.mockk.every import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource +import org.loculus.backend.api.DataUseTerms import org.loculus.backend.api.SequenceEntryStatus import org.loculus.backend.api.Status import org.loculus.backend.controller.EndpointTest @@ -68,6 +69,7 @@ class CitationEndpointsTest( "mock-group", false, "mock-submission-id", + DataUseTerms.Open, ), ) diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/RevokeEndpointTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/RevokeEndpointTest.kt index 2fcfe9bcce..60d691b9a1 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/RevokeEndpointTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/RevokeEndpointTest.kt @@ -44,8 +44,6 @@ class RevokeEndpointTest( .andExpect(jsonPath("\$.length()").value(DefaultFiles.NUMBER_OF_SEQUENCES)) .andExpect(jsonPath("\$[0].accession").value(firstAccession)) .andExpect(jsonPath("\$[0].version").value(2)) - .andExpect(jsonPath("\$[0].status").value("AWAITING_APPROVAL_FOR_REVOCATION")) - .andExpect(jsonPath("\$[0].isRevocation").value(true)) convenienceClient.getSequenceEntryOfUser(accession = firstAccession, version = 2) .assertStatusIs(AWAITING_APPROVAL_FOR_REVOCATION) diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmissionConvenienceClient.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmissionConvenienceClient.kt index ff49434066..7dfa6e66b2 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmissionConvenienceClient.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmissionConvenienceClient.kt @@ -136,7 +136,7 @@ class SubmissionConvenienceClient( fun prepareDefaultSequenceEntriesToAwaitingApprovalForRevocation( organism: String = DEFAULT_ORGANISM, - ): List { + ): List { val accessionVersions = prepareDefaultSequenceEntriesToApprovedForRelease(organism = organism) return revokeSequenceEntries(accessionVersions.map { it.accession }, organism = organism) } @@ -247,7 +247,7 @@ class SubmissionConvenienceClient( fun revokeSequenceEntries( listOfSequencesToRevoke: List, organism: String = DEFAULT_ORGANISM, - ): List = + ): List = deserializeJsonResponse(client.revokeSequenceEntries(listOfSequencesToRevoke, organism = organism)) fun confirmRevocation( diff --git a/website/package-lock.json b/website/package-lock.json index 4dd164fab2..f7ffebb14e 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -32,6 +32,7 @@ "react-chartjs-2": "^5.2.0", "react-confirm-alert": "^3.0.6", "react-dom": "^18.2.0", + "react-tooltip": "^5.26.2", "unplugin-icons": "^0.18.2", "winston": "^3.11.0", "zod": "^3.22.4" @@ -1150,14 +1151,19 @@ } }, "node_modules/@floating-ui/dom": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", - "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", "dependencies": { - "@floating-ui/core": "^1.4.2", - "@floating-ui/utils": "^0.1.3" + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" } }, + "node_modules/@floating-ui/dom/node_modules/@floating-ui/utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + }, "node_modules/@floating-ui/react-dom": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz", @@ -4241,6 +4247,11 @@ "node": ">=8" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "node_modules/cli-boxes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", @@ -10611,6 +10622,19 @@ "node": ">=0.10.0" } }, + "node_modules/react-tooltip": { + "version": "5.26.2", + "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.26.2.tgz", + "integrity": "sha512-C1qHiqWYn6l5c98kL/NKFyJSw5G11vUVJkgOPcKgn306c5iL5317LxMNn5Qg1GSSM7Qvtsd6KA5MvwfgxFF7Dg==", + "dependencies": { + "@floating-ui/dom": "^1.6.1", + "classnames": "^2.3.0" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", diff --git a/website/package.json b/website/package.json index d1b2f97a7e..259f7ba3b8 100644 --- a/website/package.json +++ b/website/package.json @@ -41,6 +41,7 @@ "react-chartjs-2": "^5.2.0", "react-confirm-alert": "^3.0.6", "react-dom": "^18.2.0", + "react-tooltip": "^5.26.2", "unplugin-icons": "^0.18.2", "winston": "^3.11.0", "zod": "^3.22.4" diff --git a/website/src/components/ReviewPage/ReviewCard.tsx b/website/src/components/ReviewPage/ReviewCard.tsx index dfeedcd89d..df20ee6562 100644 --- a/website/src/components/ReviewPage/ReviewCard.tsx +++ b/website/src/components/ReviewPage/ReviewCard.tsx @@ -1,18 +1,28 @@ import type { FC } from 'react'; +import { Tooltip } from 'react-tooltip'; +import { backendClientHooks } from '../../services/serviceHooks.ts'; import { awaitingApprovalStatus, + type DataUseTerms, hasErrorsStatus, inProcessingStatus, + type ProcessingAnnotation, receivedStatus, + restrictedDataUseTermsType, type SequenceEntryStatus, type SequenceEntryStatusNames, + type SequenceEntryToEdit, } from '../../types/backend.ts'; +import type { ClientConfig } from '../../types/runtimeConfig.ts'; +import { createAuthorizationHeader } from '../../utils/createAuthorizationHeader.ts'; import Edit from '~icons/bxs/edit'; import Trash from '~icons/bxs/trash'; import Send from '~icons/fa/send'; import Note from '~icons/fluent/note-24-filled'; import QuestionMark from '~icons/fluent/tag-question-mark-24-filled'; +import Locked from '~icons/fluent-emoji-high-contrast/locked'; +import Unlocked from '~icons/fluent-emoji-high-contrast/unlocked'; import EmptyCircle from '~icons/grommet-icons/empty-circle'; import TickOutline from '~icons/mdi/tick-outline'; @@ -21,6 +31,9 @@ type ReviewCardProps = { deleteAccessionVersion: () => void; approveAccessionVersion: () => void; editAccessionVersion: () => void; + clientConfig: ClientConfig; + organism: string; + accessToken: string; }; export const ReviewCard: FC = ({ @@ -28,81 +41,231 @@ export const ReviewCard: FC = ({ approveAccessionVersion, deleteAccessionVersion, editAccessionVersion, + clientConfig, + organism, + accessToken, }) => { + const { isLoading, data } = useGetMetadataAndAnnotations(organism, clientConfig, accessToken, sequenceEntryStatus); + + const ButtonBar = ( +
+ + + + + + + + +
+ ); + return (
-
- - - -
+ {ButtonBar}
- + 0} + /> + {data !== undefined && ( + <> + + + + + )} +
+
+ ); +}; + +type MetadataListProps = { + data: SequenceEntryToEdit; + isLoading: boolean; +}; + +const isAnnotationPresent = (metadataField: string) => (item: ProcessingAnnotation) => + item.source[0].name === metadataField; + +const MetadataList: FC = ({ data, isLoading }) => ( +
+ {!isLoading && + Object.entries(data.processedData.metadata).map((entry, index) => { + const valueString = entry[1].toString(); + return ( + + ); + })} +
+); + +type ErrorsProps = { + errors: ProcessingAnnotation[]; + accession: string; +}; + +const Errors: FC = ({ errors, accession }) => { + return ( +
+
+ {errors.map((error) => { + const uniqueKey = error.source.map((source) => source.type + source.name).join('.'); + return ( +

+ {error.message} + +

+ ); + })}
); }; -const StatusIcon: FC<{ status: SequenceEntryStatusNames }> = ({ status }) => { +type WarningsProps = { + warnings: ProcessingAnnotation[]; + accession: string; +}; + +const Warnings: FC = ({ warnings, accession }) => { + return ( +
+
+ {warnings.map((warning) => ( +

source.type + source.name).join('.') + accession} + className='text-yellow-500' + > + {warning.message} +

+ ))} +
+
+ ); +}; + +type DataUseTermsIconProps = { + dataUseTerms: DataUseTerms; + accession: string; +}; +const DataUseTermsIcon: FC = ({ dataUseTerms, accession }) => { + const hintText = + dataUseTerms.type === restrictedDataUseTermsType + ? `Data use restricted until ${dataUseTerms.restrictedUntil}` + : `Data open to use`; + + return ( + <> +
+ {dataUseTerms.type === restrictedDataUseTermsType ? : } +
+ + + ); +}; + +type StatusIconProps = { + status: SequenceEntryStatusNames; + dataUseTerms: DataUseTerms; + accession: string; + hasWarnings?: boolean; +}; + +const StatusIcon: FC = ({ status, dataUseTerms, accession, hasWarnings }) => { if (status === receivedStatus) { return ( -
- +
+
+ +
+ +
); } if (status === hasErrorsStatus) { return ( -
- +
+
+ +
+ +
); } if (status === inProcessingStatus) { return ( -
- +
+
+ +
+ +
); } if (status === awaitingApprovalStatus) { return ( // TODO(#702): When queries are implemented, this should be a yellow tick with a warning note if there are warnings -
- +
+
+ +
+ +
); } @@ -113,42 +276,55 @@ type KeyValueComponentProps = { value: string; extraStyle?: string; keyStyle?: string; - warningNote?: boolean; - errorNote?: boolean; + warnings?: ProcessingAnnotation[]; + errors?: ProcessingAnnotation[]; }; -const KeyValueComponent: FC = ({ - keyName, - value, - extraStyle, - keyStyle, - warningNote, - errorNote, -}) => { +const KeyValueComponent: FC = ({ keyName, value, extraStyle, keyStyle, warnings, errors }) => { return (
{keyName} {value} - {warningNote === true && ( - - 0 && ( + <> + + + + annotation.message).join(', ')} /> - + )} - {errorNote === true && ( - - 0 && ( + <> + + + + annotation.message).join(', ')} /> - + )}
); }; + +function useGetMetadataAndAnnotations( + organism: string, + clientConfig: ClientConfig, + accessToken: string, + sequenceEntryStatus: SequenceEntryStatus, +) { + const { status, accession, version } = sequenceEntryStatus; + return backendClientHooks(clientConfig).useGetDataToEdit( + { + headers: createAuthorizationHeader(accessToken), + params: { organism, accession, version }, + }, + { enabled: status !== receivedStatus && status !== inProcessingStatus }, + ); +} diff --git a/website/src/components/ReviewPage/ReviewPage.spec.tsx b/website/src/components/ReviewPage/ReviewPage.spec.tsx index 50d5c5e401..807daeca76 100644 --- a/website/src/components/ReviewPage/ReviewPage.spec.tsx +++ b/website/src/components/ReviewPage/ReviewPage.spec.tsx @@ -2,6 +2,7 @@ import { render, waitFor } from '@testing-library/react'; import { describe, expect, test } from 'vitest'; import { ReviewPage } from './ReviewPage.tsx'; +import { openDataUseTerms } from '../../../tests/e2e.fixture.ts'; import { mockRequest, testAccessToken, testConfig, testOrganism } from '../../../vitest.setup.ts'; import { awaitingApprovalStatus, @@ -23,6 +24,7 @@ const receivedTestData: SequenceEntryStatus = { accession: 'accession1', version: 1, isRevocation: false, + dataUseTerms: openDataUseTerms, }; const processingTestData: SequenceEntryStatus = { @@ -31,14 +33,16 @@ const processingTestData: SequenceEntryStatus = { accession: 'accession4', version: 1, isRevocation: false, + dataUseTerms: openDataUseTerms, }; -const erroneosTestData: SequenceEntryStatus = { +const erroneousTestData: SequenceEntryStatus = { submissionId: 'custom2', status: hasErrorsStatus, accession: 'accession2', version: 1, isRevocation: false, + dataUseTerms: openDataUseTerms, }; const awaitingApprovalTestData: SequenceEntryStatus = { @@ -47,6 +51,7 @@ const awaitingApprovalTestData: SequenceEntryStatus = { accession: 'accession3', version: 1, isRevocation: false, + dataUseTerms: openDataUseTerms, }; describe('ReviewPage', () => { @@ -72,14 +77,14 @@ describe('ReviewPage', () => { }); test('should render the review page and show button to bulk delete/approve all erroneous sequences', async () => { - mockRequest.backend.getSequences(200, [erroneosTestData, awaitingApprovalTestData]); + mockRequest.backend.getSequences(200, [erroneousTestData, awaitingApprovalTestData]); mockRequest.backend.approveSequences(); mockRequest.backend.deleteSequences(); const { getByText } = renderReviewPage(); await waitFor(() => { - expect(getByText(erroneosTestData.accession)).toBeDefined(); + expect(getByText(erroneousTestData.accession)).toBeDefined(); expect(getByText(awaitingApprovalTestData.accession)).toBeDefined(); }); @@ -102,7 +107,7 @@ describe('ReviewPage', () => { mockRequest.backend.getSequences(200, [ receivedTestData, processingTestData, - erroneosTestData, + erroneousTestData, awaitingApprovalTestData, ]); diff --git a/website/src/components/ReviewPage/ReviewPage.tsx b/website/src/components/ReviewPage/ReviewPage.tsx index 709e5ffe9b..fff90f54d8 100644 --- a/website/src/components/ReviewPage/ReviewPage.tsx +++ b/website/src/components/ReviewPage/ReviewPage.tsx @@ -1,7 +1,7 @@ import { type FC, useState } from 'react'; import { ReviewCard } from './ReviewCard.tsx'; -import { useSubmissionOperations } from '../../hooks/UseSubmissionOperations.tsx'; +import { useSubmissionOperations } from '../../hooks/useSubmissionOperations.ts'; import { routes } from '../../routes.ts'; import { awaitingApprovalStatus, @@ -110,6 +110,9 @@ const InnerReviewPage: FC = ({ clientConfig, organism, accessTo editAccessionVersion={() => { window.location.href = routes.editPage(organism, sequence); }} + clientConfig={clientConfig} + organism={organism} + accessToken={accessToken} />
); diff --git a/website/src/components/Submission/SubmissionForm.spec.tsx b/website/src/components/Submission/SubmissionForm.spec.tsx index 276c431983..bb63aafaa2 100644 --- a/website/src/components/Submission/SubmissionForm.spec.tsx +++ b/website/src/components/Submission/SubmissionForm.spec.tsx @@ -4,12 +4,7 @@ import { describe, expect, test, vi } from 'vitest'; import { SubmissionForm } from './SubmissionForm'; import { mockRequest, testAccessToken, testConfig, testOrganism } from '../../../vitest.setup.ts'; -import { - type DataUseTerms, - openDataUseTermsType, - type ProblemDetail, - type SubmissionIdMapping, -} from '../../types/backend.ts'; +import { type ProblemDetail, type SubmissionIdMapping } from '../../types/backend.ts'; vi.mock('../../api', () => ({ getClientLogger: () => ({ @@ -28,13 +23,9 @@ function renderSubmissionForm() { const metadataFile = new File(['content'], 'metadata.tsv', { type: 'text/plain' }); const sequencesFile = new File(['content'], 'sequences.fasta', { type: 'text/plain' }); -const defaultDataUseTerms: DataUseTerms = { - type: openDataUseTermsType, -}; - const testResponse: SubmissionIdMapping[] = [ - { accession: '0', version: 1, submissionId: 'header0', dataUseTerms: defaultDataUseTerms }, - { accession: '1', version: 1, submissionId: 'header1', dataUseTerms: defaultDataUseTerms }, + { accession: '0', version: 1, submissionId: 'header0' }, + { accession: '1', version: 1, submissionId: 'header1' }, ]; describe('SubmitForm', () => { diff --git a/website/src/components/UserSequenceList/SequenceTable.spec.tsx b/website/src/components/UserSequenceList/SequenceTable.spec.tsx index cd9562d085..db72ca200b 100644 --- a/website/src/components/UserSequenceList/SequenceTable.spec.tsx +++ b/website/src/components/UserSequenceList/SequenceTable.spec.tsx @@ -6,6 +6,7 @@ import { beforeEach, describe, expect, test } from 'vitest'; import { SequenceEntryTable } from './SequenceEntryTable.tsx'; import type { BulkSequenceActionName, SingleSequenceActionName } from './sequenceActions.ts'; +import { openDataUseTerms } from '../../../tests/e2e.fixture.ts'; import { testAccessToken, testOrganism } from '../../../vitest.setup.ts'; import { routes } from '../../routes.ts'; import type { SequenceEntryStatus } from '../../types/backend.ts'; @@ -20,6 +21,7 @@ const defaultSequenceEntryStatuses: readonly SequenceEntryStatus[] = [ status: 'HAS_ERRORS', isRevocation: false, submissionId: 'custom1', + dataUseTerms: openDataUseTerms, }, { accession: '2', @@ -27,6 +29,7 @@ const defaultSequenceEntryStatuses: readonly SequenceEntryStatus[] = [ status: 'HAS_ERRORS', isRevocation: false, submissionId: 'custom2', + dataUseTerms: openDataUseTerms, }, ]; diff --git a/website/src/hooks/UseSubmissionOperations.tsx b/website/src/hooks/useSubmissionOperations.ts similarity index 100% rename from website/src/hooks/UseSubmissionOperations.tsx rename to website/src/hooks/useSubmissionOperations.ts diff --git a/website/src/services/backendApi.ts b/website/src/services/backendApi.ts index 40ee809859..d7a305a516 100644 --- a/website/src/services/backendApi.ts +++ b/website/src/services/backendApi.ts @@ -80,7 +80,7 @@ const revokeSequencesEndpoint = makeEndpoint({ schema: accessions, }, ], - response: z.array(sequenceEntryStatus), + response: z.array(submissionIdMapping), errors: [{ status: 'default', schema: problemDetail }, { status: 422, schema: problemDetail }, notAuthorizedError], }); diff --git a/website/src/types/backend.ts b/website/src/types/backend.ts index 3cb9a0e9be..3f5a5dcbcf 100644 --- a/website/src/types/backend.ts +++ b/website/src/types/backend.ts @@ -30,6 +30,7 @@ const processingAnnotation = z.object({ ), message: z.string(), }); +export type ProcessingAnnotation = z.infer; export const metadataField = z.union([z.string(), z.number(), z.date()]); export type MetadataField = z.infer; @@ -54,15 +55,6 @@ export const accessionVersionsObject = z.object({ accessionVersions: z.array(accessionVersion), }); -export const sequenceEntryStatus = accessionVersion.merge( - z.object({ - status: sequenceEntryStatusNames, - submissionId: z.string(), - isRevocation: z.boolean(), - }), -); -export type SequenceEntryStatus = z.infer; - export const openDataUseTermsType = 'OPEN'; export const restrictedDataUseTermsType = 'RESTRICTED'; @@ -91,10 +83,20 @@ export const dataUseTermsHistoryEntry = z.object({ export type DataUseTermsHistoryEntry = z.infer; +export const sequenceEntryStatus = accessionVersion.merge( + z.object({ + status: sequenceEntryStatusNames, + submissionId: z.string(), + isRevocation: z.boolean(), + dataUseTerms, + }), +); + +export type SequenceEntryStatus = z.infer; + export const submissionIdMapping = accessionVersion.merge( z.object({ submissionId: z.string(), - dataUseTerms: dataUseTerms.nullable(), }), ); export type SubmissionIdMapping = z.infer; diff --git a/website/tests/e2e.fixture.ts b/website/tests/e2e.fixture.ts index 24e9b269a0..258ab97546 100644 --- a/website/tests/e2e.fixture.ts +++ b/website/tests/e2e.fixture.ts @@ -5,18 +5,19 @@ import { ResultAsync } from 'neverthrow'; import { Issuer } from 'openid-client'; import winston from 'winston'; +import { DatasetPage } from './pages/datasets/dataset.page'; import { EditPage } from './pages/edit/edit.page'; import { NavigationFixture } from './pages/navigation.fixture'; import { RevisePage } from './pages/revise/revise.page'; import { SearchPage } from './pages/search/search.page'; import { SequencePage } from './pages/sequences/sequences.page'; import { SubmitPage } from './pages/submit/submit.page'; -import { DatasetPage } from './pages/datasets/dataset.page'; import { GroupPage } from './pages/user/group/group.page.ts'; import { UserSequencePage } from './pages/user/userSequencePage/userSequencePage.ts'; import { ACCESS_TOKEN_COOKIE, clientMetadata, realmPath, REFRESH_TOKEN_COOKIE } from '../src/middleware/authMiddleware'; import { BackendClient } from '../src/services/backendClient'; import { GroupManagementClient } from '../src/services/groupManagementClient.ts'; +import { type DataUseTerms, openDataUseTermsType } from '../src/types/backend.ts'; type E2EFixture = { searchPage: SearchPage; @@ -32,6 +33,9 @@ type E2EFixture = { }; export const dummyOrganism = { key: 'dummy-organism', displayName: 'Test Dummy Organism' }; +export const openDataUseTerms: DataUseTerms = { + type: openDataUseTermsType, +}; export const baseUrl = 'http://localhost:3000'; export const backendUrl = 'http://localhost:8079'; diff --git a/website/tests/pages/user/userSequencePage/index.spec.ts b/website/tests/pages/user/userSequencePage/index.spec.ts index 58c43b1f3b..fd124d8fae 100644 --- a/website/tests/pages/user/userSequencePage/index.spec.ts +++ b/website/tests/pages/user/userSequencePage/index.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from '../../../e2e.fixture'; +import { expect, openDataUseTerms, test } from '../../../e2e.fixture'; import { submitRevisedDataViaApi } from '../../../util/backendCalls.ts'; import { prepareDataToBe } from '../../../util/prepareDataToBe.ts'; @@ -23,24 +23,28 @@ test.describe('The user sequence page', () => { status: 'HAS_ERRORS', isRevocation: false, submissionId: 'custom1', + dataUseTerms: openDataUseTerms, }, { ...sequenceEntryAwaitingApproval, status: 'AWAITING_APPROVAL', isRevocation: false, submissionId: 'custom1', + dataUseTerms: openDataUseTerms, }, { ...sequenceEntryReleasable, status: 'APPROVED_FOR_RELEASE', isRevocation: false, submissionId: 'custom1', + dataUseTerms: openDataUseTerms, }, { ...sequenceEntryToBeRevised, status: 'APPROVED_FOR_RELEASE', isRevocation: false, submissionId: 'custom1', + dataUseTerms: openDataUseTerms, }, ]);