From 580ef7c34c18c3f87fc397ec0ffc0c004fb6a7b2 Mon Sep 17 00:00:00 2001 From: Alec M Date: Fri, 11 Oct 2024 13:59:30 -0400 Subject: [PATCH 01/17] CRDCDH-1779 Dynamic disable of Data Commons --- .env.example | 3 ++ conf/inject.template.js | 1 + public/js/injectEnv.js | 1 + src/config/DataCommons.ts | 9 +++++- src/types/AppEnv.d.ts | 7 +++++ src/utils/envUtils.test.ts | 64 ++++++++++++++++++++++++++++++++++++++ src/utils/envUtils.ts | 25 +++++++++++++++ 7 files changed, 109 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 287e5b464..f2997a762 100644 --- a/.env.example +++ b/.env.example @@ -13,6 +13,9 @@ REACT_APP_UPLOADER_CLI_WINDOWS="" REACT_APP_UPLOADER_CLI_MAC_X64="" REACT_APP_UPLOADER_CLI_MAC_ARM="" +# Data Common Config +REACT_APP_HIDDEN_MODELS="" + # Optional - Frontend Build/Version REACT_APP_FE_VERSION="" diff --git a/conf/inject.template.js b/conf/inject.template.js index 706bad144..e8abb3521 100644 --- a/conf/inject.template.js +++ b/conf/inject.template.js @@ -11,4 +11,5 @@ window.injectedEnv = { REACT_APP_UPLOADER_CLI_WINDOWS: "${REACT_APP_UPLOADER_CLI_WINDOWS}", REACT_APP_UPLOADER_CLI_MAC_X64: "${REACT_APP_UPLOADER_CLI_MAC_X64}", REACT_APP_UPLOADER_CLI_MAC_ARM: "${REACT_APP_UPLOADER_CLI_MAC_ARM}", + REACT_APP_HIDDEN_MODELS: "${HIDDEN_MODELS}", }; diff --git a/public/js/injectEnv.js b/public/js/injectEnv.js index e6a962cd4..34eac760b 100644 --- a/public/js/injectEnv.js +++ b/public/js/injectEnv.js @@ -10,4 +10,5 @@ window.injectedEnv = { REACT_APP_GA_TRACKING_ID: "", REACT_APP_FE_VERSION: "", REACT_APP_BACKEND_API: "", + REACT_APP_HIDDEN_MODELS: "", }; diff --git a/src/config/DataCommons.ts b/src/config/DataCommons.ts index b64993237..06d533187 100644 --- a/src/config/DataCommons.ts +++ b/src/config/DataCommons.ts @@ -1,6 +1,7 @@ import logo from "../assets/modelNavigator/Logo.jpg"; import CDSLogo from "../assets/modelNavigator/CDS_Logo.png"; import GenericLogo from "../assets/modelNavigator/Generic_Logo.png"; +import { getFilteredDataCommons } from "../utils/envUtils"; /** * The URL of the Data Commons Model Repo @@ -16,7 +17,7 @@ export const MODEL_FILE_REPO = "https://raw.githubusercontent.com/CBIIT/crdc-dat /** * A collection of site-wide supported Data Commons. */ -export const DataCommons: DataCommon[] = [ +const DataCommons: DataCommon[] = [ { name: "CCDI", assets: null, @@ -441,3 +442,9 @@ export const DataCommons: DataCommon[] = [ }, }, ]; + +// TODO: This is a TEMPORARY implementation to hide Data Commons from the UI +// for 3.1.0 only. This will be refactored in 3.2.0 +const HiddenModels = getFilteredDataCommons(); +const FilteredDataCommons = DataCommons.filter((dc) => !HiddenModels.includes(dc.name)); +export { FilteredDataCommons as DataCommons }; diff --git a/src/types/AppEnv.d.ts b/src/types/AppEnv.d.ts index 7187c19c9..36fb55c02 100644 --- a/src/types/AppEnv.d.ts +++ b/src/types/AppEnv.d.ts @@ -60,6 +60,13 @@ type AppEnv = { * @example "mvp-2.213" */ REACT_APP_FE_VERSION: string; + /** + * A CSV string of Data Commons to hide from the UI + * + * @note Can be a string of 0 or more Data Commons + * @since 3.1.0 + */ + REACT_APP_HIDDEN_MODELS: string; /** * The deployment environment the app is running in */ diff --git a/src/utils/envUtils.test.ts b/src/utils/envUtils.test.ts index 1c586eed8..2a2111420 100644 --- a/src/utils/envUtils.test.ts +++ b/src/utils/envUtils.test.ts @@ -136,3 +136,67 @@ describe("buildReleaseNotesUrl cases", () => { ); }); }); + +describe("getFilteredDataCommons cases", () => { + const originalEnv = process.env; + + beforeEach(() => { + jest.resetModules(); + + // Reset the environment variables back to their original values + process.env = { ...originalEnv }; + }); + + afterAll(() => { + jest.restoreAllMocks(); + }); + + it("should return an empty array when REACT_APP_HIDDEN_MODELS is not set", async () => { + delete process.env.REACT_APP_HIDDEN_MODELS; + + const { getFilteredDataCommons } = await import("./envUtils"); + expect(getFilteredDataCommons()).toEqual([]); + }); + + it("should return an empty array when REACT_APP_HIDDEN_MODELS is not a string", async () => { + process.env.REACT_APP_HIDDEN_MODELS = 0 as unknown as string; // NOTE: Officially only be strings + + const { getFilteredDataCommons } = await import("./envUtils"); + expect(getFilteredDataCommons()).toEqual([]); + }); + + it("should return an empty array when REACT_APP_HIDDEN_MODELS is an empty string", async () => { + process.env.REACT_APP_HIDDEN_MODELS = ""; + + const { getFilteredDataCommons } = await import("./envUtils"); + expect(getFilteredDataCommons()).toEqual([]); + }); + + it("should return an empty array when REACT_APP_HIDDEN_MODELS is an CSV of nothing", async () => { + process.env.REACT_APP_HIDDEN_MODELS = ",,,"; + + const { getFilteredDataCommons } = await import("./envUtils"); + expect(getFilteredDataCommons()).toEqual([]); + }); + + it("should return an array of hidden Data Commons when REACT_APP_HIDDEN_MODELS is set", async () => { + process.env.REACT_APP_HIDDEN_MODELS = "dc1,dc2,dc3"; + + const { getFilteredDataCommons } = await import("./envUtils"); + expect(getFilteredDataCommons()).toEqual(["dc1", "dc2", "dc3"]); + }); + + it("should return an array of 1 when REACT_APP_HIDDEN_MODELS is set to a single Data Commons", async () => { + process.env.REACT_APP_HIDDEN_MODELS = "dc1"; + + const { getFilteredDataCommons } = await import("./envUtils"); + expect(getFilteredDataCommons()).toEqual(["dc1"]); + }); + + it("should filter out empty Data Commons from the list", async () => { + process.env.REACT_APP_HIDDEN_MODELS = "dc1,,dc3"; + + const { getFilteredDataCommons } = await import("./envUtils"); + expect(getFilteredDataCommons()).toEqual(["dc1", "dc3"]); + }); +}); diff --git a/src/utils/envUtils.ts b/src/utils/envUtils.ts index c34cf5748..13a8c5980 100644 --- a/src/utils/envUtils.ts +++ b/src/utils/envUtils.ts @@ -61,3 +61,28 @@ export const buildReleaseNotesUrl = (): string => { return "https://raw.githubusercontent.com/CBIIT/crdc-datahub-ui/refs/heads/main/CHANGELOG.md"; }; + +/** + * A utility to safely get the hidden Data Commons from the environment variable. + * If it is not set, return an empty array. + * + * @returns An array of hidden Data Commons or an empty array if not set. + */ +export const getFilteredDataCommons = (): string[] => { + const { REACT_APP_HIDDEN_MODELS } = env || {}; + + if (!REACT_APP_HIDDEN_MODELS || typeof REACT_APP_HIDDEN_MODELS !== "string") { + Logger.error("getFilteredDataCommons: REACT_APP_HIDDEN_MODELS is not set or is not a string"); + return []; + } + + const mapped = REACT_APP_HIDDEN_MODELS.split(",") + .map((dc) => dc?.trim()) + .filter((dc) => dc?.length); + + if (!mapped?.length) { + return []; + } + + return mapped; +}; From 1ef11433e79422053d0b6727991e8745c4fbd31d Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Fri, 11 Oct 2024 16:42:07 -0400 Subject: [PATCH 02/17] Update submit button ruleset --- src/config/SubmitButtonConfig.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/config/SubmitButtonConfig.ts b/src/config/SubmitButtonConfig.ts index 0559addc8..623339027 100644 --- a/src/config/SubmitButtonConfig.ts +++ b/src/config/SubmitButtonConfig.ts @@ -67,7 +67,7 @@ export const SUBMIT_BUTTON_CONDITIONS: SubmitButtonCondition[] = [ preConditionCheck: (s) => s.dataType === "Metadata Only", check: (s) => !!s.metadataValidationStatus, tooltip: TOOLTIP_TEXT.SUBMISSION_ACTIONS.SUBMIT.DISABLED.NEW_DATA_OR_VALIDATION_ERRORS, - required: false, + required: true, }, { _identifier: @@ -75,7 +75,7 @@ export const SUBMIT_BUTTON_CONDITIONS: SubmitButtonCondition[] = [ preConditionCheck: (s) => s.dataType === "Metadata and Data Files", check: (s) => !!s.fileValidationStatus, tooltip: TOOLTIP_TEXT.SUBMISSION_ACTIONS.SUBMIT.DISABLED.MISSING_DATA_FILE, - required: false, + required: true, }, { _identifier: @@ -83,7 +83,7 @@ export const SUBMIT_BUTTON_CONDITIONS: SubmitButtonCondition[] = [ preConditionCheck: (s) => s.dataType === "Metadata and Data Files", check: (s) => !!s.metadataValidationStatus, tooltip: TOOLTIP_TEXT.SUBMISSION_ACTIONS.SUBMIT.DISABLED.NEW_DATA_OR_VALIDATION_ERRORS, - required: false, + required: true, }, { _identifier: "There should be no validation errors for metadata or data files", @@ -104,13 +104,4 @@ export const ADMIN_OVERRIDE_CONDITIONS: AdminOverrideCondition[] = [ check: (s) => s.metadataValidationStatus === "Error" || s.fileValidationStatus === "Error", tooltip: undefined, }, - { - _identifier: - "Admin Override - Submission is missing either metadata or data files for 'Metadata and Data Files' submissions", - check: (s) => - (!!s.metadataValidationStatus && !s.fileValidationStatus) || - (!s.metadataValidationStatus && !!s.fileValidationStatus), - preConditionCheck: (s) => s.dataType === "Metadata and Data Files", - tooltip: undefined, - }, ]; From c6d71ad995d902e557e83b60f3c89e5ed79fe893 Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Fri, 11 Oct 2024 16:42:45 -0400 Subject: [PATCH 03/17] Update tests to reflect changes --- src/utils/dataSubmissionUtils.test.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/utils/dataSubmissionUtils.test.ts b/src/utils/dataSubmissionUtils.test.ts index 919b5bda0..5cbfc558f 100644 --- a/src/utils/dataSubmissionUtils.test.ts +++ b/src/utils/dataSubmissionUtils.test.ts @@ -349,15 +349,15 @@ describe("Admin Submit", () => { expect(result.isAdminOverride).toBe(false); }); - it("should allow admin override when metadata is passed but file validation is null", () => { + it("should not allow admin override when metadata is passed but file validation is null", () => { const submission: Submission = { ...baseSubmission, metadataValidationStatus: "Passed", fileValidationStatus: null, }; const result = utils.shouldEnableSubmit(submission, "Admin"); - expect(result.enabled).toBe(true); - expect(result.isAdminOverride).toBe(true); + expect(result.enabled).toBe(false); + expect(result.isAdminOverride).toBe(false); }); it("should not enable submit when both validations are null", () => { @@ -373,6 +373,7 @@ describe("Admin Submit", () => { it("should allow admin override when file validation is null and intention is 'Delete'", () => { const submission: Submission = { ...baseSubmission, + dataType: "Metadata Only", metadataValidationStatus: "Error", fileValidationStatus: null, intention: "Delete", @@ -437,6 +438,7 @@ describe("Admin Submit", () => { it("should allow submit with isAdminOverride when metadata validation is 'Error', file validation is null, and intention is 'Delete'", () => { const submission: Submission = { ...baseSubmission, + dataType: "Metadata Only", metadataValidationStatus: "Error", fileValidationStatus: null, intention: "Delete", @@ -514,14 +516,12 @@ describe("shouldAllowAdminOverride", () => { it("should check condition if preConditionCheck is met", () => { const submission: Submission = { ...baseSubmission, - dataType: "Metadata and Data Files", - metadataValidationStatus: "Passed", + dataType: "Metadata Only", + metadataValidationStatus: "Error", fileValidationStatus: null, }; const result = utils.shouldAllowAdminOverride(submission); - expect(result._identifier).toBe( - "Admin Override - Submission is missing either metadata or data files for 'Metadata and Data Files' submissions" - ); + expect(result._identifier).toBe("Admin Override - Submission has validation errors"); expect(result.enabled).toBe(true); expect(result.isAdminOverride).toBe(true); }); From 3b781710f8fba36a5e2b2372d519a220b94a992b Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Fri, 11 Oct 2024 16:47:30 -0400 Subject: [PATCH 04/17] Update additional rule --- src/config/SubmitButtonConfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/SubmitButtonConfig.ts b/src/config/SubmitButtonConfig.ts index 623339027..9607b6d66 100644 --- a/src/config/SubmitButtonConfig.ts +++ b/src/config/SubmitButtonConfig.ts @@ -60,7 +60,7 @@ export const SUBMIT_BUTTON_CONDITIONS: SubmitButtonCondition[] = [ preConditionCheck: (s) => s.intention === "Delete", check: (s) => !!s.metadataValidationStatus, tooltip: TOOLTIP_TEXT.SUBMISSION_ACTIONS.SUBMIT.DISABLED.NEW_DATA_OR_VALIDATION_ERRORS, - required: false, + required: true, }, { _identifier: "Metadata validation should be initialized for 'Metadata Only' submissions", From 22309c5073c441ae811b421527e70803f3d50632 Mon Sep 17 00:00:00 2001 From: Alec M Date: Fri, 11 Oct 2024 16:50:13 -0400 Subject: [PATCH 05/17] CRDCDH-1750 Dynamic Model Navigator Icon --- .../{modelNavigator => header}/Logo.jpg | Bin src/assets/modelNavigator/CDS_Logo.png | Bin 7798 -> 0 bytes .../{Generic_Logo.png => genericLogo.png} | Bin .../ExportRequestButton/pdf/Generate.ts | 2 +- src/config/DataCommons.ts | 7 +--- src/hooks/useBuildReduxStore.ts | 2 +- src/types/DataCommon.d.ts | 7 ++++ src/types/DataModelNavigator.d.ts | 13 ++++-- src/utils/dataModelUtils.test.ts | 38 ++++++++++++++++++ src/utils/dataModelUtils.ts | 6 +++ 10 files changed, 63 insertions(+), 12 deletions(-) rename src/assets/{modelNavigator => header}/Logo.jpg (100%) delete mode 100644 src/assets/modelNavigator/CDS_Logo.png rename src/assets/modelNavigator/{Generic_Logo.png => genericLogo.png} (100%) diff --git a/src/assets/modelNavigator/Logo.jpg b/src/assets/header/Logo.jpg similarity index 100% rename from src/assets/modelNavigator/Logo.jpg rename to src/assets/header/Logo.jpg diff --git a/src/assets/modelNavigator/CDS_Logo.png b/src/assets/modelNavigator/CDS_Logo.png deleted file mode 100644 index 2612ffb42538419b3f7478a2aa92fa2a63e29742..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7798 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 { props: `${MODEL_FILE_REPO}prod/cache/test-name/1.0/prop-file`, readme: `${MODEL_FILE_REPO}prod/cache/test-name/1.0/readme-file`, loading_file: `${MODEL_FILE_REPO}prod/cache/test-name/1.0/loading-file-zip-name`, + navigator_icon: expect.any(String), }); }); @@ -149,6 +150,43 @@ describe("buildAssetUrls cases", () => { expect(result.readme).toEqual(null); }); + it("should use GenericModelLogo if model-navigator-logo is not provided", () => { + const dc: DataCommon = { + name: "test-name", + assets: { + "current-version": "1.0", + "model-file": "model-file", + "prop-file": "prop-file", + "readme-file": "readme-file", + "loading-file": "loading-file-zip-name", + } as ManifestAssets, + } as DataCommon; + + const result = utils.buildAssetUrls(dc); + + expect(result.navigator_icon).toEqual("genericLogo.png"); + }); + + it("should use model-navigator-logo if provided in the content manifest", () => { + const dc: DataCommon = { + name: "test-name", + assets: { + "current-version": "1.0", + "model-file": "model-file", + "prop-file": "prop-file", + "readme-file": "readme-file", + "loading-file": "loading-file-zip-name", + "model-navigator-logo": "custom-logo.png", + } as ManifestAssets, + } as DataCommon; + + const result = utils.buildAssetUrls(dc); + + expect(result.navigator_icon).toEqual( + `${MODEL_FILE_REPO}prod/cache/test-name/1.0/custom-logo.png` + ); + }); + it("should not throw an exception if dealing with invalid data", () => { expect(() => utils.buildAssetUrls(null)).not.toThrow(); expect(() => utils.buildAssetUrls({} as DataCommon)).not.toThrow(); diff --git a/src/utils/dataModelUtils.ts b/src/utils/dataModelUtils.ts index 59ca7ea2d..0ba51f396 100644 --- a/src/utils/dataModelUtils.ts +++ b/src/utils/dataModelUtils.ts @@ -2,6 +2,7 @@ import { defaultTo } from "lodash"; import { MODEL_FILE_REPO } from "../config/DataCommons"; import env from "../env"; import { RetrieveCDEsResp } from "../graphql"; +import GenericModelLogo from "../assets/modelNavigator/genericLogo.png"; /** * Fetch the tracked Data Model content manifest. @@ -49,6 +50,11 @@ export const buildAssetUrls = (dc: DataCommon): ModelAssetUrls => ({ "current-version" ]}/${dc?.assets?.["loading-file"]}` : null, + navigator_icon: dc?.assets?.["model-navigator-logo"] + ? `${MODEL_FILE_REPO}${env.REACT_APP_DEV_TIER || "prod"}/cache/${dc?.name}/${dc?.assets?.[ + "current-version" + ]}/${dc?.assets?.["model-navigator-logo"]}` + : GenericModelLogo, }); /** From e889d785e972dcb3403229110dcbc79f73a04d38 Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Tue, 15 Oct 2024 13:38:55 -0400 Subject: [PATCH 06/17] Update getSubmission query and types to add collaborators --- src/graphql/getSubmission.ts | 12 ++++++++++++ src/types/Submissions.d.ts | 13 +++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/graphql/getSubmission.ts b/src/graphql/getSubmission.ts index 1559253d3..7bcac1c2f 100644 --- a/src/graphql/getSubmission.ts +++ b/src/graphql/getSubmission.ts @@ -58,6 +58,18 @@ export const query = gql` dataType otherSubmissions nodeCount + collaborators { + collaboratorID + collaboratorName + Organization { + orgID + orgName + status + createdAt + updateAt + } + permission + } createdAt updatedAt } diff --git a/src/types/Submissions.d.ts b/src/types/Submissions.d.ts index 4f6f0e853..950b73153 100644 --- a/src/types/Submissions.d.ts +++ b/src/types/Submissions.d.ts @@ -63,6 +63,10 @@ type Submission = { * The total number of nodes in the Submission */ nodeCount: number; + /** + * A list of additional submitters who can view and/or edit the submission + */ + collaborators: Collaborator[]; createdAt: string; // ISO 8601 date time format with UTC or offset e.g., 2023-05-01T09:23:30Z updatedAt: string; // ISO 8601 date time format with UTC or offset e.g., 2023-05-01T09:23:30Z }; @@ -385,3 +389,12 @@ type SubmitButtonResult = { tooltip?: string; _identifier?: string; }; + +type CollaboratorPermissions = "Can View" | "Can Edit"; + +type Collaborator = { + collaboratorID: string; + collaboratorName: string; + Organization: OrgInfo; + permission: CollaboratorPermissions; +}; From 998235c7c99c79b9b223e02d710f32828915da90 Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Tue, 15 Oct 2024 13:40:27 -0400 Subject: [PATCH 07/17] Add collaborators property to baseSubmission for tests --- src/components/Contexts/SubmissionContext.test.tsx | 1 + src/components/DataSubmissions/CrossValidationButton.test.tsx | 1 + src/components/DataSubmissions/CrossValidationFilters.test.tsx | 1 + src/components/DataSubmissions/DataUpload.test.tsx | 1 + .../DataSubmissions/DeleteAllOrphanFilesButton.test.tsx | 1 + src/components/DataSubmissions/DeleteNodeDataButton.test.tsx | 1 + src/components/DataSubmissions/DeleteOrphanFileChip.test.tsx | 1 + .../DataSubmissions/ExportCrossValidationButton.test.tsx | 1 + src/components/DataSubmissions/ExportValidationButton.test.tsx | 1 + src/components/DataSubmissions/MetadataUpload.test.tsx | 1 + src/components/DataSubmissions/ValidationControls.test.tsx | 1 + src/components/DataSubmissions/ValidationStatistics.test.tsx | 1 + src/components/DataSubmissions/ValidationStatus.test.tsx | 1 + src/content/dataSubmissions/CrossValidation.test.tsx | 1 + src/content/dataSubmissions/DataActivity.test.tsx | 1 + src/content/dataSubmissions/QualityControl.test.tsx | 1 + src/utils/dataSubmissionUtils.test.ts | 1 + 17 files changed, 17 insertions(+) diff --git a/src/components/Contexts/SubmissionContext.test.tsx b/src/components/Contexts/SubmissionContext.test.tsx index c5cec710b..04a84006a 100644 --- a/src/components/Contexts/SubmissionContext.test.tsx +++ b/src/components/Contexts/SubmissionContext.test.tsx @@ -53,6 +53,7 @@ const baseSubmission: Submission = { createdAt: "", updatedAt: "", studyID: "", + collaborators: [], }; const TestChild: FC = () => { diff --git a/src/components/DataSubmissions/CrossValidationButton.test.tsx b/src/components/DataSubmissions/CrossValidationButton.test.tsx index 925e5694f..c7d83cdbd 100644 --- a/src/components/DataSubmissions/CrossValidationButton.test.tsx +++ b/src/components/DataSubmissions/CrossValidationButton.test.tsx @@ -54,6 +54,7 @@ const baseSubmission: Omit< studyID: "", deletingData: false, nodeCount: 0, + collaborators: [], }; const baseAuthCtx: AuthCtxState = { diff --git a/src/components/DataSubmissions/CrossValidationFilters.test.tsx b/src/components/DataSubmissions/CrossValidationFilters.test.tsx index 64829e381..ab98229c5 100644 --- a/src/components/DataSubmissions/CrossValidationFilters.test.tsx +++ b/src/components/DataSubmissions/CrossValidationFilters.test.tsx @@ -52,6 +52,7 @@ const baseSubmission: Submission = { nodeCount: 0, createdAt: "", updatedAt: "", + collaborators: [], }; const baseBatch = { diff --git a/src/components/DataSubmissions/DataUpload.test.tsx b/src/components/DataSubmissions/DataUpload.test.tsx index 4f37c5f49..8b138cfd8 100644 --- a/src/components/DataSubmissions/DataUpload.test.tsx +++ b/src/components/DataSubmissions/DataUpload.test.tsx @@ -56,6 +56,7 @@ const baseSubmission: Omit = { studyID: "", deletingData: false, nodeCount: 0, + collaborators: [], }; const baseUser: User = { diff --git a/src/components/DataSubmissions/DeleteAllOrphanFilesButton.test.tsx b/src/components/DataSubmissions/DeleteAllOrphanFilesButton.test.tsx index 5764481c1..23efe1a79 100644 --- a/src/components/DataSubmissions/DeleteAllOrphanFilesButton.test.tsx +++ b/src/components/DataSubmissions/DeleteAllOrphanFilesButton.test.tsx @@ -54,6 +54,7 @@ const baseSubmission: Submission = { studyID: "", deletingData: false, nodeCount: 0, + collaborators: [], }; const baseContext: ContextState = { diff --git a/src/components/DataSubmissions/DeleteNodeDataButton.test.tsx b/src/components/DataSubmissions/DeleteNodeDataButton.test.tsx index d214ecf44..bb0ec3abc 100644 --- a/src/components/DataSubmissions/DeleteNodeDataButton.test.tsx +++ b/src/components/DataSubmissions/DeleteNodeDataButton.test.tsx @@ -50,6 +50,7 @@ const BaseSubmission: Submission = { validationType: [], deletingData: false, nodeCount: 0, + collaborators: [], }; const baseAuthCtx: AuthContextState = { diff --git a/src/components/DataSubmissions/DeleteOrphanFileChip.test.tsx b/src/components/DataSubmissions/DeleteOrphanFileChip.test.tsx index 8c2f0950d..cceb9073f 100644 --- a/src/components/DataSubmissions/DeleteOrphanFileChip.test.tsx +++ b/src/components/DataSubmissions/DeleteOrphanFileChip.test.tsx @@ -54,6 +54,7 @@ const baseSubmission: Submission = { studyID: "", deletingData: false, nodeCount: 0, + collaborators: [], }; const baseContext: ContextState = { diff --git a/src/components/DataSubmissions/ExportCrossValidationButton.test.tsx b/src/components/DataSubmissions/ExportCrossValidationButton.test.tsx index 9916b4f9c..0d0300ca3 100644 --- a/src/components/DataSubmissions/ExportCrossValidationButton.test.tsx +++ b/src/components/DataSubmissions/ExportCrossValidationButton.test.tsx @@ -49,6 +49,7 @@ const baseSubmission: Submission = { studyID: "", deletingData: false, nodeCount: 0, + collaborators: [], }; const baseCrossValidationResult: CrossValidationResult = { diff --git a/src/components/DataSubmissions/ExportValidationButton.test.tsx b/src/components/DataSubmissions/ExportValidationButton.test.tsx index b846895ab..41bfd704e 100644 --- a/src/components/DataSubmissions/ExportValidationButton.test.tsx +++ b/src/components/DataSubmissions/ExportValidationButton.test.tsx @@ -58,6 +58,7 @@ describe("ExportValidationButton cases", () => { studyID: "", deletingData: false, nodeCount: 0, + collaborators: [], }; const baseQCResult: Omit = { diff --git a/src/components/DataSubmissions/MetadataUpload.test.tsx b/src/components/DataSubmissions/MetadataUpload.test.tsx index a139d5226..08dec6250 100644 --- a/src/components/DataSubmissions/MetadataUpload.test.tsx +++ b/src/components/DataSubmissions/MetadataUpload.test.tsx @@ -41,6 +41,7 @@ const baseSubmission: Omit< studyID: "", deletingData: false, nodeCount: 0, + collaborators: [], }; const baseContext: ContextState = { diff --git a/src/components/DataSubmissions/ValidationControls.test.tsx b/src/components/DataSubmissions/ValidationControls.test.tsx index 3e705c842..8fcd99584 100644 --- a/src/components/DataSubmissions/ValidationControls.test.tsx +++ b/src/components/DataSubmissions/ValidationControls.test.tsx @@ -54,6 +54,7 @@ const baseSubmission: Omit< studyID: "", deletingData: false, nodeCount: 0, + collaborators: [], }; const baseAuthCtx: AuthCtxState = { diff --git a/src/components/DataSubmissions/ValidationStatistics.test.tsx b/src/components/DataSubmissions/ValidationStatistics.test.tsx index 47d32cfc5..6e00c6d5b 100644 --- a/src/components/DataSubmissions/ValidationStatistics.test.tsx +++ b/src/components/DataSubmissions/ValidationStatistics.test.tsx @@ -34,6 +34,7 @@ const baseSubmission: Omit = { studyID: "", deletingData: false, nodeCount: 0, + collaborators: [], }; describe("Accessibility", () => { diff --git a/src/components/DataSubmissions/ValidationStatus.test.tsx b/src/components/DataSubmissions/ValidationStatus.test.tsx index 561a1f56a..4ad941bf5 100644 --- a/src/components/DataSubmissions/ValidationStatus.test.tsx +++ b/src/components/DataSubmissions/ValidationStatus.test.tsx @@ -41,6 +41,7 @@ const BaseSubmission: Omit< studyID: "", deletingData: false, nodeCount: 0, + collaborators: [], }; type TestParentProps = { diff --git a/src/content/dataSubmissions/CrossValidation.test.tsx b/src/content/dataSubmissions/CrossValidation.test.tsx index d39f79b71..5f1d3c011 100644 --- a/src/content/dataSubmissions/CrossValidation.test.tsx +++ b/src/content/dataSubmissions/CrossValidation.test.tsx @@ -58,6 +58,7 @@ const baseSubmission: Submission = { studyID: "", deletingData: false, nodeCount: 0, + collaborators: [], }; const baseCrossValidationResult: CrossValidationResult = { diff --git a/src/content/dataSubmissions/DataActivity.test.tsx b/src/content/dataSubmissions/DataActivity.test.tsx index 5192aa06c..d7b361aed 100644 --- a/src/content/dataSubmissions/DataActivity.test.tsx +++ b/src/content/dataSubmissions/DataActivity.test.tsx @@ -43,6 +43,7 @@ const baseSubmission: Omit = { studyID: "", deletingData: false, nodeCount: 0, + collaborators: [], }; type ParentProps = { diff --git a/src/content/dataSubmissions/QualityControl.test.tsx b/src/content/dataSubmissions/QualityControl.test.tsx index adf4c7f09..d70b2aa4c 100644 --- a/src/content/dataSubmissions/QualityControl.test.tsx +++ b/src/content/dataSubmissions/QualityControl.test.tsx @@ -56,6 +56,7 @@ const baseSubmission: Submission = { studyID: "", deletingData: false, nodeCount: 0, + collaborators: [], }; const baseQCResult: QCResult = { diff --git a/src/utils/dataSubmissionUtils.test.ts b/src/utils/dataSubmissionUtils.test.ts index 5cbfc558f..fc0026541 100644 --- a/src/utils/dataSubmissionUtils.test.ts +++ b/src/utils/dataSubmissionUtils.test.ts @@ -35,6 +35,7 @@ const baseSubmission: Submission = { studyID: "", deletingData: false, nodeCount: 0, + collaborators: [], }; describe("General Submit", () => { From 9a92547bbb4aa0e5caf98a479175461fa0e62ece Mon Sep 17 00:00:00 2001 From: Alec M Date: Tue, 15 Oct 2024 13:53:48 -0400 Subject: [PATCH 08/17] Ensure all cases are covered --- src/utils/dataModelUtils.test.ts | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/utils/dataModelUtils.test.ts b/src/utils/dataModelUtils.test.ts index 0950ebaa8..ba82b64bf 100644 --- a/src/utils/dataModelUtils.test.ts +++ b/src/utils/dataModelUtils.test.ts @@ -150,7 +150,7 @@ describe("buildAssetUrls cases", () => { expect(result.readme).toEqual(null); }); - it("should use GenericModelLogo if model-navigator-logo is not provided", () => { + it("should use GenericModelLogo if model-navigator-logo is not defined", () => { const dc: DataCommon = { name: "test-name", assets: { @@ -159,6 +159,25 @@ describe("buildAssetUrls cases", () => { "prop-file": "prop-file", "readme-file": "readme-file", "loading-file": "loading-file-zip-name", + // "model-navigator-logo" - not defined, aka no logo + } as ManifestAssets, + } as DataCommon; + + const result = utils.buildAssetUrls(dc); + + expect(result.navigator_icon).toEqual("genericLogo.png"); + }); + + it("should use GenericModelLogo if the model-navigator-logo is an empty string", () => { + const dc: DataCommon = { + name: "test-name", + assets: { + "current-version": "1.0", + "model-file": "model-file", + "prop-file": "prop-file", + "readme-file": "readme-file", + "loading-file": "loading-file-zip-name", + "model-navigator-logo": "", // empty string - aka no logo } as ManifestAssets, } as DataCommon; @@ -176,7 +195,7 @@ describe("buildAssetUrls cases", () => { "prop-file": "prop-file", "readme-file": "readme-file", "loading-file": "loading-file-zip-name", - "model-navigator-logo": "custom-logo.png", + "model-navigator-logo": "custom-logo.png", // defined - must exist } as ManifestAssets, } as DataCommon; From 494efea99ee874258ee76db50f7c528bf918e104 Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Tue, 15 Oct 2024 15:07:40 -0400 Subject: [PATCH 09/17] Update spacing for copy icon --- src/components/DataSubmissions/CopyAdornment.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/DataSubmissions/CopyAdornment.tsx b/src/components/DataSubmissions/CopyAdornment.tsx index 6bc7e8b4d..389a7066c 100644 --- a/src/components/DataSubmissions/CopyAdornment.tsx +++ b/src/components/DataSubmissions/CopyAdornment.tsx @@ -68,7 +68,7 @@ const CopyAdornment: FC = ({ _id }) => { }; return ( - + SUBMISSION ID: From 0a7f0add5f697ffa6453648a70307c37122a4e33 Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Tue, 15 Oct 2024 15:08:42 -0400 Subject: [PATCH 10/17] Update Data Submission Summary section to match new styling and structure --- .../DataSubmissions/DataSubmissionSummary.tsx | 150 ++++++++---------- .../SubmissionHeaderProperty.tsx | 25 ++- 2 files changed, 89 insertions(+), 86 deletions(-) diff --git a/src/components/DataSubmissions/DataSubmissionSummary.tsx b/src/components/DataSubmissions/DataSubmissionSummary.tsx index bf0d8a05f..8ad6f6cb6 100644 --- a/src/components/DataSubmissions/DataSubmissionSummary.tsx +++ b/src/components/DataSubmissions/DataSubmissionSummary.tsx @@ -1,29 +1,19 @@ import { Button, Divider, Grid, Stack, Typography, styled } from "@mui/material"; -import React, { FC, useEffect, useMemo, useRef, useState } from "react"; +import React, { FC, useMemo, useState } from "react"; import { isEqual } from "lodash"; import SubmissionHeaderProperty, { StyledValue } from "./SubmissionHeaderProperty"; -import Tooltip from "./Tooltip"; import { ReactComponent as EmailIconSvg } from "../../assets/icons/email_icon.svg"; import HistoryDialog from "../HistoryDialog"; import DataSubmissionIconMap from "./DataSubmissionIconMap"; import ReviewCommentsDialog from "../Shared/ReviewCommentsDialog"; import { SortHistory } from "../../utils"; +import TruncatedText from "../TruncatedText"; +import StyledTooltip from "../StyledFormComponents/StyledTooltip"; const StyledSummaryWrapper = styled("div")(() => ({ borderRadius: "8px 8px 0px 0px", textWrap: "nowrap", - padding: "21px 21px 31px 48px", -})); - -const StyledSubmissionTitle = styled(Typography)(() => ({ - color: "#187A90", - fontFamily: "'Nunito Sans', 'Rubik', sans-serif", - fontSize: "13px", - fontStyle: "normal", - fontWeight: 600, - lineHeight: "26px", - letterSpacing: "0.5px", - textTransform: "uppercase", + padding: "25px 119px 25px 51px", })); const StyledSubmissionStatus = styled(Typography)(() => ({ @@ -32,22 +22,22 @@ const StyledSubmissionStatus = styled(Typography)(() => ({ fontSize: "35px", fontStyle: "normal", fontWeight: 900, - lineHeight: "30px", - minHeight: "30px", + lineHeight: "40px", + minHeight: "40px", })); const StyledButtonWrapper = styled(Stack)(() => ({ flexDirection: "column", justifyContent: "flex-start", - alignItems: "flex-start", - gap: "9px", - paddingLeft: "5px", + alignItems: "center", + gap: "10px", + width: "100%", })); const StyledReviewCommentsButton = styled(Button)(() => ({ "&.MuiButton-root": { minWidth: "168px", - marginTop: "16px", + marginTop: "10px", padding: "11px 10px", border: "1px solid #B3B3B3", color: "#BE4511", @@ -97,41 +87,39 @@ const StyledSectionDivider = styled(Divider)(() => ({ display: "flex", alignSelf: "flex-start", width: "2px", - height: "159px", + height: "157px", background: "#6CACDA", - marginLeft: "44px", - marginTop: "9px", + marginLeft: "48px", + marginRight: "81px", }, })); -const StyledSubmitterName = styled(StyledValue)(() => ({ - whiteSpace: "nowrap", - overflow: "hidden", - textOverflow: "ellipsis", - maxWidth: "100%", - lineHeight: "19.6px", - flexShrink: 1, -})); - const StyledConciergeName = styled(StyledValue)(() => ({ maxWidth: "100%", lineHeight: "19.6px", flexShrink: 1, })); -const StyledTooltipSubmitterName = styled(StyledValue)(() => ({ - color: "#595959", - fontFamily: "'Nunito'", +const StyledCollaboratorsButton = styled(Button)({ + justifyContent: "flex-start", + padding: 0, + margin: 0, + color: "#0B7F99", + fontFamily: "'Nunito', 'Rubik', sans-serif", fontSize: "16px", fontStyle: "normal", - fontWeight: 400, + fontWeight: 700, lineHeight: "19.6px", - marginTop: "6px", -})); + minWidth: 0, + textDecoration: "underline", + "&:hover": { + textDecoration: "underline", + }, +}); const StyledGridContainer = styled(Grid)(({ theme }) => ({ "&.MuiGrid-container": { - marginLeft: "45px", + marginLeft: "0px", width: "100%", overflow: "hidden", }, @@ -145,6 +133,11 @@ const StyledGridContainer = styled(Grid)(({ theme }) => ({ }, })); +const StyledEmailWrapper = styled("a")({ + marginLeft: "22px !important", + lineHeight: "19.6px", +}); + type Props = { dataSubmission: Submission; }; @@ -152,7 +145,8 @@ type Props = { const DataSubmissionSummary: FC = ({ dataSubmission }) => { const [historyDialogOpen, setHistoryDialogOpen] = useState(false); const [reviewCommentsDialogOpen, setReviewCommentsDialogOpen] = useState(false); - const [hasEllipsis, setHasEllipsis] = useState(false); + + const numCollaborators = dataSubmission?.collaborators?.length || 0; const lastReview = useMemo( () => SortHistory(dataSubmission?.history).find( @@ -160,20 +154,6 @@ const DataSubmissionSummary: FC = ({ dataSubmission }) => { ), [dataSubmission] ); - const textRef = useRef(null); - - useEffect(() => { - const checkEllipsis = () => { - if (textRef.current) { - setHasEllipsis(textRef.current.offsetWidth < textRef.current.scrollWidth); - } - }; - - checkEllipsis(); - - window.addEventListener("resize", checkEllipsis); - return () => window.removeEventListener("resize", checkEllipsis); - }, [dataSubmission?.name]); const handleOnHistoryDialogOpen = () => { setHistoryDialogOpen(true); @@ -209,11 +189,8 @@ const DataSubmissionSummary: FC = ({ dataSubmission }) => { return ( - - - - SUBMISSION TYPE: {dataSubmission?.intention} - + + {dataSubmission?.status} @@ -242,31 +219,32 @@ const DataSubmissionSummary: FC = ({ dataSubmission }) => { container flexDirection={{ xs: "column", lg: "row" }} rowSpacing={2} - columnSpacing={{ xs: 0, lg: 8.25 }} + columnSpacing={{ xs: 0 }} > + + + - {hasEllipsis ? ( - - {dataSubmission?.name} - - } - disableHoverListener - > - {dataSubmission?.name} - - ) : ( - {dataSubmission?.name} - )} - + + + {Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format( + numCollaborators + )} + + } /> - = ({ dataSubmission }) => { label="Primary Contact" value={ - {dataSubmission?.conciergeName} + + + {dataSubmission?.conciergeName && dataSubmission?.conciergeEmail && ( - - + )} } diff --git a/src/components/DataSubmissions/SubmissionHeaderProperty.tsx b/src/components/DataSubmissions/SubmissionHeaderProperty.tsx index 930674698..649b2776e 100644 --- a/src/components/DataSubmissions/SubmissionHeaderProperty.tsx +++ b/src/components/DataSubmissions/SubmissionHeaderProperty.tsx @@ -1,5 +1,6 @@ import { Box, Grid, Stack, Typography, styled } from "@mui/material"; -import { FC } from "react"; +import { memo } from "react"; +import TruncatedText from "../TruncatedText"; const StyledLabel = styled(Typography)(() => ({ color: "#000000", @@ -24,17 +25,33 @@ export const StyledValue = styled(Typography)(() => ({ type Props = { label: string; value: string | JSX.Element; + truncateAfter?: number | false; }; -const SubmissionHeaderProperty: FC = ({ label, value }) => ( +const SubmissionHeaderProperty = ({ label, value, truncateAfter = 10 }: Props) => ( {label} - {typeof value === "string" ? {value} : value} + {typeof value === "string" ? ( + + {truncateAfter && truncateAfter > 0 ? ( + + ) : ( + value + )} + + ) : ( + value + )} ); -export default SubmissionHeaderProperty; +export default memo(SubmissionHeaderProperty); From 51d10ec04ab86bded8c76377f3cb696495102b8c Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Tue, 15 Oct 2024 15:39:39 -0400 Subject: [PATCH 11/17] Update tooltip to support tests, and updated tests --- .../DataSubmissionSummary.test.tsx | 320 ++++++++++++++++-- .../DataSubmissions/DataSubmissionSummary.tsx | 20 +- 2 files changed, 304 insertions(+), 36 deletions(-) diff --git a/src/components/DataSubmissions/DataSubmissionSummary.test.tsx b/src/components/DataSubmissions/DataSubmissionSummary.test.tsx index 8c0684d5b..bbb5b4b70 100644 --- a/src/components/DataSubmissions/DataSubmissionSummary.test.tsx +++ b/src/components/DataSubmissions/DataSubmissionSummary.test.tsx @@ -1,12 +1,14 @@ -import { render, fireEvent, waitFor } from "@testing-library/react"; +import React, { FC, useMemo } from "react"; +import userEvent from "@testing-library/user-event"; +import { render, waitFor } from "@testing-library/react"; +import { isEqual } from "lodash"; import { BrowserRouter } from "react-router-dom"; -import { FC, useMemo } from "react"; import { axe } from "jest-axe"; import DataSubmissionSummary from "./DataSubmissionSummary"; import HistoryIconMap from "./DataSubmissionIconMap"; type Props = { - dataSubmission: object; + dataSubmission: RecursivePartial; }; const BaseComponent: FC = ({ dataSubmission = {} }: Props) => { @@ -26,10 +28,13 @@ const BaseComponent: FC = ({ dataSubmission = {} }: Props) => { describe("DataSubmissionSummary Accessibility Tests", () => { it("has no accessibility violations when there are review comments", async () => { - const dataSubmission = { + const dataSubmission: RecursivePartial = { history: [ { reviewComment: "This is a review comment", + status: "New", + dateTime: "", + userID: "", }, ], }; @@ -40,12 +45,256 @@ describe("DataSubmissionSummary Accessibility Tests", () => { }); }); +describe("Basic Functionality", () => { + it("renders all property labels and corresponding values", () => { + const dataSubmission: RecursivePartial = { + name: "Test Submission", + intention: "Test Intention" as SubmissionIntention, + submitterName: "Submitter Test", + collaborators: [ + { + collaboratorID: "col-1", + collaboratorName: "", + Organization: { + orgID: "", + orgName: "", + status: "Active", + createdAt: "", + updateAt: "", + }, + permission: "Can View", + }, + { + collaboratorID: "col-2", + collaboratorName: "", + Organization: { + orgID: "", + orgName: "", + status: "Active", + createdAt: "", + updateAt: "", + }, + permission: "Can View", + }, + ], + studyAbbreviation: "AAAAAAAAAAA", + dataCommons: "Test Commons", + organization: { + _id: "", + name: "Test Organization", + }, + conciergeName: "Test Concierge", + conciergeEmail: "concierge@test.com", + }; + + const { getByText, getByLabelText } = render(); + + // Check labels + expect(getByText("Submission Name")).toBeVisible(); + expect(getByText("Submission Type")).toBeVisible(); + expect(getByText("Submitter")).toBeVisible(); + expect(getByText("Collaborators")).toBeVisible(); + expect(getByText("Study")).toBeVisible(); + expect(getByText("Data Commons")).toBeVisible(); + expect(getByText("Organization")).toBeVisible(); + expect(getByText("Primary Contact")).toBeVisible(); + + // Check values + expect(getByText("Test Submi...")).toBeVisible(); + expect(getByText("Test Intention")).toBeVisible(); // Not truncated + expect(getByText("Submitter...")).toBeVisible(); + expect(getByText("AAAAAAAAAA...")).toBeVisible(); + expect(getByText("Test Commo...")).toBeVisible(); + expect(getByText("Test Organ...")).toBeVisible(); + expect(getByText("Test Conci...")).toBeVisible(); + + expect(getByText("2")).toBeVisible(); + + const emailLink = getByLabelText("Email Primary Contact"); + expect(emailLink).toBeVisible(); + expect(emailLink).toHaveAttribute("href", "mailto:concierge@test.com"); + }); + + it("renders the Collaborators property with correct number and tooltip", async () => { + const dataSubmission: RecursivePartial = { + collaborators: [ + { + collaboratorID: "1", + collaboratorName: "", + Organization: { + orgID: "", + orgName: "", + status: "Active", + createdAt: "", + updateAt: "", + }, + permission: "Can View", + }, + { + collaboratorID: "2", + collaboratorName: "", + Organization: { + orgID: "", + orgName: "", + status: "Active", + createdAt: "", + updateAt: "", + }, + permission: "Can View", + }, + { + collaboratorID: "3", + collaboratorName: "", + Organization: { + orgID: "", + orgName: "", + status: "Active", + createdAt: "", + updateAt: "", + }, + permission: "Can View", + }, + ], + }; + + const { getByTestId } = render(); + + // Hover to trigger the tooltip + userEvent.hover(getByTestId("collaborators-button")); + + await waitFor(() => { + expect(getByTestId("collaborators-button-tooltip")).toBeVisible(); + expect(getByTestId("collaborators-button-tooltip")).toHaveTextContent( + "Click to add new collaborators or view existing ones." + ); + }); + }); + + it("renders the Primary Contact with name and email link when email is provided", () => { + const dataSubmission: RecursivePartial = { + conciergeName: "Test Concierge", + conciergeEmail: "concierge@test.com", + }; + + const { getByText, getByLabelText } = render(); + + expect(getByText("Primary Contact")).toBeVisible(); + expect(getByText("Test Conci...")).toBeVisible(); + + const emailLink = getByLabelText("Email Primary Contact"); + expect(emailLink).toBeVisible(); + expect(emailLink).toHaveAttribute("href", "mailto:concierge@test.com"); + }); + + it("renders the Primary Contact with name only when email is not provided", () => { + const dataSubmission: RecursivePartial = { + conciergeName: "Test Concierge", + conciergeEmail: null, + }; + + const { getByText, queryByLabelText } = render( + + ); + + expect(getByText("Primary Contact")).toBeVisible(); + expect(getByText("Test Conci...")).toBeVisible(); + + const emailLink = queryByLabelText("Email Primary Contact"); + expect(emailLink).toBeNull(); + }); +}); + +describe("DataSubmissionSummary Memoization Tests", () => { + it("does not re-render when props are equal due to memoization", () => { + const dataSubmission: RecursivePartial = { + name: "Test Submission", + }; + + const renderSpy = jest.fn(); + + // Create a wrapper component that increments the renderSpy + const MemoizedComponent = ({ dataSubmission }: Props) => { + React.useEffect(() => { + renderSpy(); + }); + return ; + }; + + // Wrap the MemoizedComponent with React.memo and custom comparison + const MemoizedComponentWithMemo = React.memo(MemoizedComponent, (prevProps, nextProps) => + isEqual(prevProps, nextProps) + ); + + const { rerender } = render( + + + + ); + + expect(renderSpy).toHaveBeenCalledTimes(1); + + // Re-render with the same props + rerender( + + + + ); + + // renderSpy should not have been called again + expect(renderSpy).toHaveBeenCalledTimes(1); + }); + + it("re-renders when props change due to memoization", () => { + const dataSubmission: RecursivePartial = { + name: "Test Submission", + }; + + const newDataSubmission: RecursivePartial = { + name: "Updated Submission", + }; + + const renderSpy = jest.fn(); + + const MemoizedComponent = ({ dataSubmission }: Props) => { + React.useEffect(() => { + renderSpy(); + }); + return ; + }; + + const MemoizedComponentWithMemo = React.memo(MemoizedComponent, (prevProps, nextProps) => + isEqual(prevProps, nextProps) + ); + + const { rerender } = render( + + + + ); + + expect(renderSpy).toHaveBeenCalledTimes(1); + + // Re-render with different props + rerender( + + + + ); + + // renderSpy should have been called again + expect(renderSpy).toHaveBeenCalledTimes(2); + }); +}); + describe("DataSubmissionSummary Review Comments Dialog Tests", () => { it("renders the Review Comments button if there is a review comment", () => { - const dataSubmission = { + const dataSubmission: RecursivePartial = { history: [ { reviewComment: "This is a review comment", + status: "New", + dateTime: "", + userID: "", }, ], }; @@ -55,19 +304,20 @@ describe("DataSubmissionSummary Review Comments Dialog Tests", () => { }); it("shows the correct content in the Review Comments dialog", async () => { - const dataSubmission = { + const dataSubmission: RecursivePartial = { history: [ { status: "Rejected", reviewComment: "This is the most recent review comment", dateTime: "2023-11-30T11:26:01Z", + userID: "", }, ], }; const { getByText } = render(); - fireEvent.click(getByText("Review Comments")); + userEvent.click(getByText("Review Comments")); await waitFor(() => { expect(getByText(/This is the most recent review comment/)).toBeVisible(); @@ -75,24 +325,26 @@ describe("DataSubmissionSummary Review Comments Dialog Tests", () => { }); it("only shows the review comment for the latest 'Rejected' submission, ignoring other statuses", async () => { - const dataSubmission = { + const dataSubmission: RecursivePartial = { history: [ { status: "Rejected", reviewComment: "This is a rejected comment", dateTime: "2023-11-29T11:26:01Z", + userID: "", }, { status: "Submitted", reviewComment: "Admin Submit - This should not be displayed", dateTime: "2023-11-30T11:26:01Z", + userID: "", }, ], }; const { getByText } = render(); - fireEvent.click(getByText("Review Comments")); + userEvent.click(getByText("Review Comments")); await waitFor(() => { expect(getByText(/This is a rejected comment/)).toBeVisible(); @@ -101,34 +353,36 @@ describe("DataSubmissionSummary Review Comments Dialog Tests", () => { }); it("closes the Review Comments dialog with the close button", async () => { - const dataSubmission = { + const dataSubmission: RecursivePartial = { history: [ { status: "Rejected", reviewComment: "Comment for closing test", dateTime: "2023-11-30T11:26:01Z", + userID: "", }, ], }; const { getByText, queryByText } = render(); - fireEvent.click(getByText("Review Comments")); + userEvent.click(getByText("Review Comments")); await waitFor(() => expect(getByText("Comment for closing test")).toBeVisible()); - fireEvent.click(getByText("Close")); + userEvent.click(getByText("Close")); await waitFor(() => expect(queryByText("Comment for closing test")).not.toBeInTheDocument()); }); it("closes the Review Comments dialog with the close icon button", async () => { - const dataSubmission = { + const dataSubmission: RecursivePartial = { history: [ { status: "Rejected", reviewComment: "Another comment for close icon test", dateTime: "2023-11-30T11:26:01Z", + userID: "", }, ], }; @@ -137,12 +391,12 @@ describe("DataSubmissionSummary Review Comments Dialog Tests", () => { ); - fireEvent.click(getByText("Review Comments")); + userEvent.click(getByText("Review Comments")); await waitFor(() => expect(getByText("Another comment for close icon test")).toBeVisible()); const closeButton = getByTestId("review-comments-dialog-close-icon-button"); - fireEvent.click(closeButton); + userEvent.click(closeButton); await waitFor(() => expect(queryByText("Another comment for close icon test")).not.toBeInTheDocument() @@ -152,8 +406,14 @@ describe("DataSubmissionSummary Review Comments Dialog Tests", () => { describe("DataSubmissionSummary History Dialog Tests", () => { it("renders the Full History button if there are historical events", () => { - const dataSubmission = { - history: [{ dateTime: "2023-11-23T14:26:01Z" }], + const dataSubmission: RecursivePartial = { + history: [ + { + dateTime: "2023-11-23T14:26:01Z", + status: "New", + userID: "", + }, + ], }; const { getByText } = render(); @@ -170,7 +430,7 @@ describe("DataSubmissionSummary History Dialog Tests", () => { const { getByText } = render(); - fireEvent.click(getByText("Full History")); + userEvent.click(getByText("Full History")); await waitFor(() => { expect(getByText("SUBMITTED")).toBeVisible(); @@ -179,7 +439,7 @@ describe("DataSubmissionSummary History Dialog Tests", () => { }); it("renders the modal and displays history events in descending order", async () => { - const dataSubmission = { + const dataSubmission: RecursivePartial = { history: [ { dateTime: "2023-01-02T10:00:00Z", status: "In Progress" }, { dateTime: "2023-01-01T10:00:00Z", status: "New" }, @@ -194,7 +454,7 @@ describe("DataSubmissionSummary History Dialog Tests", () => { const { getAllByTestId, getByText } = render(); - fireEvent.click(getByText("Full History")); + userEvent.click(getByText("Full History")); const dates = getAllByTestId(/history-item-\d-date/i); const statuses = getAllByTestId(/history-item-\d-status/i); @@ -207,23 +467,23 @@ describe("DataSubmissionSummary History Dialog Tests", () => { }); it("closes the History dialog with the close button", async () => { - const dataSubmission = { + const dataSubmission: RecursivePartial = { history: [{ dateTime: "2023-11-30T11:26:01Z", status: "Submitted" }], }; const { getByText, queryByTestId } = render(); - fireEvent.click(getByText("Full History")); + userEvent.click(getByText("Full History")); await waitFor(() => expect(queryByTestId("history-dialog")).toBeVisible()); - fireEvent.click(queryByTestId("history-dialog-close")); + userEvent.click(queryByTestId("history-dialog-close")); await waitFor(() => expect(queryByTestId("history-dialog")).not.toBeInTheDocument()); }); it("sorts the historical events by date in descending order", async () => { - const dataSubmission = { + const dataSubmission: RecursivePartial = { history: [ { dateTime: "2023-11-20T10:00:00Z", status: "New" }, { dateTime: "2023-11-22T10:00:00Z", status: "In Progress" }, @@ -233,7 +493,7 @@ describe("DataSubmissionSummary History Dialog Tests", () => { const { getByText, getAllByTestId } = render(); - fireEvent.click(getByText("Full History")); + userEvent.click(getByText("Full History")); await waitFor(() => { const items = getAllByTestId(/history-item-\d-date/); @@ -247,7 +507,7 @@ describe("DataSubmissionSummary History Dialog Tests", () => { }); it("renders only the most recent event with an icon", () => { - const dataSubmission = { + const dataSubmission: RecursivePartial = { history: [ { dateTime: "2023-11-24T01:25:45Z", status: "Rejected" }, { dateTime: "2023-11-22T15:36:01Z", status: "Completed" }, @@ -256,7 +516,7 @@ describe("DataSubmissionSummary History Dialog Tests", () => { const { getByTestId, getByText } = render(); - fireEvent.click(getByText("Full History")); + userEvent.click(getByText("Full History")); expect(getByTestId("history-item-0-icon")).toBeVisible(); expect(() => getByTestId("history-item-1-icon")).toThrow(); @@ -265,13 +525,13 @@ describe("DataSubmissionSummary History Dialog Tests", () => { it.each(Object.entries(HistoryIconMap))( "renders the correct icon for the status %s", (status, svg) => { - const dataSubmission = { - history: [{ dateTime: "2023-11-24T01:25:45Z", status }], + const dataSubmission: RecursivePartial = { + history: [{ dateTime: "2023-11-24T01:25:45Z", status: status as SubmissionStatus }], }; const { getByTestId, getByText } = render(); - fireEvent.click(getByText("Full History")); + userEvent.click(getByText("Full History")); const icon = getByTestId("history-item-0-icon"); diff --git a/src/components/DataSubmissions/DataSubmissionSummary.tsx b/src/components/DataSubmissions/DataSubmissionSummary.tsx index 8ad6f6cb6..0265c8421 100644 --- a/src/components/DataSubmissions/DataSubmissionSummary.tsx +++ b/src/components/DataSubmissions/DataSubmissionSummary.tsx @@ -235,13 +235,21 @@ const DataSubmissionSummary: FC = ({ dataSubmission }) => { placement="top" title="Click to add new collaborators or view existing ones." disableHoverListener={false} - data-testid="collaborators-button-tooltip" + slotProps={{ + tooltip: { "data-testid": "collaborators-button-tooltip" } as unknown, + }} > - - {Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format( - numCollaborators - )} - + + + {Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format( + numCollaborators + )} + + } /> From e26ba9cc116539dcf109ba125d977a922b457dde Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Tue, 15 Oct 2024 16:19:55 -0400 Subject: [PATCH 12/17] Add test coverage for the submission header property component --- .../SubmissionHeaderProperty.test.tsx | 63 +++++++++++++++++++ .../SubmissionHeaderProperty.tsx | 2 +- 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 src/components/DataSubmissions/SubmissionHeaderProperty.test.tsx diff --git a/src/components/DataSubmissions/SubmissionHeaderProperty.test.tsx b/src/components/DataSubmissions/SubmissionHeaderProperty.test.tsx new file mode 100644 index 000000000..5a1b4c154 --- /dev/null +++ b/src/components/DataSubmissions/SubmissionHeaderProperty.test.tsx @@ -0,0 +1,63 @@ +import { render } from "@testing-library/react"; +import SubmissionHeaderProperty from "./SubmissionHeaderProperty"; + +describe("Basic Functionality", () => { + it("renders the label correctly", () => { + const { getByText } = render( + + ); + + expect(getByText("Test Label")).toBeInTheDocument(); + }); + + it("renders the value correctly when value is a string", () => { + const { getByText } = render( + + ); + + expect(getByText("Test Value")).toBeInTheDocument(); + }); + + it("renders the truncated value when truncateAfter is provided", async () => { + const { getByText } = render( + + ); + + expect(getByText("Th...")).toBeInTheDocument(); + }); + + it("renders the value correctly when value is a JSX.Element", () => { + const valueElement = Custom Value; + + const { getByTestId } = render( + + ); + + expect(getByTestId("custom-element")).toBeInTheDocument(); + expect(getByTestId("custom-element")).toHaveTextContent("Custom Value"); + }); + + it("renders the full value when truncateAfter is false", () => { + const { getByText, queryByTestId } = render( + + ); + + expect(getByText("This is a very long value that should not be truncated")).toBeInTheDocument(); + expect(queryByTestId("truncated-text-label")).not.toBeInTheDocument(); + }); + + it("renders correctly when value is an empty string", () => { + const { getByTestId } = render(); + + expect(getByTestId("property-value")).toBeInTheDocument(); + expect(getByTestId("truncated-text-label")).toBeEmptyDOMElement(); + }); +}); diff --git a/src/components/DataSubmissions/SubmissionHeaderProperty.tsx b/src/components/DataSubmissions/SubmissionHeaderProperty.tsx index 649b2776e..5f3491d2b 100644 --- a/src/components/DataSubmissions/SubmissionHeaderProperty.tsx +++ b/src/components/DataSubmissions/SubmissionHeaderProperty.tsx @@ -34,7 +34,7 @@ const SubmissionHeaderProperty = ({ label, value, truncateAfter = 10 }: Props) = {label} {typeof value === "string" ? ( - + {truncateAfter && truncateAfter > 0 ? ( Date: Tue, 15 Oct 2024 16:28:17 -0400 Subject: [PATCH 13/17] Remove truncation from data commons property --- .../DataSubmissions/DataSubmissionSummary.test.tsx | 2 +- src/components/DataSubmissions/DataSubmissionSummary.tsx | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/DataSubmissions/DataSubmissionSummary.test.tsx b/src/components/DataSubmissions/DataSubmissionSummary.test.tsx index bbb5b4b70..938366d82 100644 --- a/src/components/DataSubmissions/DataSubmissionSummary.test.tsx +++ b/src/components/DataSubmissions/DataSubmissionSummary.test.tsx @@ -104,7 +104,7 @@ describe("Basic Functionality", () => { expect(getByText("Test Intention")).toBeVisible(); // Not truncated expect(getByText("Submitter...")).toBeVisible(); expect(getByText("AAAAAAAAAA...")).toBeVisible(); - expect(getByText("Test Commo...")).toBeVisible(); + expect(getByText("Test Commons")).toBeVisible(); // Not truncated expect(getByText("Test Organ...")).toBeVisible(); expect(getByText("Test Conci...")).toBeVisible(); diff --git a/src/components/DataSubmissions/DataSubmissionSummary.tsx b/src/components/DataSubmissions/DataSubmissionSummary.tsx index 0265c8421..5aaceb8d8 100644 --- a/src/components/DataSubmissions/DataSubmissionSummary.tsx +++ b/src/components/DataSubmissions/DataSubmissionSummary.tsx @@ -254,7 +254,11 @@ const DataSubmissionSummary: FC = ({ dataSubmission }) => { } /> - + Date: Tue, 15 Oct 2024 17:04:02 -0400 Subject: [PATCH 14/17] Added comments and minor change --- src/components/DataSubmissions/DataSubmissionSummary.tsx | 2 +- src/types/Submissions.d.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/DataSubmissions/DataSubmissionSummary.tsx b/src/components/DataSubmissions/DataSubmissionSummary.tsx index 5aaceb8d8..29785f4df 100644 --- a/src/components/DataSubmissions/DataSubmissionSummary.tsx +++ b/src/components/DataSubmissions/DataSubmissionSummary.tsx @@ -219,7 +219,7 @@ const DataSubmissionSummary: FC = ({ dataSubmission }) => { container flexDirection={{ xs: "column", lg: "row" }} rowSpacing={2} - columnSpacing={{ xs: 0 }} + columnSpacing={0} > Date: Wed, 16 Oct 2024 12:09:16 -0400 Subject: [PATCH 15/17] Update default truncate value to 16. Also updated tests --- .../DataSubmissionSummary.test.tsx | 35 +++++++++---------- .../DataSubmissions/DataSubmissionSummary.tsx | 2 +- .../SubmissionHeaderProperty.tsx | 2 +- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/components/DataSubmissions/DataSubmissionSummary.test.tsx b/src/components/DataSubmissions/DataSubmissionSummary.test.tsx index 938366d82..64fd2aba8 100644 --- a/src/components/DataSubmissions/DataSubmissionSummary.test.tsx +++ b/src/components/DataSubmissions/DataSubmissionSummary.test.tsx @@ -48,9 +48,9 @@ describe("DataSubmissionSummary Accessibility Tests", () => { describe("Basic Functionality", () => { it("renders all property labels and corresponding values", () => { const dataSubmission: RecursivePartial = { - name: "Test Submission", - intention: "Test Intention" as SubmissionIntention, - submitterName: "Submitter Test", + name: "Test Submission AAAAAA", + intention: "Test Intention AAAAAA" as SubmissionIntention, + submitterName: "Submitter Test AAAAAA", collaborators: [ { collaboratorID: "col-1", @@ -77,13 +77,13 @@ describe("Basic Functionality", () => { permission: "Can View", }, ], - studyAbbreviation: "AAAAAAAAAAA", - dataCommons: "Test Commons", + studyAbbreviation: "AAAAAAAAAAAAAAAAA", + dataCommons: "Test Commons AAAAAA", organization: { _id: "", - name: "Test Organization", + name: "Test Organization AAAAAA", }, - conciergeName: "Test Concierge", + conciergeName: "Test Concierge AAAAAA", conciergeEmail: "concierge@test.com", }; @@ -100,13 +100,13 @@ describe("Basic Functionality", () => { expect(getByText("Primary Contact")).toBeVisible(); // Check values - expect(getByText("Test Submi...")).toBeVisible(); - expect(getByText("Test Intention")).toBeVisible(); // Not truncated - expect(getByText("Submitter...")).toBeVisible(); - expect(getByText("AAAAAAAAAA...")).toBeVisible(); - expect(getByText("Test Commons")).toBeVisible(); // Not truncated - expect(getByText("Test Organ...")).toBeVisible(); - expect(getByText("Test Conci...")).toBeVisible(); + expect(getByText("Test Submission...")).toBeVisible(); + expect(getByText("Test Intention AAAAAA")).toBeVisible(); // Not truncated + expect(getByText("Submitter Test A...")).toBeVisible(); + expect(getByText("AAAAAAAAAAAAAAAA...")).toBeVisible(); + expect(getByText("Test Commons AAAAAA")).toBeVisible(); // Not truncated + expect(getByText("Test Organizatio...")).toBeVisible(); + expect(getByText("Test Concierge A...")).toBeVisible(); expect(getByText("2")).toBeVisible(); @@ -179,7 +179,7 @@ describe("Basic Functionality", () => { const { getByText, getByLabelText } = render(); expect(getByText("Primary Contact")).toBeVisible(); - expect(getByText("Test Conci...")).toBeVisible(); + expect(getByText("Test Concierge")).toBeVisible(); const emailLink = getByLabelText("Email Primary Contact"); expect(emailLink).toBeVisible(); @@ -197,7 +197,7 @@ describe("Basic Functionality", () => { ); expect(getByText("Primary Contact")).toBeVisible(); - expect(getByText("Test Conci...")).toBeVisible(); + expect(getByText("Test Concierge")).toBeVisible(); const emailLink = queryByLabelText("Email Primary Contact"); expect(emailLink).toBeNull(); @@ -212,7 +212,6 @@ describe("DataSubmissionSummary Memoization Tests", () => { const renderSpy = jest.fn(); - // Create a wrapper component that increments the renderSpy const MemoizedComponent = ({ dataSubmission }: Props) => { React.useEffect(() => { renderSpy(); @@ -220,7 +219,6 @@ describe("DataSubmissionSummary Memoization Tests", () => { return ; }; - // Wrap the MemoizedComponent with React.memo and custom comparison const MemoizedComponentWithMemo = React.memo(MemoizedComponent, (prevProps, nextProps) => isEqual(prevProps, nextProps) ); @@ -233,7 +231,6 @@ describe("DataSubmissionSummary Memoization Tests", () => { expect(renderSpy).toHaveBeenCalledTimes(1); - // Re-render with the same props rerender( diff --git a/src/components/DataSubmissions/DataSubmissionSummary.tsx b/src/components/DataSubmissions/DataSubmissionSummary.tsx index 29785f4df..56241dae7 100644 --- a/src/components/DataSubmissions/DataSubmissionSummary.tsx +++ b/src/components/DataSubmissions/DataSubmissionSummary.tsx @@ -270,7 +270,7 @@ const DataSubmissionSummary: FC = ({ dataSubmission }) => { diff --git a/src/components/DataSubmissions/SubmissionHeaderProperty.tsx b/src/components/DataSubmissions/SubmissionHeaderProperty.tsx index 5f3491d2b..af4cf999a 100644 --- a/src/components/DataSubmissions/SubmissionHeaderProperty.tsx +++ b/src/components/DataSubmissions/SubmissionHeaderProperty.tsx @@ -28,7 +28,7 @@ type Props = { truncateAfter?: number | false; }; -const SubmissionHeaderProperty = ({ label, value, truncateAfter = 10 }: Props) => ( +const SubmissionHeaderProperty = ({ label, value, truncateAfter = 16 }: Props) => ( {label} From 1e10db6f3ee6d7e8e28af29e3ca6a1baf2db256d Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Wed, 16 Oct 2024 12:15:14 -0400 Subject: [PATCH 16/17] Remove unused properties from query, type, and tests --- .../DataSubmissionSummary.test.tsx | 15 --------------- src/graphql/getSubmission.ts | 3 --- src/types/Submissions.d.ts | 2 +- 3 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/components/DataSubmissions/DataSubmissionSummary.test.tsx b/src/components/DataSubmissions/DataSubmissionSummary.test.tsx index 64fd2aba8..7ed8f8704 100644 --- a/src/components/DataSubmissions/DataSubmissionSummary.test.tsx +++ b/src/components/DataSubmissions/DataSubmissionSummary.test.tsx @@ -58,9 +58,6 @@ describe("Basic Functionality", () => { Organization: { orgID: "", orgName: "", - status: "Active", - createdAt: "", - updateAt: "", }, permission: "Can View", }, @@ -70,9 +67,6 @@ describe("Basic Functionality", () => { Organization: { orgID: "", orgName: "", - status: "Active", - createdAt: "", - updateAt: "", }, permission: "Can View", }, @@ -124,9 +118,6 @@ describe("Basic Functionality", () => { Organization: { orgID: "", orgName: "", - status: "Active", - createdAt: "", - updateAt: "", }, permission: "Can View", }, @@ -136,9 +127,6 @@ describe("Basic Functionality", () => { Organization: { orgID: "", orgName: "", - status: "Active", - createdAt: "", - updateAt: "", }, permission: "Can View", }, @@ -148,9 +136,6 @@ describe("Basic Functionality", () => { Organization: { orgID: "", orgName: "", - status: "Active", - createdAt: "", - updateAt: "", }, permission: "Can View", }, diff --git a/src/graphql/getSubmission.ts b/src/graphql/getSubmission.ts index 7bcac1c2f..b7b07e272 100644 --- a/src/graphql/getSubmission.ts +++ b/src/graphql/getSubmission.ts @@ -64,9 +64,6 @@ export const query = gql` Organization { orgID orgName - status - createdAt - updateAt } permission } diff --git a/src/types/Submissions.d.ts b/src/types/Submissions.d.ts index a9c2e13fb..600864b59 100644 --- a/src/types/Submissions.d.ts +++ b/src/types/Submissions.d.ts @@ -401,6 +401,6 @@ type CollaboratorPermissions = "Can View" | "Can Edit"; type Collaborator = { collaboratorID: string; collaboratorName: string; - Organization: OrgInfo; + Organization: Pick; permission: CollaboratorPermissions; }; From 326be8f9c5077b1ed5e0170e323c7ef692c35e3c Mon Sep 17 00:00:00 2001 From: Alejandro-Vega Date: Wed, 16 Oct 2024 12:20:41 -0400 Subject: [PATCH 17/17] Remove redundant styling --- src/components/DataSubmissions/DataSubmissionSummary.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/DataSubmissions/DataSubmissionSummary.tsx b/src/components/DataSubmissions/DataSubmissionSummary.tsx index 56241dae7..124c9ec97 100644 --- a/src/components/DataSubmissions/DataSubmissionSummary.tsx +++ b/src/components/DataSubmissions/DataSubmissionSummary.tsx @@ -105,9 +105,7 @@ const StyledCollaboratorsButton = styled(Button)({ padding: 0, margin: 0, color: "#0B7F99", - fontFamily: "'Nunito', 'Rubik', sans-serif", fontSize: "16px", - fontStyle: "normal", fontWeight: 700, lineHeight: "19.6px", minWidth: 0,