diff --git a/website/src/components/DataUseTerms/DataUseTermsSelector.tsx b/website/src/components/DataUseTerms/DataUseTermsSelector.tsx new file mode 100644 index 0000000000..a0c689123a --- /dev/null +++ b/website/src/components/DataUseTerms/DataUseTermsSelector.tsx @@ -0,0 +1,68 @@ +import { type FC } from 'react'; + +import { routes } from '../../routes/routes.ts'; +import { type DataUseTermsType, openDataUseTermsType, restrictedDataUseTermsType } from '../../types/backend.ts'; +import Locked from '~icons/fluent-emoji-high-contrast/locked'; +import Unlocked from '~icons/fluent-emoji-high-contrast/unlocked'; + +type DataUseTermsSelectorProps = { + dataUseTermsType: DataUseTermsType; + setDataUseTermsType: (dataUseTermsType: DataUseTermsType) => void; +}; + +const DataUseTermsSelector: FC = ({ dataUseTermsType, setDataUseTermsType }) => { + return ( + <> +
+ setDataUseTermsType(openDataUseTermsType)} + type='radio' + checked={dataUseTermsType === openDataUseTermsType} + className='h-4 w-4 p-2 border-gray-300 text-iteal-600 focus:ring-iteal-600 inline-block' + /> + +
+ Anyone can use and share the data (though we believe researchers should exercise scientific + etiquette, including the importance of citation). Data will be released to the INSDC databases + shortly after submission.{' '} + + Find out more + + . +
+
+
+ setDataUseTermsType(restrictedDataUseTermsType)} + type='radio' + checked={dataUseTermsType === restrictedDataUseTermsType} + className='h-4 w-4 border-gray-300 text-iteal-600 focus:ring-iteal-600 inline-block' + /> + +
+ Data will be restricted for a period of time. The sequences will be available but there will be + limitations on how they can be used by others.{' '} + + Find out more + + . +
+
+ + ); +}; + +export default DataUseTermsSelector; diff --git a/website/src/components/DataUseTerms/EditDataUseTermsButton.tsx b/website/src/components/DataUseTerms/EditDataUseTermsButton.tsx new file mode 100644 index 0000000000..051798a2fe --- /dev/null +++ b/website/src/components/DataUseTerms/EditDataUseTermsButton.tsx @@ -0,0 +1,129 @@ +import { Datepicker } from 'flowbite-react'; +import { DateTime } from 'luxon'; +import { type FC, useState, useRef } from 'react'; +import { toast } from 'react-toastify'; + +import { withQueryProvider } from './../common/withQueryProvider'; +import DataUseTermsSelector from './DataUseTermsSelector'; +import { backendClientHooks } from '../../services/serviceHooks'; +import { type RestrictedDataUseTerms, type DataUseTermsType, restrictedDataUseTermsType } from '../../types/backend.ts'; +import type { ClientConfig } from '../../types/runtimeConfig'; +import { createAuthorizationHeader } from '../../utils/createAuthorizationHeader'; +import { stringifyMaybeAxiosError } from '../../utils/stringifyMaybeAxiosError'; +import { datePickerTheme } from '../Submission/DateChangeModal'; + +type EditDataUseTermsButtonProps = { + accessToken: string; + clientConfig: ClientConfig; + accessionVersion: string[]; + dataUseTerms: RestrictedDataUseTerms; +}; + +const InnerEditDataUseTermsButton: FC = ({ + accessToken, + clientConfig, + accessionVersion, + dataUseTerms, +}) => { + const restrictedUntil = DateTime.fromISO(dataUseTerms.restrictedUntil); + const [dataUseTermsType, setDataUseTermsType] = useState(dataUseTerms.type); + const [newRestrictedDate, setNewRestrictedDate] = useState(restrictedUntil); + + const dialogRef = useRef(null); + + const openDialog = () => { + if (dialogRef.current) { + dialogRef.current.showModal(); + } + }; + + const closeDialog = () => { + if (dialogRef.current) { + dialogRef.current.close(); + } + }; + + const hooks = backendClientHooks(clientConfig); + const useSetDataUseTerms = hooks.useSetDataUseTerms( + { headers: createAuthorizationHeader(accessToken) }, + { + onError: (error) => + toast.error('Failed to edit terms of use: ' + stringifyMaybeAxiosError(error), { + position: 'top-center', + autoClose: false, + }), + onSuccess: () => + toast.success('The use terms for this sequence will be updated within a few minutes.', { + position: 'top-center', + autoClose: false, + }), + }, + ); + + return ( + <> + + + + +
+
+
+ +
+ {dataUseTermsType === restrictedDataUseTermsType && ( + <> +
+ Currently restricted until {restrictedUntil.toFormat('yyyy-MM-dd')}.
+ New restriction will be set to {newRestrictedDate.toFormat('yyyy-MM-dd')}. +
+ { + setNewRestrictedDate(DateTime.fromJSDate(date)); + }} + inline + /> + + )} +
+
+
+ +
+
+ + ); +}; + +export const EditDataUseTermsButton = withQueryProvider(InnerEditDataUseTermsButton); diff --git a/website/src/components/SequenceDetailsPage/SequencesDetailsPage.astro b/website/src/components/SequenceDetailsPage/SequencesDetailsPage.astro index c5dc07f7d3..e6574f0cda 100644 --- a/website/src/components/SequenceDetailsPage/SequencesDetailsPage.astro +++ b/website/src/components/SequenceDetailsPage/SequencesDetailsPage.astro @@ -8,6 +8,7 @@ import { routes } from '../../routes/routes.ts'; import { GroupManagementClient } from '../../services/groupManagementClient'; import { type DataUseTermsHistoryEntry } from '../../types/backend'; import { getAccessToken } from '../../utils/getAccessToken'; +import { EditDataUseTermsButton } from '../DataUseTerms/EditDataUseTermsButton'; import ErrorBox from '../common/ErrorBox.astro'; import MdiEye from '~icons/mdi/eye'; @@ -19,8 +20,13 @@ interface Props { } const { tableData, organism, accessionVersion, dataUseTermsHistory } = Astro.props; + const groupId = tableData.find((entry) => entry.name === 'groupId')!.value as number; -const isRestricted = tableData.find((entry) => entry.name === 'dataUseTerms')?.value === 'RESTRICTED'; + +dataUseTermsHistory.sort((a, b) => (a.changeDate > b.changeDate ? -1 : 1)); +const currentDataUseTerms = dataUseTermsHistory[0].dataUseTerms; + +const isRestricted = currentDataUseTerms.type === 'RESTRICTED'; const runtimeConfig = getRuntimeConfig(); @@ -82,6 +88,16 @@ const isMyGroup = Only visible to group members + {isRestricted && ( + + )} +
-
- setDataUseTermsType(openDataUseTermsType)} - type='radio' - checked={dataUseTermsType === openDataUseTermsType} - className='h-4 w-4 border-gray-300 text-iteal-600 focus:ring-iteal-600' - /> - -
-
- Anyone can use and share the data (though we believe researchers should exercise - scientific etiquette, including the importance of citation). Data will be released to - the INSDC databases shortly after submission.{' '} - - Find out more - - . -
- -
- setDataUseTermsType(restrictedDataUseTermsType)} - type='radio' - checked={dataUseTermsType === restrictedDataUseTermsType} - className='h-4 w-4 border-gray-300 text-iteal-600 focus:ring-iteal-600' - /> - -
- -
- Data will be restricted for a period of time. The sequences will be available but there - will be limitations on how they can be used by others.{' '} - - Find out more - - . -
+ {dataUseTermsType === restrictedDataUseTermsType && (
Data will be restricted until {restrictedUntil.toFormat('yyyy-MM-dd')}.{' '} diff --git a/website/src/services/backendApi.ts b/website/src/services/backendApi.ts index 2e0bee89ee..d645fb8590 100644 --- a/website/src/services/backendApi.ts +++ b/website/src/services/backendApi.ts @@ -7,6 +7,7 @@ import { accessionVersion, accessionVersionsFilterWithApprovalScope, accessionVersionsFilterWithDeletionScope, + dataUseTerms, dataUseTermsHistoryEntry, getSequencesResponse, problemDetail, @@ -218,6 +219,28 @@ const getDataUseTermsHistoryEndpoint = makeEndpoint({ ], }); +const setDataUseTerms = makeEndpoint({ + method: 'put', + path: '/data-use-terms', + alias: 'setDataUseTerms', + parameters: [ + authorizationHeader, + { + name: 'data', + type: 'Body', + schema: z.object({ + accessions: z.array(z.string()), + newDataUseTerms: dataUseTerms, + }), + }, + ], + response: z.never(), + errors: [ + { status: 'default', schema: problemDetail }, + { status: 404, schema: problemDetail }, + ], +}); + export const backendApi = makeApi([ submitEndpoint, reviseEndpoint, @@ -230,4 +253,5 @@ export const backendApi = makeApi([ extractUnprocessedDataEndpoint, submitProcessedDataEndpoint, getDataUseTermsHistoryEndpoint, + setDataUseTerms, ]); diff --git a/website/src/types/backend.ts b/website/src/types/backend.ts index 51c6738c02..7943fab3c5 100644 --- a/website/src/types/backend.ts +++ b/website/src/types/backend.ts @@ -88,11 +88,15 @@ export const dataUseTermsTypes = [restrictedDataUseTermsType, openDataUseTermsTy export type DataUseTermsType = typeof openDataUseTermsType | typeof restrictedDataUseTermsType; +export const restrictedDataUseTerms = z.object({ + type: z.literal(restrictedDataUseTermsType), + restrictedUntil: z.string(), +}); + +export type RestrictedDataUseTerms = z.infer; + export const dataUseTerms = z.union([ - z.object({ - type: z.literal(restrictedDataUseTermsType), - restrictedUntil: z.string(), - }), + restrictedDataUseTerms, z.object({ type: z.literal(openDataUseTermsType), }),