From f488121ec7aeada62d213f174d85785ae9e1c642 Mon Sep 17 00:00:00 2001 From: weijie Date: Wed, 22 Oct 2025 11:52:48 -0400 Subject: [PATCH 01/31] fix: remove logo link after log in --- web/src/components/Layout/DesktopSidebar.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/components/Layout/DesktopSidebar.tsx b/web/src/components/Layout/DesktopSidebar.tsx index c2d01095..8b626611 100644 --- a/web/src/components/Layout/DesktopSidebar.tsx +++ b/web/src/components/Layout/DesktopSidebar.tsx @@ -1,5 +1,5 @@ import { LanguageToggle, ThemeToggle, Tooltip } from '@douglasneuroinformatics/libui/components'; -import { Link, useNavigate } from '@tanstack/react-router'; +import { useNavigate } from '@tanstack/react-router'; import { Logo } from '@/components'; @@ -17,9 +17,9 @@ export const DesktopSidebar = ({ isLogIn, navigation }: DesktopSidebarProps) => return (
- +
- +

- + {tabularDataset.isManager && ( <> From 25dfeb9dd0fcb0cc64f880f168e573d66dc613d2 Mon Sep 17 00:00:00 2001 From: weijie Date: Thu, 30 Oct 2025 11:12:56 -0400 Subject: [PATCH 23/31] fix: primary keys linting error in setup service page --- api/src/setup/setup.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/setup/setup.service.ts b/api/src/setup/setup.service.ts index 042a729b..b8d3bb8b 100644 --- a/api/src/setup/setup.service.ts +++ b/api/src/setup/setup.service.ts @@ -73,7 +73,7 @@ export class SetupService { await this.datasetsService.createDataset( { ...createDemoDatasetData, - primaryKeys: [...createDemoDatasetData.primaryKeys] + primaryKeys: createDemoDatasetData.primaryKeys }, await fs.readFile(path.resolve(import.meta.dirname, 'resources', 'demo-dataset.csv'), 'utf-8'), demoUser.id From a0405e315f85b8ef2e7d3e56cec10a6a194cd940 Mon Sep 17 00:00:00 2001 From: weijie Date: Thu, 30 Oct 2025 13:19:17 -0400 Subject: [PATCH 24/31] fix: success messages in dataset table --- web/src/features/dataset/components/DatasetTable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/features/dataset/components/DatasetTable.tsx b/web/src/features/dataset/components/DatasetTable.tsx index 76e3c631..5e28e041 100644 --- a/web/src/features/dataset/components/DatasetTable.tsx +++ b/web/src/features/dataset/components/DatasetTable.tsx @@ -64,7 +64,7 @@ export const DatasetTable = (tabularDataset: DatasetTableProps) => { .patch(`/v1/datasets/column-nullable/${tabularDataset.id}/${columnId}`) .then(() => { addNotification({ - message: `The data permission level of column with Id ${columnId} has been modified`, + message: `The nullability of column with Id ${columnId} has been modified`, type: 'success' }); }) @@ -102,7 +102,7 @@ export const DatasetTable = (tabularDataset: DatasetTableProps) => { .delete(`/v1/datasets/column/${tabularDataset.id}/${columnId}`) .then(() => { addNotification({ - message: `The data type of column with Id ${columnId} has been modified`, + message: `The column with Id ${columnId} has been deleted.`, type: 'success' }); }) From d58e444f1a30a23148aa9a0faa712c428979b098 Mon Sep 17 00:00:00 2001 From: weijie Date: Mon, 3 Nov 2025 11:13:51 -0500 Subject: [PATCH 25/31] fix: encode uri components in axios calls with email in manage project users page --- web/src/features/projects/pages/ManageProjectUsersPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/features/projects/pages/ManageProjectUsersPage.tsx b/web/src/features/projects/pages/ManageProjectUsersPage.tsx index 33d64e0d..9931fdbf 100644 --- a/web/src/features/projects/pages/ManageProjectUsersPage.tsx +++ b/web/src/features/projects/pages/ManageProjectUsersPage.tsx @@ -16,7 +16,7 @@ const ManageProjectUsersPage = () => { const addProjectUser = (userEmailToAdd: string) => { axios - .post(`/v1/projects/add-user/${projectId}/${userEmailToAdd}`) + .post(`/v1/projects/add-user/${projectId}/${encodeURIComponent(userEmailToAdd)}`) .then(() => { addNotification({ message: `User with Email ${userEmailToAdd} has been added to the current project`, From 9b014dd6ba8e6ca206270c75fe6732eacd6124d6 Mon Sep 17 00:00:00 2001 From: weijie Date: Mon, 3 Nov 2025 11:23:02 -0500 Subject: [PATCH 26/31] fix: double colon in project user card --- web/src/features/projects/components/UserCard.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/features/projects/components/UserCard.tsx b/web/src/features/projects/components/UserCard.tsx index c2b0ec9c..d7ab7e96 100644 --- a/web/src/features/projects/components/UserCard.tsx +++ b/web/src/features/projects/components/UserCard.tsx @@ -56,13 +56,13 @@ const UserCard = ({ projectId, userId, userNumber }: UserCardProps) => {
  • - {t('userFirstName')}: {user.firstName} + {t('userFirstName')} {user.firstName}
  • - {t('userLastName')}: {user.lastName} + {t('userLastName')} {user.lastName}
  • - {t('userEmail')}: {user.email} + {t('userEmail')} {user.email}
From 39482567904ab11e78d1de4e8d0514a1217ee1b7 Mon Sep 17 00:00:00 2001 From: weijie Date: Mon, 3 Nov 2025 13:54:13 -0500 Subject: [PATCH 27/31] fix: add dateime conversion guard to handle null datetime entries being converted to 1970-01-01 --- api/src/columns/columns.service.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/src/columns/columns.service.ts b/api/src/columns/columns.service.ts index 9aa10148..9f9bdd41 100644 --- a/api/src/columns/columns.service.ts +++ b/api/src/columns/columns.service.ts @@ -164,19 +164,20 @@ export class ColumnsService { throw new NotFoundException('Datetime summary NOT FOUND!'); } - let datetimeDataArray: { value: Date }[]; + let datetimeDataArray: { value: Date | null }[]; // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access if (colSeries.dtype.toString() === pl.Datetime('us').toString()) { datetimeDataArray = dataArray.map((entry) => { return { - value: new Date(Math.floor(entry.value / 1000)) + value: entry.value === undefined || entry.value === null ? new Date(Math.floor(entry.value / 1000)) : null }; }); // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access } else if (colSeries.dtype.toString() === pl.Datetime('ns').toString()) { datetimeDataArray = dataArray.map((entry) => { return { - value: new Date(Math.floor(entry.value / 1000000)) + value: + entry.value === undefined || entry.value === null ? new Date(Math.floor(entry.value / 1000000)) : null }; }); } else { From 12447ab94c9b4c12699abb7ee9223e05f35a19d5 Mon Sep 17 00:00:00 2001 From: weijie Date: Mon, 3 Nov 2025 13:55:31 -0500 Subject: [PATCH 28/31] fix: handle null datevalues display in tabular dataset getView --- api/src/tabular-data/tabular-data.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/tabular-data/tabular-data.service.ts b/api/src/tabular-data/tabular-data.service.ts index 66fc5c3b..547025de 100644 --- a/api/src/tabular-data/tabular-data.service.ts +++ b/api/src/tabular-data/tabular-data.service.ts @@ -341,7 +341,7 @@ export class TabularDataService { if (columnIdsModifyData.has(col._id.$oid)) { rows[i][col.name] = 'Hidden'; } else { - rows[i][col.name] = entry.value.$date ? new Date(entry.value.$date).toDateString() : null; + rows[i][col.name] = entry.value ? new Date(entry.value.$date!).toDateString() : null; } }); From eba3dccdbe1135b5276e3cdcda59ffaa6b06eb1d Mon Sep 17 00:00:00 2001 From: weijie Date: Thu, 6 Nov 2025 11:45:50 -0500 Subject: [PATCH 29/31] fix: handling the summary of datetime columns that are entirely null --- api/src/columns/columns.service.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/api/src/columns/columns.service.ts b/api/src/columns/columns.service.ts index 9f9bdd41..8c32d8a5 100644 --- a/api/src/columns/columns.service.ts +++ b/api/src/columns/columns.service.ts @@ -724,7 +724,7 @@ export class ColumnsService { data: { datetimeColumnValidation: { max: new Date(), - min: '1970-01-01' + min: new Date() }, datetimeData: data.toArray().map((entry: Date) => { return { value: entry }; @@ -890,10 +890,13 @@ export class ColumnsService { case 'DATETIME': return { count: currSeries.len() - currSeries.nullCount(), - datetimeSummary: { - max: new Date(Math.floor(currSeries.cast(pl.Datetime('ns'), true).max() / 1000000)), - min: new Date(Math.floor(currSeries.cast(pl.Datetime('ns'), true).min() / 1000000)) - }, + datetimeSummary: + currSeries.len === currSeries.nullCount + ? null + : { + max: new Date(Math.floor(currSeries.cast(pl.Datetime('ns'), true).max() / 1000000)), + min: new Date(Math.floor(currSeries.cast(pl.Datetime('ns'), true).min() / 1000000)) + }, nullCount: currSeries.nullCount() }; case 'ENUM': From 3f5e67c4f8b169d259dfb112267d8600933ba8fa Mon Sep 17 00:00:00 2001 From: weijie Date: Thu, 6 Nov 2025 11:47:42 -0500 Subject: [PATCH 30/31] fix: add query keys to view dataset and dataset table to invalidate query upon changes to the dataset --- .../dataset/components/DatasetTable.tsx | 18 +++++++++----- .../dataset/pages/ViewOneDatasetPage.tsx | 6 +++-- web/src/routes/portal/datasets/$datasetId.tsx | 24 ++++++++++++++----- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/web/src/features/dataset/components/DatasetTable.tsx b/web/src/features/dataset/components/DatasetTable.tsx index 5e28e041..1cc31e04 100644 --- a/web/src/features/dataset/components/DatasetTable.tsx +++ b/web/src/features/dataset/components/DatasetTable.tsx @@ -6,7 +6,11 @@ import { ChevronDownIcon, QuestionMarkCircleIcon, TrashIcon } from '@heroicons/r import { useQueryClient } from '@tanstack/react-query'; import axios from 'axios'; -type DatasetTableProps = Omit<$TabularDataset, 'permission'> & { isManager: boolean; isProject: boolean }; +type DatasetTableProps = Omit<$TabularDataset, 'permission'> & { + isManager: boolean; + isProject: boolean; + queryKey: string; +}; export const DatasetTable = (tabularDataset: DatasetTableProps) => { const { t } = useTranslation('common'); @@ -24,6 +28,9 @@ export const DatasetTable = (tabularDataset: DatasetTableProps) => { message: `The metadata permission level of column with Id ${columnId} has been modified`, type: 'success' }); + void queryClient.invalidateQueries({ + queryKey: [tabularDataset.queryKey] + }); }) .catch((error) => { console.error(error); @@ -32,7 +39,6 @@ export const DatasetTable = (tabularDataset: DatasetTableProps) => { type: 'error' }); }); - await queryClient.invalidateQueries({ queryKey: ['dataset-query'] }); } ); @@ -47,6 +53,7 @@ export const DatasetTable = (tabularDataset: DatasetTableProps) => { message: `The data permission level of column with Id ${columnId} has been modified`, type: 'success' }); + void queryClient.invalidateQueries({ queryKey: [tabularDataset.queryKey] }); }) .catch((error) => { console.error(error); @@ -55,7 +62,6 @@ export const DatasetTable = (tabularDataset: DatasetTableProps) => { type: 'error' }); }); - await queryClient.invalidateQueries({ queryKey: ['dataset-query'] }); } ); @@ -67,6 +73,7 @@ export const DatasetTable = (tabularDataset: DatasetTableProps) => { message: `The nullability of column with Id ${columnId} has been modified`, type: 'success' }); + void queryClient.invalidateQueries({ queryKey: [tabularDataset.queryKey] }); }) .catch((error) => { console.error(error); @@ -75,7 +82,6 @@ export const DatasetTable = (tabularDataset: DatasetTableProps) => { type: 'error' }); }); - await queryClient.invalidateQueries({ queryKey: ['dataset-query'] }); }; const handleChangeColumnType = useDestructiveAction(async (columnId: string, type: $ColumnType) => { @@ -86,6 +92,7 @@ export const DatasetTable = (tabularDataset: DatasetTableProps) => { message: `The data type of column with Id ${columnId} has been modified`, type: 'success' }); + void queryClient.invalidateQueries({ queryKey: [tabularDataset.queryKey] }); }) .catch((error) => { console.error(error); @@ -94,7 +101,6 @@ export const DatasetTable = (tabularDataset: DatasetTableProps) => { type: 'error' }); }); - await queryClient.invalidateQueries({ queryKey: ['dataset-query'] }); }); const handleDeleteColumn = useDestructiveAction(async (columnId: string) => { @@ -105,6 +111,7 @@ export const DatasetTable = (tabularDataset: DatasetTableProps) => { message: `The column with Id ${columnId} has been deleted.`, type: 'success' }); + void queryClient.invalidateQueries({ queryKey: [tabularDataset.queryKey] }); }) .catch((error) => { console.error(error); @@ -113,7 +120,6 @@ export const DatasetTable = (tabularDataset: DatasetTableProps) => { type: 'error' }); }); - await queryClient.invalidateQueries({ queryKey: ['dataset-query'] }); }); const getSummary = (columnName: string) => { diff --git a/web/src/features/dataset/pages/ViewOneDatasetPage.tsx b/web/src/features/dataset/pages/ViewOneDatasetPage.tsx index 650e74cc..c25d812f 100644 --- a/web/src/features/dataset/pages/ViewOneDatasetPage.tsx +++ b/web/src/features/dataset/pages/ViewOneDatasetPage.tsx @@ -24,6 +24,7 @@ type ViewOneDatasetPageProps = { dataset: $TabularDataset; downloadDataUrl: string; downloadMetaDataUrl: string; + queryKey: string; rowPagination: $DatasetViewPagination; }; @@ -32,7 +33,8 @@ const ViewOneDatasetPage = ({ downloadDataUrl, downloadMetaDataUrl, columnPagination, - rowPagination + rowPagination, + queryKey }: ViewOneDatasetPageProps) => { const { t } = useTranslation('common'); const navigate = useNavigate(); @@ -205,7 +207,7 @@ const ViewOneDatasetPage = ({ totalNumberOfItems={dataset.totalNumberOfColumns} /> - + { const dataQueryUrl = `/v1/datasets/${datasetId}`; return queryOptions({ @@ -28,9 +29,7 @@ const getViewDatasetQueryOptions = ( }); return $TabularDataset.parse(response.data); }, - queryKey: [ - `dataset-query-${datasetId}-colPage-${columnPagination.currentPage}-colItems-${columnPagination.itemsPerPage}-rowPage-${rowPagination.currentPage}-rowItems-${rowPagination.itemsPerPage}` - ] + queryKey: [queryKey] }); }; @@ -38,7 +37,13 @@ export const Route = createFileRoute('/portal/datasets/$datasetId')({ validateSearch: zodValidator($ViewOneDatasetPageSearchParams), loaderDeps: ({ search: { columnPagination, rowPagination } }) => ({ columnPagination, rowPagination }), loader: async ({ deps: { columnPagination, rowPagination }, params }) => { - const viewOneDatasetOptions = getViewDatasetQueryOptions(params.datasetId, columnPagination, rowPagination); + const queryKey = `dataset-query-${params.datasetId}-colPage-${columnPagination.currentPage}-colItems-${columnPagination.itemsPerPage}-rowPage-${rowPagination.currentPage}-rowItems-${rowPagination.itemsPerPage}`; + const viewOneDatasetOptions = getViewDatasetQueryOptions( + params.datasetId, + columnPagination, + rowPagination, + queryKey + ); await queryClient.ensureQueryData(viewOneDatasetOptions); }, component: () => { @@ -46,7 +51,13 @@ export const Route = createFileRoute('/portal/datasets/$datasetId')({ const params = useParams({ from: '/portal/datasets/$datasetId' }); const downloadDataUrl = `/v1/datasets/download-data/`; const downloadMetaDataUrl = `/v1/datasets/download-metadata/`; - const viewOneDatasetOptions = getViewDatasetQueryOptions(params.datasetId, columnPagination, rowPagination); + const queryKey = `dataset-query-${params.datasetId}-colPage-${columnPagination.currentPage}-colItems-${columnPagination.itemsPerPage}-rowPage-${rowPagination.currentPage}-rowItems-${rowPagination.itemsPerPage}`; + const viewOneDatasetOptions = getViewDatasetQueryOptions( + params.datasetId, + columnPagination, + rowPagination, + queryKey + ); const datasetQuery = useSuspenseQuery(viewOneDatasetOptions); const dataset = datasetQuery.data; @@ -57,6 +68,7 @@ export const Route = createFileRoute('/portal/datasets/$datasetId')({ dataset={dataset} downloadDataUrl={downloadDataUrl} downloadMetaDataUrl={downloadMetaDataUrl} + queryKey={queryKey} rowPagination={rowPagination} /> ); From fc261e4695ebe2b2dac563d0861140d7187ba7ae Mon Sep 17 00:00:00 2001 From: weijie Date: Thu, 6 Nov 2025 13:53:41 -0500 Subject: [PATCH 31/31] refactor: create the get query key function to avoid duplication --- web/src/routes/portal/datasets/$datasetId.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/web/src/routes/portal/datasets/$datasetId.tsx b/web/src/routes/portal/datasets/$datasetId.tsx index a276a579..fe40b743 100644 --- a/web/src/routes/portal/datasets/$datasetId.tsx +++ b/web/src/routes/portal/datasets/$datasetId.tsx @@ -14,6 +14,13 @@ const $ViewOneDatasetPageSearchParams = z.object({ rowPagination: $DatasetViewPagination.default({ currentPage: 1, itemsPerPage: 10 }) }); +const getDatasetQueryKey = ( + datasetId: string, + columnPagination: $DatasetViewPagination, + rowPagination: $DatasetViewPagination +) => + `dataset-query-${datasetId}-colPage-${columnPagination.currentPage}-colItems-${columnPagination.itemsPerPage}-rowPage-${rowPagination.currentPage}-rowItems-${rowPagination.itemsPerPage}`; + const getViewDatasetQueryOptions = ( datasetId: string, columnPagination: $DatasetViewPagination, @@ -37,12 +44,11 @@ export const Route = createFileRoute('/portal/datasets/$datasetId')({ validateSearch: zodValidator($ViewOneDatasetPageSearchParams), loaderDeps: ({ search: { columnPagination, rowPagination } }) => ({ columnPagination, rowPagination }), loader: async ({ deps: { columnPagination, rowPagination }, params }) => { - const queryKey = `dataset-query-${params.datasetId}-colPage-${columnPagination.currentPage}-colItems-${columnPagination.itemsPerPage}-rowPage-${rowPagination.currentPage}-rowItems-${rowPagination.itemsPerPage}`; const viewOneDatasetOptions = getViewDatasetQueryOptions( params.datasetId, columnPagination, rowPagination, - queryKey + getDatasetQueryKey(params.datasetId, columnPagination, rowPagination) ); await queryClient.ensureQueryData(viewOneDatasetOptions); }, @@ -51,7 +57,8 @@ export const Route = createFileRoute('/portal/datasets/$datasetId')({ const params = useParams({ from: '/portal/datasets/$datasetId' }); const downloadDataUrl = `/v1/datasets/download-data/`; const downloadMetaDataUrl = `/v1/datasets/download-metadata/`; - const queryKey = `dataset-query-${params.datasetId}-colPage-${columnPagination.currentPage}-colItems-${columnPagination.itemsPerPage}-rowPage-${rowPagination.currentPage}-rowItems-${rowPagination.itemsPerPage}`; + const queryKey = getDatasetQueryKey(params.datasetId, columnPagination, rowPagination); + const viewOneDatasetOptions = getViewDatasetQueryOptions( params.datasetId, columnPagination,