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

task/DES-2685: Add API endpoint and frontend view for project listing #1167

Merged
merged 13 commits into from
Feb 26, 2024
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
1 change: 1 addition & 0 deletions client/modules/_hooks/src/datafiles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ export {
usePathDisplayName,
getSystemRootDisplayName,
} from './usePathDisplayName';
export * from './projects';
14 changes: 14 additions & 0 deletions client/modules/_hooks/src/datafiles/projects/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export * from './types';
export { useProjectListing } from './useProjectListing';
export {
useProjectDetail,
useFileAssociations,
useFileTags,
} from './useProjectDetail';
export { useProjectPreview } from './useProjectPreview';
export { useProjectEntityReorder } from './useProjectEntityReorder';
export { useAddEntityToTree } from './useAddEntityToTree';
export { useRemoveEntityFromTree } from './useRemoveEntityFromTree';
export { useAddFileAssociation } from './useAddFileAssociation';
export { useRemoveFileAssociation } from './useRemoveFileAssociation';
export { useSetFileTags } from './useSetFileTags';
118 changes: 118 additions & 0 deletions client/modules/_hooks/src/datafiles/projects/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
export type TProjectUser = {
fname: string;
lname: string;
email: string;
inst: string;
role: 'pi' | 'co_pi' | 'team_member' | 'guest';
username?: string;
};

export type TProjectAward = {
name: string;
number: string;
fundingSource: string;
};

export type TReferencedWork = {
title: string;
doi: string;
hrefType: 'doi' | 'url';
};

export type TAssociatedProject = {
type: 'Context' | 'Linked Dataset' | 'Cited By';
title: string;
href: string;
hrefType: 'doi' | 'url';
};

export type TFileTag = {
tagName: string;
path: string;
};

export type TFileObj = {
system: string;
name: string;
path: string;
type: 'file' | 'dir';
length?: number;
lastModified?: string;
};

export type THazmapperMap = {
name: string;
uuid: string;
path: string;
deployment: string;
href?: string;
};

export type TDropdownValue = {
id: string;
name: string;
};

export type TNHEvent = {
eventName: string;
eventStart: string;
eventEnd: string;
location: string;
latitude: string;
longitude: string;
};

export type TBaseProjectValue = {
projectId: string;
projectType:
| 'other'
| 'experimental'
| 'simulation'
| 'hybrid_simulation'
| 'field_recon'
| 'field_reconnaissance'
| 'None';

title: string;
description: string;
users: TProjectUser[];
dataTypes?: TDropdownValue[];
authors: TProjectUser[];

awardNumbers: TProjectAward[];
associatedProjects: TAssociatedProject[];
referencedData: TReferencedWork[];
keywords: string[];
nhEvents: TNHEvent[];
nhTypes: TDropdownValue[];
frTypes?: TDropdownValue[];
facilities: TDropdownValue[];

dois: string[];
fileObjs: TFileObj[];
fileTags: TFileTag[];
};

type TEntityValue = {
title: string;
description?: string;
authors?: TProjectUser[];
fileObjs?: TFileObj[];
fileTags: TFileTag[];
};

export type TProjectMeta = {
uuid: string;
name: string;
created: string;
lastUpdated: string;
};

export type TBaseProject = TProjectMeta & {
name: 'designsafe.project';
value: TBaseProjectValue;
};

export type TEntityMeta = TProjectMeta & {
value: TEntityValue;
};
22 changes: 22 additions & 0 deletions client/modules/_hooks/src/datafiles/projects/useAddEntityToTree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import apiClient from '../../apiClient';

async function addEntity(projectId: string, nodeId: string, uuid: string) {
const res = await apiClient.post(
`/api/projects/v2/${projectId}/entities/associations/${nodeId}/`,
{ uuid }
);
return res.data;
}

export function useAddEntityToTree(projectId: string, nodeId: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ uuid }: { uuid: string }) =>
addEntity(projectId, nodeId, uuid),
onSuccess: () =>
queryClient.invalidateQueries({
queryKey: ['datafiles', 'projects', 'detail', projectId],
}),
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import apiClient from '../../apiClient';
import { TFileObj } from './types';

async function addFileAssociation(
projectId: string,
entityUuid: string,
fileObjs: TFileObj[]
) {
const res = await apiClient.post(
`/api/projects/v2/${projectId}/entities/${entityUuid}/files/`,
{ fileObjs }
);
return res.data;
}

export function useAddFileAssociation(projectId: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
fileObjs,
entityUuid,
}: {
fileObjs: TFileObj[];
entityUuid: string;
}) => addFileAssociation(projectId, entityUuid, fileObjs),
onSuccess: () =>
queryClient.invalidateQueries({
queryKey: ['datafiles', 'projects', 'detail', projectId],
}),
});
}
73 changes: 73 additions & 0 deletions client/modules/_hooks/src/datafiles/projects/useProjectDetail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { useQuery } from '@tanstack/react-query';
import apiClient from '../../apiClient';
import { TBaseProject, TEntityMeta, TFileTag } from './types';
import { useMemo } from 'react';

type TProjectDetailResponse = {
baseProject: TBaseProject;
entities: TEntityMeta[];
tree: unknown;
};

async function getProjectDetail({
projectId,
signal,
}: {
projectId: string;
signal: AbortSignal;
}) {
const resp = await apiClient.get<TProjectDetailResponse>(
`/api/projects/v2/${projectId}/`,
{
signal,
}
);
return resp.data;
}

export function useProjectDetail(projectId: string) {
return useQuery({
queryKey: ['datafiles', 'projects', 'detail', projectId],
queryFn: ({ signal }) => getProjectDetail({ projectId, signal }),
enabled: !!projectId,
});
}

export function useFileAssociations(projectId: string) {
/*Return a record mapping file paths to an array of entities containing those paths.*/
const { data } = useProjectDetail(projectId);

const memoizedFileMapping = useMemo(() => {
const entities = data?.entities ?? [];
const fileMapping: Record<string, TEntityMeta[]> = {};
entities.forEach((entity) => {
const fileObjs = entity.value.fileObjs ?? [];
fileObjs.forEach((fileObj) => {
const entityList = fileMapping[fileObj.path] ?? [];
entityList.push(entity);
fileMapping[fileObj.path] = entityList;
});
});
return fileMapping;
}, [data]);

return memoizedFileMapping;
}

export function useFileTags(projectId: string) {
/*Return a record mapping file paths to an array of entities containing those paths.*/
const { data } = useProjectDetail(projectId);

const memoizedFileMapping = useMemo(() => {
const entities = data?.entities ?? [];
const tagMapping: Record<string, TFileTag[]> = {};
entities.forEach((entity) => {
const fileTags = entity.value.fileTags ?? [];
tagMapping[entity.uuid] = fileTags;
});

return tagMapping;
}, [data]);

return memoizedFileMapping;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import apiClient from '../../apiClient';

async function reorderEntitity(
projectId: string,
nodeId: string,
order: number
) {
const res = await apiClient.put(
`/api/projects/v2/${projectId}/entities/ordering/`,
{ nodeId, order }
);
return res.data;
}

export function useProjectEntityReorder(projectId: string, nodeId: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ order }: { order: number }) =>
reorderEntitity(projectId, nodeId, order),
onSuccess: () =>
queryClient.invalidateQueries({
queryKey: ['datafiles', 'projects', 'detail', projectId],
}),
});
}
34 changes: 34 additions & 0 deletions client/modules/_hooks/src/datafiles/projects/useProjectListing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useQuery } from '@tanstack/react-query';
import apiClient from '../../apiClient';
import { TBaseProject } from './types';

export type TProjectListingResponse = {
total: number;
result: TBaseProject[];
};

async function getProjectListing({
page = 1,
limit = 100,
signal,
}: {
page: number;
limit: number;
signal: AbortSignal;
}) {
const resp = await apiClient.get<TProjectListingResponse>(
'/api/projects/v2',
{
signal,
params: { offset: (page - 1) * limit, limit },
}
);
return resp.data;
}

export function useProjectListing(page: number, limit: number) {
return useQuery({
queryKey: ['datafiles', 'projects', 'listing', page, limit],
queryFn: ({ signal }) => getProjectListing({ page, limit, signal }),
});
}
33 changes: 33 additions & 0 deletions client/modules/_hooks/src/datafiles/projects/useProjectPreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useQuery } from '@tanstack/react-query';
import apiClient from '../../apiClient';
import { TBaseProject, TEntityMeta } from './types';

type TProjectPreviewResponse = {
baseProject: TBaseProject;
entities: TEntityMeta[];
tree: unknown;
};

async function getProjectPreview({
projectId,
signal,
}: {
projectId: string;
signal: AbortSignal;
}) {
const resp = await apiClient.get<TProjectPreviewResponse>(
`/api/projects/v2/${projectId}/preview/`,
{
signal,
}
);
return resp.data;
}

export function useProjectPreview(projectId: string) {
return useQuery({
queryKey: ['datafiles', 'projects', 'detail', projectId, 'preview'],
queryFn: ({ signal }) => getProjectPreview({ projectId, signal }),
enabled: !!projectId,
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import apiClient from '../../apiClient';

async function removeEntity(projectId: string, nodeId: string) {
const res = await apiClient.delete(
`/api/projects/v2/${projectId}/entities/associations/${nodeId}/`
);
return res.data;
}

export function useRemoveEntityFromTree(projectId: string, nodeId: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => removeEntity(projectId, nodeId),
onSuccess: () =>
queryClient.invalidateQueries({
queryKey: ['datafiles', 'projects', 'detail', projectId],
}),
});
}
Loading
Loading