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
26 changes: 26 additions & 0 deletions packages/client/src/graphql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ export type DatasetCreate = {
name: Scalars['String']['input'];
};

export type DatasetProjectPermission = {
__typename?: 'DatasetProjectPermission';
dataset: Dataset;
projectHasAccess: Scalars['Boolean']['output'];
};

export type EmailLoginDto = {
email: Scalars['String']['input'];
password: Scalars['String']['input'];
Expand Down Expand Up @@ -191,6 +197,7 @@ export type Mutation = {
forgotPassword: Scalars['Boolean']['output'];
grantContributor: Scalars['Boolean']['output'];
grantOwner: Scalars['Boolean']['output'];
grantProjectDatasetAccess: Scalars['Boolean']['output'];
grantProjectPermissions: Scalars['Boolean']['output'];
grantStudyAdmin: Scalars['Boolean']['output'];
grantTrainedContributor: Scalars['Boolean']['output'];
Expand Down Expand Up @@ -327,6 +334,13 @@ export type MutationGrantOwnerArgs = {
};


export type MutationGrantProjectDatasetAccessArgs = {
dataset: Scalars['ID']['input'];
hasAccess: Scalars['Boolean']['input'];
project: Scalars['ID']['input'];
};


export type MutationGrantProjectPermissionsArgs = {
isAdmin: Scalars['Boolean']['input'];
project: Scalars['ID']['input'];
Expand Down Expand Up @@ -520,7 +534,9 @@ export type Query = {
findStudies: Array<Study>;
/** Get the presigned URL for where to upload the CSV against */
getCSVUploadURL: Scalars['String']['output'];
getDatasetProjectPermissions: Array<DatasetProjectPermission>;
getDatasets: Array<Dataset>;
getDatasetsByProject: Array<Dataset>;
getEntryUploadURL: Scalars['String']['output'];
getOrganizations: Array<Organization>;
getProject: ProjectModel;
Expand Down Expand Up @@ -564,6 +580,16 @@ export type QueryGetCsvUploadUrlArgs = {
};


export type QueryGetDatasetProjectPermissionsArgs = {
project: Scalars['ID']['input'];
};


export type QueryGetDatasetsByProjectArgs = {
project: Scalars['ID']['input'];
};


export type QueryGetEntryUploadUrlArgs = {
contentType: Scalars['String']['input'];
filename: Scalars['String']['input'];
Expand Down
15 changes: 15 additions & 0 deletions packages/client/src/graphql/permission/permission.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,18 @@ mutation grantContributor($study: ID!, $user: ID!, $isContributor: Boolean!) {
mutation grantTrainedContributor($study: ID!, $user: ID!, $isTrained: Boolean!) {
grantTrainedContributor(study: $study, user: $user, isTrained: $isTrained)
}

query getDatasetProjectPermissions($project: ID!) {
getDatasetProjectPermissions(project: $project) {
dataset {
_id,
name,
description
},
projectHasAccess
}
}

mutation grantProjectDatasetAccess($project: ID!, $dataset: ID!, $hasAccess: Boolean!) {
grantProjectDatasetAccess(project: $project, dataset: $dataset, hasAccess: $hasAccess)
}
95 changes: 94 additions & 1 deletion packages/client/src/graphql/permission/permission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,22 @@ export type GrantTrainedContributorMutationVariables = Types.Exact<{

export type GrantTrainedContributorMutation = { __typename?: 'Mutation', grantTrainedContributor: boolean };

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


export type GetDatasetProjectPermissionsQuery = { __typename?: 'Query', getDatasetProjectPermissions: Array<{ __typename?: 'DatasetProjectPermission', projectHasAccess: boolean, dataset: { __typename?: 'Dataset', _id: string, name: string, description: string } }> };

export type GrantProjectDatasetAccessMutationVariables = Types.Exact<{
project: Types.Scalars['ID']['input'];
dataset: Types.Scalars['ID']['input'];
hasAccess: Types.Scalars['Boolean']['input'];
}>;


export type GrantProjectDatasetAccessMutation = { __typename?: 'Mutation', grantProjectDatasetAccess: boolean };


export const GetProjectPermissionsDocument = gql`
query getProjectPermissions($project: ID!) {
Expand Down Expand Up @@ -285,4 +301,81 @@ export function useGrantTrainedContributorMutation(baseOptions?: Apollo.Mutation
}
export type GrantTrainedContributorMutationHookResult = ReturnType<typeof useGrantTrainedContributorMutation>;
export type GrantTrainedContributorMutationResult = Apollo.MutationResult<GrantTrainedContributorMutation>;
export type GrantTrainedContributorMutationOptions = Apollo.BaseMutationOptions<GrantTrainedContributorMutation, GrantTrainedContributorMutationVariables>;
export type GrantTrainedContributorMutationOptions = Apollo.BaseMutationOptions<GrantTrainedContributorMutation, GrantTrainedContributorMutationVariables>;
export const GetDatasetProjectPermissionsDocument = gql`
query getDatasetProjectPermissions($project: ID!) {
getDatasetProjectPermissions(project: $project) {
dataset {
_id
name
description
}
projectHasAccess
}
}
`;

/**
* __useGetDatasetProjectPermissionsQuery__
*
* To run a query within a React component, call `useGetDatasetProjectPermissionsQuery` and pass it any options that fit your needs.
* When your component renders, `useGetDatasetProjectPermissionsQuery` 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 } = useGetDatasetProjectPermissionsQuery({
* variables: {
* project: // value for 'project'
* },
* });
*/
export function useGetDatasetProjectPermissionsQuery(baseOptions: Apollo.QueryHookOptions<GetDatasetProjectPermissionsQuery, GetDatasetProjectPermissionsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetDatasetProjectPermissionsQuery, GetDatasetProjectPermissionsQueryVariables>(GetDatasetProjectPermissionsDocument, options);
}
export function useGetDatasetProjectPermissionsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetDatasetProjectPermissionsQuery, GetDatasetProjectPermissionsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetDatasetProjectPermissionsQuery, GetDatasetProjectPermissionsQueryVariables>(GetDatasetProjectPermissionsDocument, options);
}
export type GetDatasetProjectPermissionsQueryHookResult = ReturnType<typeof useGetDatasetProjectPermissionsQuery>;
export type GetDatasetProjectPermissionsLazyQueryHookResult = ReturnType<typeof useGetDatasetProjectPermissionsLazyQuery>;
export type GetDatasetProjectPermissionsQueryResult = Apollo.QueryResult<GetDatasetProjectPermissionsQuery, GetDatasetProjectPermissionsQueryVariables>;
export const GrantProjectDatasetAccessDocument = gql`
mutation grantProjectDatasetAccess($project: ID!, $dataset: ID!, $hasAccess: Boolean!) {
grantProjectDatasetAccess(
project: $project
dataset: $dataset
hasAccess: $hasAccess
)
}
`;
export type GrantProjectDatasetAccessMutationFn = Apollo.MutationFunction<GrantProjectDatasetAccessMutation, GrantProjectDatasetAccessMutationVariables>;

/**
* __useGrantProjectDatasetAccessMutation__
*
* To run a mutation, you first call `useGrantProjectDatasetAccessMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useGrantProjectDatasetAccessMutation` 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 [grantProjectDatasetAccessMutation, { data, loading, error }] = useGrantProjectDatasetAccessMutation({
* variables: {
* project: // value for 'project'
* dataset: // value for 'dataset'
* hasAccess: // value for 'hasAccess'
* },
* });
*/
export function useGrantProjectDatasetAccessMutation(baseOptions?: Apollo.MutationHookOptions<GrantProjectDatasetAccessMutation, GrantProjectDatasetAccessMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<GrantProjectDatasetAccessMutation, GrantProjectDatasetAccessMutationVariables>(GrantProjectDatasetAccessDocument, options);
}
export type GrantProjectDatasetAccessMutationHookResult = ReturnType<typeof useGrantProjectDatasetAccessMutation>;
export type GrantProjectDatasetAccessMutationResult = Apollo.MutationResult<GrantProjectDatasetAccessMutation>;
export type GrantProjectDatasetAccessMutationOptions = Apollo.BaseMutationOptions<GrantProjectDatasetAccessMutation, GrantProjectDatasetAccessMutationVariables>;
149 changes: 78 additions & 71 deletions packages/client/src/pages/datasets/ProjectAccess.tsx
Original file line number Diff line number Diff line change
@@ -1,78 +1,85 @@
import { Accordion, Box, Container, Typography } from '@mui/material';
import AccordionSummary from '@mui/material/AccordionSummary';
import AccordionDetails from '@mui/material/AccordionDetails';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { DatasetAccess } from '../../components/DatasetAccess.component';

const rows = [
{
id: 1,
name: 'Project Flour',
description:
'Led by James Beard Award-winning pastry chef + co-owner Joanne Chang, Flour Bakery now has nine locations in Boston + Cambridge. We offer buttery breakfast pastries; soft + chewy cookies; luscious pies; gorgeous cakes; and fresh, made-to-order sandwiches, soups, and salads - all prepared daily by our dedicated team.',
access: true
},
{
id: 2,
name: 'Project Tesla',
description:
'The Energy Generation and Storage segment engages in the design, manufacture, installation, sale, and leasing of solar energy generation and energy storage products, and related services to residential, commercial, and industrial customers and utilities through its website, stores, and galleries, as well as through a network of channel partners',
access: true
},
{
id: 3,
name: 'Project Starbucks',
description:
'The company was founded by Steven Paul Jobs, Ronald Gerald Wayne, and Stephen G. Wozniak on April 1, 1976 and is headquartered in Cupertino, CA.',
access: true
},
{
id: 4,
name: 'Project Charles',
description:
'Investment, wealth and alternative managers, asset owners and insurers in over 30 countries rely on Charles River IMS to manage USD $48 Trillion in assets.',
access: false
}
];
import { Typography, Switch } from '@mui/material';
import { useProject } from '../../context/Project.context';
import { DataGrid, GridColDef } from '@mui/x-data-grid';
import { useGetDatasetProjectPermissionsLazyQuery } from '../../graphql/permission/permission';
import { useEffect, useState } from 'react';
import { DatasetProjectPermission, Project } from '../../graphql/graphql';
import { useGrantProjectDatasetAccessMutation } from '../../graphql/permission/permission';

export const ProjectAccess: React.FC = () => {
const { project } = useProject();
const [getDatasetProjectPermissions, datasetProjectPermissionResults] = useGetDatasetProjectPermissionsLazyQuery();
const [projectAccess, setProjectAccess] = useState<DatasetProjectPermission[]>([]);
const [grantProjectDatasetAccess, grantProjectDatasetAccessResults] = useGrantProjectDatasetAccessMutation();

// For querying for the permissions
useEffect(() => {
if (project) {
getDatasetProjectPermissions({ variables: { project: project._id } });
}
}, [project]);

// For setting the permissions
useEffect(() => {
if (datasetProjectPermissionResults.data) {
setProjectAccess(datasetProjectPermissionResults.data.getDatasetProjectPermissions);
}
}, [datasetProjectPermissionResults]);

// For updating the permissions
useEffect(() => {
if (grantProjectDatasetAccessResults.data) {
datasetProjectPermissionResults.refetch();
}
}, [grantProjectDatasetAccessResults]);

const handleAccessChange = (dataset: string, project: string, hasAccess: boolean) => {
grantProjectDatasetAccess({ variables: { dataset, project, hasAccess } });
};

const columns: GridColDef[] = [
{ field: 'name', headerName: 'Dataset Name', width: 200, valueGetter: (params) => params.row.dataset.name },
{
field: 'description',
headerName: 'Description',
width: 200,
valueGetter: (params) => params.row.dataset.description
},
{
field: 'access',
headerName: 'Project Has Access',
width: 200,
renderCell: (params) => (
<DatasetAccess permission={params.row} project={project!} changeAccess={handleAccessChange} />
)
}
];

return (
<>
<Typography variant="h5">Project Access</Typography>
<Accordion disableGutters>
<AccordionSummary expandIcon={<ExpandMoreIcon />} aria-controls="panel1a-content" id="panel1a-header">
<Typography sx={{ fontSize: '15px', position: 'absolute', top: '14px', left: '3%' }}>
Dataset 1 name
</Typography>
<Typography sx={{ fontSize: '15px', position: 'absolute', top: '14px', right: '52%' }}>
Dataset 1 description
</Typography>
</AccordionSummary>
<AccordionDetails>
<Container sx={{ width: '100%', display: 'flex', flexDirection: 'column', justifyContext: 'space-between' }}>
<Box sx={{ position: '-webkit-sticky' }}>
<DatasetAccess tableRows={rows} />
</Box>
</Container>
</AccordionDetails>
</Accordion>
<Accordion disableGutters>
<AccordionSummary expandIcon={<ExpandMoreIcon />} aria-controls="panel2a-content" id="panel2a-header">
<Typography sx={{ fontSize: '15px', position: 'absolute', top: '14px', left: '3%' }}>
Dataset 2 name
</Typography>
<Typography sx={{ fontSize: '15px', position: 'absolute', top: '14px', right: '52%' }}>
Dataset2 description
</Typography>
</AccordionSummary>
<AccordionDetails>
<Container sx={{ width: '100%', display: 'flex', flexDirection: 'column', justifyContext: 'space-between' }}>
<Box sx={{ position: '-webkit-sticky' }}>
<DatasetAccess tableRows={rows} />
</Box>
</Container>
</AccordionDetails>
</Accordion>
{project ? (
<>
<Typography variant="h5">Dataset Access for "{project.name}"</Typography>
<DataGrid columns={columns} rows={projectAccess} getRowId={(row) => row.dataset._id} />
</>
) : (
<Typography variant="h5">Select a Project to Continue</Typography>
)}
</>
);
};

interface DatasetAccessProps {
permission: DatasetProjectPermission;
project: Project;
changeAccess: (dataset: string, project: string, access: boolean) => void;
}

const DatasetAccess: React.FC<DatasetAccessProps> = ({ permission, changeAccess, project }) => {
return (
<Switch
checked={permission.projectHasAccess}
onChange={(event) => changeAccess(permission.dataset._id, project._id, event.target.checked)}
/>
);
};
8 changes: 8 additions & 0 deletions packages/server/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ type StudyPermissionModel {
isTrainedEditable: Boolean!
}

type DatasetProjectPermission {
dataset: Dataset!
projectHasAccess: Boolean!
}

type Entry {
_id: String!
organization: ID!
Expand Down Expand Up @@ -128,8 +133,10 @@ type Query {
getOrganizations: [Organization!]!
exists(name: String!): Boolean!
getDatasets: [Dataset!]!
getDatasetsByProject(project: ID!): [Dataset!]!
getProjectPermissions(project: ID!): [ProjectPermissionModel!]!
getStudyPermissions(study: ID!): [StudyPermissionModel!]!
getDatasetProjectPermissions(project: ID!): [DatasetProjectPermission!]!
projectExists(name: String!): Boolean!
getProjects: [Project!]!
studyExists(name: String!, project: ID!): Boolean!
Expand All @@ -152,6 +159,7 @@ type Mutation {
grantStudyAdmin(study: ID!, user: ID!, isAdmin: Boolean!): Boolean!
grantContributor(study: ID!, user: ID!, isContributor: Boolean!): Boolean!
grantTrainedContributor(study: ID!, user: ID!, isTrained: Boolean!): Boolean!
grantProjectDatasetAccess(project: ID!, dataset: ID!, hasAccess: Boolean!): Boolean!
signLabCreateProject(project: ProjectCreate!): Project!
deleteProject(project: ID!): Boolean!
createStudy(study: StudyCreate!): Study!
Expand Down
10 changes: 8 additions & 2 deletions packages/server/src/dataset/dataset.module.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { Module } from '@nestjs/common';
import { Module, forwardRef } from '@nestjs/common';
import { DatasetResolver } from './dataset.resolver';
import { DatasetService } from './dataset.service';
import { MongooseModule } from '@nestjs/mongoose';
import { Dataset, DatasetSchema } from './dataset.model';
import { DatasetPipe } from './pipes/dataset.pipe';
import { PermissionModule } from '../permission/permission.module';
import { JwtModule } from '../jwt/jwt.module';
import { ProjectModule } from '../project/project.module';

@Module({
imports: [MongooseModule.forFeature([{ name: Dataset.name, schema: DatasetSchema }]), PermissionModule, JwtModule],
imports: [
MongooseModule.forFeature([{ name: Dataset.name, schema: DatasetSchema }]),
forwardRef(() => PermissionModule),
JwtModule,
ProjectModule
],
providers: [DatasetResolver, DatasetService, DatasetPipe],
exports: [DatasetService, DatasetPipe]
})
Expand Down
Loading