Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions packages/client/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"redo": "Redo",
"dataset": "Dataset",
"status": "Status",
"dateFormat": "{{date, datetime}}"
"dateFormat": "{{date, datetime}}",
"download": "Download"
},
"languages": {
"en": "English",
Expand All @@ -55,7 +56,8 @@
"contribute": "Contribute",
"tagInStudy": "Tag in Study",
"logout": "Logout",
"datasetDownloads": "Dataset Downloads"
"datasetDownloads": "Dataset Downloads",
"studyDownloads": "Study Downloads"
},
"components": {
"environment": {
Expand Down Expand Up @@ -143,6 +145,14 @@
"tagView": {
"originalEntry": "Original Entry",
"export": "Export"
},
"studyDownload": {
"csv": "Tag CSV",
"taggedEntries": "Entries Tagged",
"downloadStartedSuccess": "Download has started, the download will be available under the study download page",
"downloadFailed": "Could not download study data, please reach out to your administrator",
"downloadTitle": "Study Download Request",
"downloadDescription": "Would you like to download this study? The tag data, any recorded videos, and the original entries will be download, this may take a while, downloads will appear in the study download page when complete"
}
},
"errors": {
Expand Down
2 changes: 2 additions & 0 deletions packages/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { PermissionProvider } from './context/Permission.context';
import { TagTrainingView } from './pages/studies/TagTrainingView';
import { SnackbarProvider } from './context/Snackbar.context';
import { DatasetDownloads } from './pages/datasets/DatasetDownloads';
import { StudyDownloads } from './pages/studies/StudyDownloads';

const drawerWidth = 256;
const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })<{
Expand Down Expand Up @@ -135,6 +136,7 @@ const MyRoutes: FC = () => {
<Route path={'/study/entries'} element={<EntryControls />} />
<Route path={'/study/tags'} element={<TagView />} />
<Route path={'/study/training'} element={<TagTrainingView />} />
<Route path={'/study/downloads'} element={<StudyDownloads />} />
<Route path={'/successpage'} element={<SuccessPage />} />
<Route path={'/dataset/controls'} element={<DatasetControls />} />
<Route path={'/dataset/projectaccess'} element={<ProjectAccess />} />
Expand Down
3 changes: 2 additions & 1 deletion packages/client/src/components/SideBar.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ export const SideBar: FC<SideBarProps> = ({ open, drawerWidth }) => {
visible: (p) => p!.studyAdmin
},
{ name: t('menu.entryControl'), action: () => navigate('/study/entries'), visible: (p) => p!.studyAdmin },
{ name: t('menu.viewTags'), action: () => navigate('/study/tags'), visible: (p) => p!.studyAdmin }
{ name: t('menu.viewTags'), action: () => navigate('/study/tags'), visible: (p) => p!.studyAdmin },
{ name: t('menu.studyDownloads'), action: () => navigate('/study/downloads'), visible: (p) => p!.studyAdmin }
]
},
{
Expand Down
33 changes: 33 additions & 0 deletions packages/client/src/graphql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ export type CreateDatasetDownloadRequest = {
dataset: Scalars['ID']['input'];
};

export type CreateStudyDownloadRequest = {
study: Scalars['ID']['input'];
};

export type Dataset = {
__typename?: 'Dataset';
_id: Scalars['ID']['output'];
Expand Down Expand Up @@ -78,6 +82,7 @@ export type Entry = {
signedUrl: Scalars['String']['output'];
/** Get the number of milliseconds the signed URL is valid for. */
signedUrlExpiration: Scalars['Float']['output'];
signlabRecording?: Maybe<SignLabRecorded>;
};

export type FreeTextField = {
Expand Down Expand Up @@ -146,6 +151,7 @@ export type Mutation = {
createDatasetDownload: DatasetDownloadRequest;
createOrganization: Organization;
createStudy: Study;
createStudyDownload: StudyDownloadRequest;
createTags: Array<Tag>;
createTrainingSet: Scalars['Boolean']['output'];
createUploadSession: UploadSession;
Expand Down Expand Up @@ -229,6 +235,11 @@ export type MutationCreateStudyArgs = {
};


export type MutationCreateStudyDownloadArgs = {
downloadRequest: CreateStudyDownloadRequest;
};


export type MutationCreateTagsArgs = {
entries: Array<Scalars['ID']['input']>;
study: Scalars['ID']['input'];
Expand Down Expand Up @@ -410,6 +421,7 @@ export type Query = {
getProjectPermissions: Array<ProjectPermissionModel>;
getProjects: Array<Project>;
getRoles: Permission;
getStudyDownloads: Array<StudyDownloadRequest>;
getStudyPermissions: Array<StudyPermissionModel>;
getTags: Array<Tag>;
getTrainingTags: Array<Tag>;
Expand Down Expand Up @@ -486,6 +498,11 @@ export type QueryGetRolesArgs = {
};


export type QueryGetStudyDownloadsArgs = {
study: Scalars['ID']['input'];
};


export type QueryGetStudyPermissionsArgs = {
study: Scalars['ID']['input'];
};
Expand Down Expand Up @@ -535,6 +552,11 @@ export type QueryValidateCsvArgs = {
session: Scalars['ID']['input'];
};

export type SignLabRecorded = {
__typename?: 'SignLabRecorded';
fieldName: Scalars['String']['output'];
};

export type SliderField = {
__typename?: 'SliderField';
value: Scalars['Float']['output'];
Expand All @@ -560,6 +582,17 @@ export type StudyCreate = {
tagsPerEntry: Scalars['Float']['input'];
};

export type StudyDownloadRequest = {
__typename?: 'StudyDownloadRequest';
_id: Scalars['String']['output'];
date: Scalars['DateTime']['output'];
entryZip: Scalars['String']['output'];
status: Scalars['String']['output'];
study: Study;
tagCSV: Scalars['String']['output'];
taggedEntries: Scalars['String']['output'];
};

export type StudyPermissionModel = {
__typename?: 'StudyPermissionModel';
isContributor: Scalars['Boolean']['output'];
Expand Down
31 changes: 31 additions & 0 deletions packages/client/src/graphql/study/study.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,34 @@ mutation createStudy($study: StudyCreate!) {
query studyExists($name: String!, $project: ID!) {
studyExists(name: $name, project: $project)
}

query getStudyDownloads($study: ID!) {
getStudyDownloads(study: $study) {
_id,
date,
status,
entryZip,
tagCSV,
taggedEntries,
study {
_id
name
description
instructions
project
tagsPerEntry
tagSchema {
dataSchema
uiSchema
}
}
}
}

mutation createStudyDownload($downloadRequest: CreateStudyDownloadRequest!) {
createStudyDownload(downloadRequest: $downloadRequest) {
_id,
status,
date
}
}
103 changes: 102 additions & 1 deletion packages/client/src/graphql/study/study.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@ export type StudyExistsQueryVariables = Types.Exact<{

export type StudyExistsQuery = { __typename?: 'Query', studyExists: boolean };

export type GetStudyDownloadsQueryVariables = Types.Exact<{
study: Types.Scalars['ID']['input'];
}>;


export type GetStudyDownloadsQuery = { __typename?: 'Query', getStudyDownloads: Array<{ __typename?: 'StudyDownloadRequest', _id: string, date: any, status: string, entryZip: string, tagCSV: string, taggedEntries: string, study: { __typename?: 'Study', _id: string, name: string, description: string, instructions: string, project: string, tagsPerEntry: number, tagSchema: { __typename?: 'TagSchema', dataSchema: any, uiSchema: any } } }> };

export type CreateStudyDownloadMutationVariables = Types.Exact<{
downloadRequest: Types.CreateStudyDownloadRequest;
}>;


export type CreateStudyDownloadMutation = { __typename?: 'Mutation', createStudyDownload: { __typename?: 'StudyDownloadRequest', _id: string, status: string, date: any } };


export const FindStudiesDocument = gql`
query findStudies($project: ID!) {
Expand Down Expand Up @@ -185,4 +199,91 @@ export function useStudyExistsLazyQuery(baseOptions?: Apollo.LazyQueryHookOption
}
export type StudyExistsQueryHookResult = ReturnType<typeof useStudyExistsQuery>;
export type StudyExistsLazyQueryHookResult = ReturnType<typeof useStudyExistsLazyQuery>;
export type StudyExistsQueryResult = Apollo.QueryResult<StudyExistsQuery, StudyExistsQueryVariables>;
export type StudyExistsQueryResult = Apollo.QueryResult<StudyExistsQuery, StudyExistsQueryVariables>;
export const GetStudyDownloadsDocument = gql`
query getStudyDownloads($study: ID!) {
getStudyDownloads(study: $study) {
_id
date
status
entryZip
tagCSV
taggedEntries
study {
_id
name
description
instructions
project
tagsPerEntry
tagSchema {
dataSchema
uiSchema
}
}
}
}
`;

/**
* __useGetStudyDownloadsQuery__
*
* To run a query within a React component, call `useGetStudyDownloadsQuery` and pass it any options that fit your needs.
* When your component renders, `useGetStudyDownloadsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetStudyDownloadsQuery({
* variables: {
* study: // value for 'study'
* },
* });
*/
export function useGetStudyDownloadsQuery(baseOptions: Apollo.QueryHookOptions<GetStudyDownloadsQuery, GetStudyDownloadsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetStudyDownloadsQuery, GetStudyDownloadsQueryVariables>(GetStudyDownloadsDocument, options);
}
export function useGetStudyDownloadsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetStudyDownloadsQuery, GetStudyDownloadsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetStudyDownloadsQuery, GetStudyDownloadsQueryVariables>(GetStudyDownloadsDocument, options);
}
export type GetStudyDownloadsQueryHookResult = ReturnType<typeof useGetStudyDownloadsQuery>;
export type GetStudyDownloadsLazyQueryHookResult = ReturnType<typeof useGetStudyDownloadsLazyQuery>;
export type GetStudyDownloadsQueryResult = Apollo.QueryResult<GetStudyDownloadsQuery, GetStudyDownloadsQueryVariables>;
export const CreateStudyDownloadDocument = gql`
mutation createStudyDownload($downloadRequest: CreateStudyDownloadRequest!) {
createStudyDownload(downloadRequest: $downloadRequest) {
_id
status
date
}
}
`;
export type CreateStudyDownloadMutationFn = Apollo.MutationFunction<CreateStudyDownloadMutation, CreateStudyDownloadMutationVariables>;

/**
* __useCreateStudyDownloadMutation__
*
* To run a mutation, you first call `useCreateStudyDownloadMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateStudyDownloadMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [createStudyDownloadMutation, { data, loading, error }] = useCreateStudyDownloadMutation({
* variables: {
* downloadRequest: // value for 'downloadRequest'
* },
* });
*/
export function useCreateStudyDownloadMutation(baseOptions?: Apollo.MutationHookOptions<CreateStudyDownloadMutation, CreateStudyDownloadMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<CreateStudyDownloadMutation, CreateStudyDownloadMutationVariables>(CreateStudyDownloadDocument, options);
}
export type CreateStudyDownloadMutationHookResult = ReturnType<typeof useCreateStudyDownloadMutation>;
export type CreateStudyDownloadMutationResult = Apollo.MutationResult<CreateStudyDownloadMutation>;
export type CreateStudyDownloadMutationOptions = Apollo.BaseMutationOptions<CreateStudyDownloadMutation, CreateStudyDownloadMutationVariables>;
43 changes: 41 additions & 2 deletions packages/client/src/pages/studies/StudyControl.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Typography, Box } from '@mui/material';
import { Typography, Box, IconButton } from '@mui/material';
import { useStudy } from '../../context/Study.context';
import { DataGrid, GridColDef, GridRowId } from '@mui/x-data-grid';
import DeleteIcon from '@mui/icons-material/DeleteOutlined';
import { GridActionsCellItem } from '@mui/x-data-grid-pro';
import { Study } from '../../graphql/graphql';
import { useDeleteStudyMutation } from '../../graphql/study/study';
import { useCreateStudyDownloadMutation, useDeleteStudyMutation } from '../../graphql/study/study';
import { useEffect } from 'react';
import { useConfirmation } from '../../context/Confirmation.context';
import { useTranslation } from 'react-i18next';
import { useSnackbar } from '../../context/Snackbar.context';
import { Download } from '@mui/icons-material';

export const StudyControl: React.FC = () => {
const { studies, updateStudies } = useStudy();
Expand All @@ -18,6 +19,8 @@ export const StudyControl: React.FC = () => {
const { t } = useTranslation();
const { pushSnackbarMessage } = useSnackbar();

const [createDownloadMutation, createDownloadResults] = useCreateStudyDownloadMutation();

const handleDelete = async (id: GridRowId) => {
// Execute delete mutation
confirmation.pushConfirmationRequest({
Expand All @@ -40,6 +43,32 @@ export const StudyControl: React.FC = () => {
}
}, [deleteStudyResults.called, deleteStudyResults.data, deleteStudyResults.error]);

const handleDownloadRequest = (study: Study) => {
confirmation.pushConfirmationRequest({
title: t('components.studyDownload.downloadTitle'),
message: t('components.studyDownload.downloadDescription'),
onConfirm: () => {
createDownloadMutation({
variables: {
downloadRequest: {
study: study._id
}
}
});
},
onCancel: () => {}
});
};

// Share the results with the user
useEffect(() => {
if (createDownloadResults.data) {
pushSnackbarMessage(t('components.studyDownload.downloadStartedSuccess'), 'success');
} else if (createDownloadResults.error) {
pushSnackbarMessage(t('components.studyDownload.downloadFailed'), 'error');
}
}, [createDownloadResults.data, createDownloadResults.error]);

const columns: GridColDef[] = [
{
field: 'name',
Expand All @@ -53,6 +82,16 @@ export const StudyControl: React.FC = () => {
width: 500,
editable: false
},
{
field: 'download',
headerName: t('common.download'),
width: 200,
renderCell: (params) => (
<IconButton onClick={() => handleDownloadRequest(params.row)}>
<Download />
</IconButton>
)
},
{
field: 'delete',
type: 'actions',
Expand Down
Loading