Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(website): Modify data use terms for individual sequences #1580

Merged
merged 12 commits into from
Apr 15, 2024
68 changes: 68 additions & 0 deletions website/src/components/DataUseTerms/DataUseTermsSelector.tsx
Original file line number Diff line number Diff line change
@@ -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<DataUseTermsSelectorProps> = ({ dataUseTermsType, setDataUseTermsType }) => {
return (
<>
<div>
<input
id='data-use-open'
name='data-use'
onChange={() => 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'
/>
<label htmlFor='data-use-open' className='ml-2 h-4 p-2 text-sm font-medium leading-6 text-gray-900'>
<Unlocked className='h-4 w-4 inline-block mr-2 -mt-1' />
Open
</label>
<div className='text-xs pl-8 text-gray-500 pb-4'>
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.{' '}
<a href={routes.datauseTermsPage()} className='text-primary-600'>
Find out more
</a>
.
</div>
</div>
<div>
<input
id='data-use-restricted'
name='data-use'
onChange={() => setDataUseTermsType(restrictedDataUseTermsType)}
type='radio'
checked={dataUseTermsType === restrictedDataUseTermsType}
className='h-4 w-4 border-gray-300 text-iteal-600 focus:ring-iteal-600 inline-block'
/>
<label
htmlFor='data-use-restricted'
className='ml-2 h-4 p-2 text-sm font-medium leading-6 text-gray-900'
>
<Locked className='h-4 w-4 inline-block mr-2 -mt-1' />
Restricted
</label>
<div className='text-xs pl-8 text-gray-500 mb-4'>
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.{' '}
<a href={routes.datauseTermsPage()} className='text-primary-600'>
Find out more
</a>
.
</div>
</div>
</>
);
};

export default DataUseTermsSelector;
129 changes: 129 additions & 0 deletions website/src/components/DataUseTerms/EditDataUseTermsButton.tsx
bh-ethz marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -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<EditDataUseTermsButtonProps> = ({
accessToken,
clientConfig,
accessionVersion,
dataUseTerms,
}) => {
const restrictedUntil = DateTime.fromISO(dataUseTerms.restrictedUntil);
const [dataUseTermsType, setDataUseTermsType] = useState<DataUseTermsType>(dataUseTerms.type);
const [newRestrictedDate, setNewRestrictedDate] = useState<DateTime>(restrictedUntil);

const dialogRef = useRef<HTMLDialogElement>(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 (
<>
<button className='btn btn-sm' onClick={openDialog}>
Edit Data Use Terms
</button>
<dialog ref={dialogRef} className='modal-box'>
<button
className='btn btn-sm btn-circle btn-ghost text-gray-900 absolute right-2 top-2'
onClick={closeDialog}
>
</button>
<label className='block text-sm font-medium leading-6 text-gray-900'>Edit Data Use Terms</label>
<div className='mt-2'>
<div className='mt-6 space-y-2'>
<div className='flex flex-col items-center gap-x-3'>
<DataUseTermsSelector
dataUseTermsType={dataUseTermsType}
setDataUseTermsType={setDataUseTermsType}
/>
</div>
{dataUseTermsType === restrictedDataUseTermsType && (
<>
<div className='text-sm pl-8 text-gray-900 mb-4 py-2'>
Currently restricted until <b>{restrictedUntil.toFormat('yyyy-MM-dd')}</b>.<br />
New restriction will be set to <b>{newRestrictedDate.toFormat('yyyy-MM-dd')}</b>.
</div>
<Datepicker
className='ml-8'
defaultDate={restrictedUntil.toJSDate()}
showClearButton={false}
showTodayButton={false}
minDate={new Date()}
maxDate={restrictedUntil.toJSDate()}
theme={datePickerTheme}
onSelectedDateChanged={(date) => {
setNewRestrictedDate(DateTime.fromJSDate(date));
}}
inline
/>
</>
)}
</div>
</div>
<div className='flex items-center justify-end my-2'>
<button
className='btn btn-sm'
onClick={() => {
closeDialog();
useSetDataUseTerms.mutate({
accessions: accessionVersion,
newDataUseTerms: {
type: dataUseTermsType,
restrictedUntil: newRestrictedDate.toFormat('yyyy-MM-dd'),
},
});
}}
>
Submit
</button>
</div>
</dialog>
</>
);
};

export const EditDataUseTermsButton = withQueryProvider(InnerEditDataUseTermsButton);
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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();

Expand Down Expand Up @@ -82,6 +88,16 @@ const isMyGroup =
Only visible to group members
</div>

{isRestricted && (
<EditDataUseTermsButton
clientConfig={clientConfig}
accessToken={accessToken}
accessionVersion={[accessionVersion.split('.')[0]]}
dataUseTerms={currentDataUseTerms}
client:load
/>
)}

<RevokeButton
organism={organism}
clientConfig={clientConfig}
Expand Down
61 changes: 5 additions & 56 deletions website/src/components/Submission/DataUploadForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { type FormEvent, useState, useRef, useEffect, useCallback, type ElementT

import { DateChangeModal } from './DateChangeModal';
import { getClientLogger } from '../../clientLogger.ts';
import { routes } from '../../routes/routes.ts';
import DataUseTermsSelector from '../../components/DataUseTerms/DataUseTermsSelector';
import { backendApi } from '../../services/backendApi.ts';
import { backendClientHooks } from '../../services/serviceHooks.ts';
import {
Expand All @@ -19,8 +19,6 @@ import { dateTimeInMonths } from '../../utils/DateTimeInMonths.tsx';
import { createAuthorizationHeader } from '../../utils/createAuthorizationHeader.ts';
import { stringifyMaybeAxiosError } from '../../utils/stringifyMaybeAxiosError.ts';
import { withQueryProvider } from '../common/withQueryProvider.tsx';
import Locked from '~icons/fluent-emoji-high-contrast/locked';
import Unlocked from '~icons/fluent-emoji-high-contrast/unlocked';
import MaterialSymbolsInfoOutline from '~icons/material-symbols/info-outline';
import MaterialSymbolsLightDataTableOutline from '~icons/material-symbols-light/data-table-outline';
import PhDnaLight from '~icons/ph/dna-light';
Expand Down Expand Up @@ -73,59 +71,10 @@ const DataUseTerms = ({
</label>
<div className='mt-2'>
<div className='mt-6 space-y-2'>
<div className='flex items-center gap-x-3'>
<input
id='data-use-open'
name='data-use'
onChange={() => setDataUseTermsType(openDataUseTermsType)}
type='radio'
checked={dataUseTermsType === openDataUseTermsType}
className='h-4 w-4 border-gray-300 text-iteal-600 focus:ring-iteal-600'
/>
<label
htmlFor='data-use-open'
className='block text-sm font-medium leading-6 text-gray-900'
>
<Unlocked className='h-4 w-4 inline-block mr-2 -mt-1' />
Open
</label>
</div>
<div className='text-xs pl-6 text-gray-500 pb-4'>
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.{' '}
<a href={routes.datauseTermsPage()} className='text-primary-600'>
Find out more
</a>
.
</div>

<div className='flex items-center gap-x-3'>
<input
id='data-use-restricted'
name='data-use'
onChange={() => setDataUseTermsType(restrictedDataUseTermsType)}
type='radio'
checked={dataUseTermsType === restrictedDataUseTermsType}
className='h-4 w-4 border-gray-300 text-iteal-600 focus:ring-iteal-600'
/>
<label
htmlFor='data-use-restricted'
className='block text-sm font-medium leading-6 text-gray-900'
>
<Locked className='h-4 w-4 inline-block mr-2 -mt-1' />
Restricted
</label>
</div>

<div className='text-xs pl-6 text-gray-500 mb-4'>
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.{' '}
<a href={routes.datauseTermsPage()} className='text-primary-600'>
Find out more
</a>
.
</div>
<DataUseTermsSelector
dataUseTermsType={dataUseTermsType}
setDataUseTermsType={setDataUseTermsType}
/>
{dataUseTermsType === restrictedDataUseTermsType && (
<div className='text-sm pl-6 text-gray-900 mb-4'>
Data will be restricted until <b>{restrictedUntil.toFormat('yyyy-MM-dd')}</b>.{' '}
Expand Down
24 changes: 24 additions & 0 deletions website/src/services/backendApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
accessionVersion,
accessionVersionsFilterWithApprovalScope,
accessionVersionsFilterWithDeletionScope,
dataUseTerms,
dataUseTermsHistoryEntry,
getSequencesResponse,
problemDetail,
Expand Down Expand Up @@ -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,
Expand All @@ -230,4 +253,5 @@ export const backendApi = makeApi([
extractUnprocessedDataEndpoint,
submitProcessedDataEndpoint,
getDataUseTermsHistoryEndpoint,
setDataUseTerms,
]);
12 changes: 8 additions & 4 deletions website/src/types/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof restrictedDataUseTerms>;

export const dataUseTerms = z.union([
z.object({
type: z.literal(restrictedDataUseTermsType),
restrictedUntil: z.string(),
}),
restrictedDataUseTerms,
z.object({
type: z.literal(openDataUseTermsType),
}),
Expand Down
Loading