diff --git a/__tests__/viewModelBuilders/activity-identity.test.tsx b/__tests__/viewModelBuilders/activity-identity.test.tsx index 47d8fec9a..91f9c80c7 100644 --- a/__tests__/viewModelBuilders/activity-identity.test.tsx +++ b/__tests__/viewModelBuilders/activity-identity.test.tsx @@ -1,8 +1,10 @@ import type { AggregatedDataset } from "../../app/apis/azul/anvil-cmg/common/aggregatedEntities"; import type { ActivityEntity } from "../../app/apis/azul/anvil-cmg/common/entities"; import type { ActivitiesResponse } from "../../app/apis/azul/anvil-cmg/common/responses"; -import { buildActivityIdentity } from "../../app/viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders"; -import { getChipLabels } from "./utils/identityCell"; +import { + buildActivityIdentity, + buildActivityType, +} from "../../app/viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders"; const DATASET_TITLE = "Test Dataset Title"; const DOCUMENT_ID = "test-document-id"; @@ -33,50 +35,18 @@ const createMockActivitiesResponse = ( }) as ActivitiesResponse; describe("buildActivityIdentity", () => { - it("renders the document ID as a plain text title", () => { - const { title } = buildActivityIdentity(createMockActivitiesResponse()); - expect(title).toEqual({ label: DOCUMENT_ID, url: "" }); - }); - - it("renders an activity type chip", () => { - expect( - getChipLabels(buildActivityIdentity(createMockActivitiesResponse()).chips) - ).toEqual(["activity type Sequencing"]); - }); - - it("renders an activity type chip for the Unknown activity type", () => { - const response = createMockActivitiesResponse({ activity_type: "Unknown" }); - expect(getChipLabels(buildActivityIdentity(response).chips)).toEqual([ - "activity type Unknown", - ]); - }); - - it.each([ - ["null", null as unknown as string], - ["unspecified", "Unspecified"], - ])("renders no chips when the activity type is %s", (_, value) => { - const response = createMockActivitiesResponse({ activity_type: value }); - expect(buildActivityIdentity(response).chips).toEqual([]); - }); - - it("renders the dataset title as a plain text subtitle", () => { - const { subtitle } = buildActivityIdentity(createMockActivitiesResponse()); - expect(subtitle).toEqual({ label: DATASET_TITLE, url: "" }); - }); - - it("renders the dataset count as the subtitle when there is more than one dataset", () => { - const response = createMockActivitiesResponse({}, [ - { dataset_id: ["dataset-a"], title: ["Dataset A"] }, - { dataset_id: ["dataset-b"], title: ["Dataset B"] }, - ]); - expect(buildActivityIdentity(response).subtitle).toEqual({ - label: "2 datasets", - url: "", + it("renders the document ID as a plain text title, clamped to two lines, with no chips or subtitle", () => { + expect(buildActivityIdentity(createMockActivitiesResponse())).toEqual({ + maxLines: 2, + title: { label: DOCUMENT_ID, url: "" }, }); }); +}); - it("omits the subtitle when there are no datasets", () => { - const response = createMockActivitiesResponse({}, []); - expect(buildActivityIdentity(response).subtitle).toBeUndefined(); +describe("buildActivityType", () => { + it("renders the activity type", () => { + expect(buildActivityType(createMockActivitiesResponse()).value).toBe( + "Sequencing" + ); }); }); diff --git a/__tests__/viewModelBuilders/biosample-identity.test.tsx b/__tests__/viewModelBuilders/biosample-identity.test.tsx index 837cb0c53..8840c90c9 100644 --- a/__tests__/viewModelBuilders/biosample-identity.test.tsx +++ b/__tests__/viewModelBuilders/biosample-identity.test.tsx @@ -4,15 +4,14 @@ import type { } from "../../app/apis/azul/anvil-cmg/common/aggregatedEntities"; import type { BioSampleEntity } from "../../app/apis/azul/anvil-cmg/common/entities"; import type { BioSamplesResponse } from "../../app/apis/azul/anvil-cmg/common/responses"; -import { buildBioSampleIdentity } from "../../app/viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders"; -import { getChipLabels } from "./utils/identityCell"; +import { + buildAnatomicalSite, + buildBioSampleIdentity, + buildBioSampleType, +} from "../../app/viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders"; const BIOSAMPLE_ID = "test-biosample-id"; const DATASET_TITLE = "Test Dataset Title"; -const UNSPECIFIED_CASES: [string, null | string][] = [ - ["null", null], - ["unspecified", "Unspecified"], -]; /** * Creates a mock BioSamplesResponse for testing. @@ -43,98 +42,26 @@ const createMockBioSamplesResponse = ( }) as BioSamplesResponse; describe("buildBioSampleIdentity", () => { - it("renders the biosample ID as a plain text title", () => { - const { title } = buildBioSampleIdentity(createMockBioSamplesResponse()); - expect(title).toEqual({ label: BIOSAMPLE_ID, url: "" }); - }); - - it("renders biosample type, anatomical site, then organism type chips", () => { - const response = createMockBioSamplesResponse({}, [ - { organism_type: ["Human"] }, - { organism_type: ["Mouse"] }, - ]); - expect(getChipLabels(buildBioSampleIdentity(response).chips)).toEqual([ - "biosample type Tissue", - "anatomical site Blood", - "organism type Human", - "organism type Mouse", - ]); - }); - - it.each(UNSPECIFIED_CASES)( - "omits the biosample type chip when the biosample type is %s", - (_, value) => { - const response = createMockBioSamplesResponse({ biosample_type: value }); - expect(getChipLabels(buildBioSampleIdentity(response).chips)).toEqual([ - "anatomical site Blood", - "organism type Human", - ]); - } - ); - - it.each(UNSPECIFIED_CASES)( - "omits the anatomical site chip when the anatomical site is %s", - (_, value) => { - const response = createMockBioSamplesResponse({ anatomical_site: value }); - expect(getChipLabels(buildBioSampleIdentity(response).chips)).toEqual([ - "biosample type Tissue", - "organism type Human", - ]); - } - ); - - it.each([ - ["empty", []], - ["null", [null]], - ["unspecified", ["Unspecified"]], - ])("omits organism type chips when the organism type is %s", (_, value) => { - const response = createMockBioSamplesResponse({}, [ - { organism_type: value }, - ]); - expect(getChipLabels(buildBioSampleIdentity(response).chips)).toEqual([ - "biosample type Tissue", - "anatomical site Blood", - ]); - }); - - it("renders one organism type chip per unique organism type across donors", () => { - const response = createMockBioSamplesResponse({}, [ - { organism_type: ["Human"] }, - { organism_type: ["Human"] }, - ]); - expect(getChipLabels(buildBioSampleIdentity(response).chips)).toEqual([ - "biosample type Tissue", - "anatomical site Blood", - "organism type Human", - ]); + it("renders the biosample ID as a plain text title, clamped to two lines, with no chips or subtitle", () => { + expect(buildBioSampleIdentity(createMockBioSamplesResponse())).toEqual({ + maxLines: 2, + title: { label: BIOSAMPLE_ID, url: "" }, + }); }); +}); - it("renders no chips when every biosample field is unspecified", () => { - const response = createMockBioSamplesResponse( - { anatomical_site: null, biosample_type: null }, - [] +describe("buildAnatomicalSite", () => { + it("renders the anatomical site", () => { + expect(buildAnatomicalSite(createMockBioSamplesResponse()).value).toBe( + "Blood" ); - expect(buildBioSampleIdentity(response).chips).toEqual([]); - }); - - it("renders the dataset title as a plain text subtitle", () => { - const { subtitle } = buildBioSampleIdentity(createMockBioSamplesResponse()); - expect(subtitle).toEqual({ label: DATASET_TITLE, url: "" }); - }); - - it("renders the dataset count as the subtitle when there is more than one dataset", () => { - const response = createMockBioSamplesResponse({}, undefined, [ - { dataset_id: ["dataset-a"], title: ["Dataset A"] }, - { dataset_id: ["dataset-b"], title: ["Dataset B"] }, - ]); - expect(buildBioSampleIdentity(response).subtitle).toEqual({ - label: "2 datasets", - url: "", - }); }); +}); - it("omits the subtitle when there are no datasets", () => { - const response = createMockBioSamplesResponse({}, undefined, []); - expect(buildBioSampleIdentity(response).subtitle).toBeUndefined(); +describe("buildBioSampleType", () => { + it("renders the biosample type", () => { + expect(buildBioSampleType(createMockBioSamplesResponse()).value).toBe( + "Tissue" + ); }); }); diff --git a/__tests__/viewModelBuilders/buildFileDownload.test.ts b/__tests__/viewModelBuilders/buildFileDownload.test.ts index 16efc9669..b403c8d83 100644 --- a/__tests__/viewModelBuilders/buildFileDownload.test.ts +++ b/__tests__/viewModelBuilders/buildFileDownload.test.ts @@ -1,7 +1,5 @@ -import { AzulFileDownloadProps } from "@databiosphere/findable-ui/lib/components/Index/components/AzulFileDownload/azulFileDownload"; -import { ReactElement } from "react"; import { FilesResponse } from "../../app/apis/azul/anvil-cmg/common/responses"; -import { buildFileDownloadWithTooltip } from "../../app/viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders"; +import { buildFileDownloadMenu } from "../../app/viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders"; /** * Creates a mock FilesResponse with configurable file and dataset values. @@ -52,6 +50,7 @@ const createMockFilesResponse = ( file_format: "bam", file_id: "file-123", file_name: "sample.bam", + file_path: "folder/sample.bam", file_size: 1024, file_type: "Analysis", ...overrides, @@ -64,7 +63,7 @@ const createMockFilesResponse = ( ], }); -describe("buildFileDownload", () => { +describe("buildFileDownloadMenu", () => { describe("url prop based on azul_mirror_uri", () => { it("returns undefined url when azul_mirror_uri is null", () => { const response = createMockFilesResponse({ @@ -72,11 +71,9 @@ describe("buildFileDownload", () => { azul_url: "https://service.azul.data/repository/files/file-123", }); - const result = buildFileDownloadWithTooltip(response); + const result = buildFileDownloadMenu(response); - expect( - (result.children as ReactElement).props.url - ).toBeUndefined(); + expect(result.url).toBeUndefined(); }); it("returns undefined url when azul_mirror_uri is empty string", () => { @@ -85,11 +82,9 @@ describe("buildFileDownload", () => { azul_url: "https://service.azul.data/repository/files/file-123", }); - const result = buildFileDownloadWithTooltip(response); + const result = buildFileDownloadMenu(response); - expect( - (result.children as ReactElement).props.url - ).toBeUndefined(); + expect(result.url).toBeUndefined(); }); it("returns azul_url when azul_mirror_uri has a value", () => { @@ -99,11 +94,9 @@ describe("buildFileDownload", () => { azul_url: azulUrl, }); - const result = buildFileDownloadWithTooltip(response); + const result = buildFileDownloadMenu(response); - expect( - (result.children as ReactElement).props.url - ).toBe(azulUrl); + expect(result.url).toBe(azulUrl); }); it("returns azul_url value exactly as provided when mirror uri exists", () => { @@ -114,11 +107,9 @@ describe("buildFileDownload", () => { azul_url: azulUrl, }); - const result = buildFileDownloadWithTooltip(response); + const result = buildFileDownloadMenu(response); - expect( - (result.children as ReactElement).props.url - ).toBe(azulUrl); + expect(result.url).toBe(azulUrl); }); }); @@ -128,12 +119,9 @@ describe("buildFileDownload", () => { file_name: "my-sample-file.bam", }); - const result = buildFileDownloadWithTooltip(response); + const result = buildFileDownloadMenu(response); - expect( - (result.children as ReactElement).props - .entityName - ).toBe("my-sample-file.bam"); + expect(result.entityName).toBe("my-sample-file.bam"); }); it("returns correct relatedEntityId from dataset_id", () => { @@ -142,12 +130,9 @@ describe("buildFileDownload", () => { { dataset_id: ["my-dataset-id-123"] } ); - const result = buildFileDownloadWithTooltip(response); + const result = buildFileDownloadMenu(response); - expect( - (result.children as ReactElement).props - .relatedEntityId - ).toBe("my-dataset-id-123"); + expect(result.relatedEntityId).toBe("my-dataset-id-123"); }); it("returns correct relatedEntityName from dataset title", () => { @@ -156,39 +141,17 @@ describe("buildFileDownload", () => { { title: ["My Research Dataset"] } ); - const result = buildFileDownloadWithTooltip(response); + const result = buildFileDownloadMenu(response); - expect( - (result.children as ReactElement).props - .relatedEntityName - ).toBe("My Research Dataset"); + expect(result.relatedEntityName).toBe("My Research Dataset"); }); }); - describe("tooltip title", () => { - it("returns tooltip message when download is disabled", () => { - const response = createMockFilesResponse({ - azul_mirror_uri: null, - }); - - const result = buildFileDownloadWithTooltip(response); - - expect(result.title).toBe( - "Direct file downloads are currently only available for open-access files." - ); - }); + describe("drsUri", () => { + it("returns the file's DRS URI", () => { + const result = buildFileDownloadMenu(createMockFilesResponse()); - it("returns enabled tooltip message when download is available", () => { - const response = createMockFilesResponse({ - azul_mirror_uri: "s3://bucket/path/to/file.bam", - azul_url: "https://service.azul.data/repository/files/file-123", - }); - - const result = buildFileDownloadWithTooltip(response); - - expect(result.title).toBe( - "This open-access file is freely downloadable from AWS Open Data, with no data transfer fees." - ); + expect(result.drsUri).toBe("drs://data.azul/file-123"); }); }); @@ -206,11 +169,10 @@ describe("buildFileDownload", () => { } ); - const result = buildFileDownloadWithTooltip(response); + const result = buildFileDownloadMenu(response); - expect( - (result.children as ReactElement).props - ).toEqual({ + expect(result).toEqual({ + drsUri: "drs://data.azul/file-123", entityName: "research-data.bam", relatedEntityId: "dataset-abc", relatedEntityName: "Genomics Research Project", @@ -231,11 +193,10 @@ describe("buildFileDownload", () => { } ); - const result = buildFileDownloadWithTooltip(response); + const result = buildFileDownloadMenu(response); - expect( - (result.children as ReactElement).props - ).toEqual({ + expect(result).toEqual({ + drsUri: "drs://data.azul/file-123", entityName: "research-data.bam", relatedEntityId: "dataset-abc", relatedEntityName: "Genomics Research Project", diff --git a/__tests__/viewModelBuilders/dataset-identity.test.tsx b/__tests__/viewModelBuilders/dataset-identity.test.tsx index d6e057ce8..b9f337a0d 100644 --- a/__tests__/viewModelBuilders/dataset-identity.test.tsx +++ b/__tests__/viewModelBuilders/dataset-identity.test.tsx @@ -1,16 +1,15 @@ import { CHIP_PROPS } from "@databiosphere/findable-ui/lib/styles/common/mui/chip"; import type { DatasetEntity } from "../../app/apis/azul/anvil-cmg/common/entities"; import type { DatasetsResponse } from "../../app/apis/azul/anvil-cmg/common/responses"; -import { buildDatasetIdentity } from "../../app/viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders"; -import { getChipLabels } from "./utils/identityCell"; +import { + buildConsentGroup, + buildDatasetAccess, + buildDatasetIdentity, + buildRegisteredIdentifiers, +} from "../../app/viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders"; const DATASET_ID = "test-dataset-id"; const DATASET_TITLE = "Test Dataset Title"; -const UNSPECIFIED_CASES: [string, (string | null)[]][] = [ - ["empty", []], - ["null", [null]], - ["unspecified", ["Unspecified"]], -]; /** * Creates a mock DatasetsResponse for testing. @@ -44,66 +43,46 @@ describe("buildDatasetIdentity", () => { }); }); - it("renders access, identifier, then consent group chips", () => { - const response = createMockDatasetsResponse({ - consent_group: ["DS-BDIS-MDS", "GRU"], - }); - expect(getChipLabels(buildDatasetIdentity(response).chips)).toEqual([ - "access Granted", - "identifier phs000693", - "consent group DS-BDIS-MDS", - "consent group GRU", - ]); - }); - - it("renders one identifier chip per unique registered identifier", () => { - const response = createMockDatasetsResponse({ - registered_identifier: ["phs000693", "phs001272", "phs000693"], - }); - expect(getChipLabels(buildDatasetIdentity(response).chips)).toEqual([ - "access Granted", - "identifier phs000693", - "identifier phs001272", - "consent group GRU", - ]); + it("renders no chips", () => { + expect( + buildDatasetIdentity(createMockDatasetsResponse()).chips + ).toBeUndefined(); }); +}); +describe("buildDatasetAccess", () => { it.each([ - [true, CHIP_PROPS.COLOR.SUCCESS, "access Granted"], - [false, CHIP_PROPS.COLOR.WARNING, "access Required"], + [true, CHIP_PROPS.COLOR.SUCCESS, "Granted"], + [false, CHIP_PROPS.COLOR.WARNING, "Required"], ])( - "renders the access chip when accessible is %s", + "renders the access badge when accessible is %s", (accessible, color, label) => { const response = createMockDatasetsResponse({ accessible }); - const [accessChip] = buildDatasetIdentity(response).chips ?? []; - expect(accessChip.color).toBe(color); - expect(getChipLabels(buildDatasetIdentity(response).chips)[0]).toBe( - label - ); + expect(buildDatasetAccess(response)).toEqual({ color, label }); } ); +}); - it.each(UNSPECIFIED_CASES)( - "omits the identifier chip when the identifier is %s", - (_, value) => { - const response = createMockDatasetsResponse({ - registered_identifier: value, - }); - expect(getChipLabels(buildDatasetIdentity(response).chips)).toEqual([ - "access Granted", - "consent group GRU", - ]); - } - ); +describe("buildRegisteredIdentifiers", () => { + it("renders each unique registered identifier", () => { + const response = createMockDatasetsResponse({ + registered_identifier: ["phs000693", "phs001272", "phs000693"], + }); + expect(buildRegisteredIdentifiers(response)).toEqual({ + label: "dbGap ids", + values: ["phs000693", "phs001272"], + }); + }); +}); - it.each(UNSPECIFIED_CASES)( - "omits consent group chips when the consent group is %s", - (_, value) => { - const response = createMockDatasetsResponse({ consent_group: value }); - expect(getChipLabels(buildDatasetIdentity(response).chips)).toEqual([ - "access Granted", - "identifier phs000693", - ]); - } - ); +describe("buildConsentGroup", () => { + it("renders each consent group", () => { + const response = createMockDatasetsResponse({ + consent_group: ["DS-BDIS-MDS", "GRU"], + }); + expect(buildConsentGroup(response)).toEqual({ + label: "consent codes", + values: ["DS-BDIS-MDS", "GRU"], + }); + }); }); diff --git a/__tests__/viewModelBuilders/donor-identity.test.tsx b/__tests__/viewModelBuilders/donor-identity.test.tsx index 75a797b18..6516cbcd1 100644 --- a/__tests__/viewModelBuilders/donor-identity.test.tsx +++ b/__tests__/viewModelBuilders/donor-identity.test.tsx @@ -1,15 +1,14 @@ import type { AggregatedDataset } from "../../app/apis/azul/anvil-cmg/common/aggregatedEntities"; import type { DonorEntity } from "../../app/apis/azul/anvil-cmg/common/entities"; import type { DonorsResponse } from "../../app/apis/azul/anvil-cmg/common/responses"; -import { buildDonorIdentity } from "../../app/viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders"; -import { getChipLabels } from "./utils/identityCell"; +import { + buildDonorIdentity, + buildOrganismType, + buildPhenotypicSex, +} from "../../app/viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders"; const DATASET_TITLE = "Test Dataset Title"; const DONOR_ID = "test-donor-id"; -const UNSPECIFIED_CASES: [string, null | string][] = [ - ["null", null], - ["unspecified", "Unspecified"], -]; /** * Creates a mock DonorsResponse for testing. @@ -38,105 +37,22 @@ const createMockDonorsResponse = ( }) as DonorsResponse; describe("buildDonorIdentity", () => { - it("renders the donor ID as a plain text title", () => { - const { title } = buildDonorIdentity(createMockDonorsResponse()); - expect(title).toEqual({ label: DONOR_ID, url: "" }); - }); - - it("renders organism type, phenotypic sex, then reported ethnicity chips", () => { - const response = createMockDonorsResponse({ - reported_ethnicity: ["European", "Hispanic or Latin American"], - }); - expect(getChipLabels(buildDonorIdentity(response).chips)).toEqual([ - "organism type Human", - "phenotypic sex Female", - "reported ethnicity European", - "reported ethnicity Hispanic or Latin American", - ]); - }); - - it.each(UNSPECIFIED_CASES)( - "omits the organism type chip when the organism type is %s", - (_, value) => { - const response = createMockDonorsResponse({ organism_type: value }); - expect(getChipLabels(buildDonorIdentity(response).chips)).toEqual([ - "phenotypic sex Female", - "reported ethnicity European", - ]); - } - ); - - it.each(UNSPECIFIED_CASES)( - "omits the phenotypic sex chip when the phenotypic sex is %s", - (_, value) => { - const response = createMockDonorsResponse({ phenotypic_sex: value }); - expect(getChipLabels(buildDonorIdentity(response).chips)).toEqual([ - "organism type Human", - "reported ethnicity European", - ]); - } - ); - - it.each([ - ["empty", []], - ["null", [null]], - ["unspecified", ["Unspecified"]], - ])( - "omits reported ethnicity chips when the reported ethnicity is %s", - (_, value) => { - const response = createMockDonorsResponse({ reported_ethnicity: value }); - expect(getChipLabels(buildDonorIdentity(response).chips)).toEqual([ - "organism type Human", - "phenotypic sex Female", - ]); - } - ); - - it("renders no chips when every donor field is unspecified", () => { - const response = createMockDonorsResponse({ - organism_type: null, - phenotypic_sex: null, - reported_ethnicity: [], - }); - expect(buildDonorIdentity(response).chips).toEqual([]); - }); - - it("renders the dataset title as a plain text subtitle", () => { - const { subtitle } = buildDonorIdentity(createMockDonorsResponse()); - expect(subtitle).toEqual({ label: DATASET_TITLE, url: "" }); - }); - - it("renders the dataset count as the subtitle when there is more than one dataset", () => { - const response = createMockDonorsResponse({}, [ - { dataset_id: ["dataset-a"], title: ["Dataset A"] }, - { dataset_id: ["dataset-b"], title: ["Dataset B"] }, - ]); - expect(buildDonorIdentity(response).subtitle).toEqual({ - label: "2 datasets", - url: "", - }); - }); - - it("counts datasets by ID when two datasets share a title", () => { - const response = createMockDonorsResponse({}, [ - { dataset_id: ["dataset-a"], title: [DATASET_TITLE] }, - { dataset_id: ["dataset-b"], title: [DATASET_TITLE] }, - ]); - expect(buildDonorIdentity(response).subtitle).toEqual({ - label: "2 datasets", - url: "", + it("renders the donor ID as a plain text title, clamped to two lines, with no chips or subtitle", () => { + expect(buildDonorIdentity(createMockDonorsResponse())).toEqual({ + maxLines: 2, + title: { label: DONOR_ID, url: "" }, }); }); +}); - it("omits the subtitle when the donor has no datasets", () => { - const response = createMockDonorsResponse({}, []); - expect(buildDonorIdentity(response).subtitle).toBeUndefined(); +describe("buildOrganismType", () => { + it("renders the organism type", () => { + expect(buildOrganismType(createMockDonorsResponse()).value).toBe("Human"); }); +}); - it("omits the subtitle when the dataset title is unspecified", () => { - const response = createMockDonorsResponse({}, [ - { dataset_id: ["test-dataset-id"], title: [null as unknown as string] }, - ]); - expect(buildDonorIdentity(response).subtitle).toBeUndefined(); +describe("buildPhenotypicSex", () => { + it("renders the phenotypic sex", () => { + expect(buildPhenotypicSex(createMockDonorsResponse()).value).toBe("Female"); }); }); diff --git a/__tests__/viewModelBuilders/file-identity.test.tsx b/__tests__/viewModelBuilders/file-identity.test.tsx index 314793f9c..409a85d36 100644 --- a/__tests__/viewModelBuilders/file-identity.test.tsx +++ b/__tests__/viewModelBuilders/file-identity.test.tsx @@ -1,14 +1,18 @@ +import { render } from "@testing-library/react"; import type { AggregatedDataset } from "../../app/apis/azul/anvil-cmg/common/aggregatedEntities"; import type { FileEntity } from "../../app/apis/azul/anvil-cmg/common/entities"; import type { FilesResponse } from "../../app/apis/azul/anvil-cmg/common/responses"; import { + buildDatasetTitles, + buildFileDataModality, + buildFileFormat, buildFileIdentity, buildFileSize, } from "../../app/viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders"; -import { getChipLabels } from "./utils/identityCell"; const DATASET_TITLE = "Test Dataset Title"; const FILE_NAME = "test-file.cram"; +const FOLDER_PATH = "AnVIL_T2T/long_read/sv_jasmine"; /** * Creates a mock FilesResponse for testing. @@ -30,6 +34,7 @@ const createMockFilesResponse = ( data_modality: ["Genomic"], file_format: ".cram", file_name: FILE_NAME, + file_path: `${FOLDER_PATH}/${FILE_NAME}`, file_size: 2048, ...fileOverrides, }, @@ -39,70 +44,59 @@ const createMockFilesResponse = ( describe("buildFileIdentity", () => { it("renders the file name as a plain text title", () => { const { title } = buildFileIdentity(createMockFilesResponse()); - expect(title).toEqual({ label: FILE_NAME, url: "" }); + expect(title).toEqual({ + TypographyProps: { sx: { wordBreak: "break-all" } }, + label: FILE_NAME, + url: "", + }); }); - it("renders format, then data modality chips", () => { - const response = createMockFilesResponse({ - data_modality: ["Genomic", "Transcriptomic"], - }); - expect(getChipLabels(buildFileIdentity(response).chips)).toEqual([ - "format .cram", - "data modality Genomic", - "data modality Transcriptomic", - ]); + it("renders no chips", () => { + expect(buildFileIdentity(createMockFilesResponse()).chips).toBeUndefined(); }); - it("renders one data modality chip per unique data modality", () => { - const response = createMockFilesResponse({ - data_modality: ["Genomic", "Genomic"], - }); - expect(getChipLabels(buildFileIdentity(response).chips)).toEqual([ - "format .cram", - "data modality Genomic", - ]); + it("renders the folder path as a plain text subtitle that wraps between folders", () => { + const { subtitle } = buildFileIdentity(createMockFilesResponse()); + expect(subtitle?.url).toBe(""); + const { container } = render(<>{subtitle?.label}); + expect(container.textContent).toBe(FOLDER_PATH); + expect(container.querySelectorAll("wbr")).toHaveLength(2); }); - it.each([ - ["null", null as unknown as string], - ["unspecified", "Unspecified"], - ])("omits the format chip when the format is %s", (_, value) => { - const response = createMockFilesResponse({ file_format: value }); - expect(getChipLabels(buildFileIdentity(response).chips)).toEqual([ - "data modality Genomic", - ]); + it("omits the subtitle when the file is not in a folder", () => { + const response = createMockFilesResponse({ file_path: FILE_NAME }); + expect(buildFileIdentity(response).subtitle).toBeUndefined(); }); +}); - it.each([ - ["empty", []], - ["null", [null as unknown as string]], - ["unspecified", ["Unspecified"]], - ])("omits data modality chips when the data modality is %s", (_, value) => { - const response = createMockFilesResponse({ data_modality: value }); - expect(getChipLabels(buildFileIdentity(response).chips)).toEqual([ - "format .cram", - ]); +describe("buildFileFormat", () => { + it("renders the file format", () => { + expect(buildFileFormat(createMockFilesResponse()).value).toBe(".cram"); }); +}); - it("renders the dataset title as a plain text subtitle", () => { - const { subtitle } = buildFileIdentity(createMockFilesResponse()); - expect(subtitle).toEqual({ label: DATASET_TITLE, url: "" }); +describe("buildFileDataModality", () => { + it("renders each unique data modality", () => { + const response = createMockFilesResponse({ + data_modality: ["Genomic", "Transcriptomic", "Genomic"], + }); + expect(buildFileDataModality(response).values).toEqual([ + "Genomic", + "Transcriptomic", + ]); }); +}); - it("renders the dataset count as the subtitle when there is more than one dataset", () => { +describe("buildDatasetTitles", () => { + it("renders the dataset titles", () => { const response = createMockFilesResponse({}, [ { dataset_id: ["dataset-a"], title: ["Dataset A"] }, { dataset_id: ["dataset-b"], title: ["Dataset B"] }, ]); - expect(buildFileIdentity(response).subtitle).toEqual({ - label: "2 datasets", - url: "", - }); - }); - - it("omits the subtitle when there are no datasets", () => { - const response = createMockFilesResponse({}, []); - expect(buildFileIdentity(response).subtitle).toBeUndefined(); + expect(buildDatasetTitles(response).values).toEqual([ + "Dataset A", + "Dataset B", + ]); }); }); diff --git a/__tests__/viewModelBuilders/utils/identityCell.tsx b/__tests__/viewModelBuilders/utils/identityCell.tsx deleted file mode 100644 index 5393d564f..000000000 --- a/__tests__/viewModelBuilders/utils/identityCell.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import type { IdentityChipProps } from "@databiosphere/findable-ui/lib/components/Table/components/TableCell/components/IdentityCell/components/Chips/components/Chip/types"; -import { render } from "@testing-library/react"; - -/** - * Returns the rendered text of each of the given IdentityCell chip labels. - * @param chips - IdentityCell chip props. - * @returns Chip label text, in chip order. - */ -export const getChipLabels = (chips: IdentityChipProps[] = []): string[] => - chips.map(({ label }) => render(<>{label}).container.textContent ?? ""); diff --git a/app/apis/azul/anvil-cmg/common/entities.ts b/app/apis/azul/anvil-cmg/common/entities.ts index 9600b1dcb..458752118 100644 --- a/app/apis/azul/anvil-cmg/common/entities.ts +++ b/app/apis/azul/anvil-cmg/common/entities.ts @@ -98,6 +98,7 @@ export interface FileEntity { file_format: string; file_id: string; file_name: string; + file_path: string; file_size: number; file_type: string; } diff --git a/app/apis/azul/anvil-cmg/common/transformers.ts b/app/apis/azul/anvil-cmg/common/transformers.ts index b5f664872..3cc16378f 100644 --- a/app/apis/azul/anvil-cmg/common/transformers.ts +++ b/app/apis/azul/anvil-cmg/common/transformers.ts @@ -192,6 +192,11 @@ export function getFileName(response: FileEntityResponse): string { return processEntityValue(response.files, "file_name"); } +export function getFileFolderPath(response: FileEntityResponse): string { + const filePath = processEntityValue(response.files, "file_path", LABEL.EMPTY); + return filePath.slice(0, Math.max(filePath.lastIndexOf("/"), 0)); +} + /** * Maps file format from the core file value returned from the /index/files API response. * @param response - Response model return from index/files API endpoint. diff --git a/app/components/Detail/components/AnVILCMG/components/RequestAccess/constants.ts b/app/components/Detail/components/AnVILCMG/components/RequestAccess/constants.ts index d478c8f1f..f318e7557 100644 --- a/app/components/Detail/components/AnVILCMG/components/RequestAccess/constants.ts +++ b/app/components/Detail/components/AnVILCMG/components/RequestAccess/constants.ts @@ -1,23 +1,10 @@ -import { TYPOGRAPHY_PROPS } from "@databiosphere/findable-ui/lib/styles/common/mui/typography"; -import { - ButtonProps, - ListItemTextProps, - MenuProps, - SvgIconProps, -} from "@mui/material"; +import { ButtonProps, MenuProps, SvgIconProps } from "@mui/material"; export const BUTTON_PROPS: ButtonProps = { color: "primary", variant: "contained", }; -export const LIST_ITEM_TEXT_PROPS: ListItemTextProps = { - primaryTypographyProps: { variant: TYPOGRAPHY_PROPS.VARIANT.BODY_500 }, - secondaryTypographyProps: { - variant: TYPOGRAPHY_PROPS.VARIANT.BODY_SMALL_400_2_LINES, - }, -}; - export const MENU_PROPS: Partial = { variant: "menu", }; diff --git a/app/components/Detail/components/AnVILCMG/components/RequestAccess/requestAccess.styles.ts b/app/components/Detail/components/AnVILCMG/components/RequestAccess/requestAccess.styles.ts index 7f467f50d..88513c07a 100644 --- a/app/components/Detail/components/AnVILCMG/components/RequestAccess/requestAccess.styles.ts +++ b/app/components/Detail/components/AnVILCMG/components/RequestAccess/requestAccess.styles.ts @@ -1,4 +1,3 @@ -import { DropdownMenu } from "@databiosphere/findable-ui/lib/components/common/DropdownMenu/dropdownMenu"; import { PALETTE } from "@databiosphere/findable-ui/lib/styles/common/constants/palette"; import { css } from "@emotion/react"; import styled from "@emotion/styled"; @@ -8,18 +7,6 @@ interface Props { open?: boolean; } -export const StyledDropdownMenu = styled(DropdownMenu)` - .MuiPaper-menu { - max-width: 324px; - - .MuiListItemText-root { - display: grid; - gap: 4px; - white-space: normal; - } - } -`; - export const StyledButton = styled(Button, { shouldForwardProp: (prop) => prop !== "open", })` diff --git a/app/components/Detail/components/AnVILCMG/components/RequestAccess/requestAccess.tsx b/app/components/Detail/components/AnVILCMG/components/RequestAccess/requestAccess.tsx index f07e0baf6..43f0d0bac 100644 --- a/app/components/Detail/components/AnVILCMG/components/RequestAccess/requestAccess.tsx +++ b/app/components/Detail/components/AnVILCMG/components/RequestAccess/requestAccess.tsx @@ -7,13 +7,10 @@ import { CallToActionButton } from "@databiosphere/findable-ui/lib/components/co import ArrowDropDownRoundedIcon from "@mui/icons-material/ArrowDropDownRounded"; import { ListItemText, MenuItem } from "@mui/material"; import { JSX } from "react"; -import { - BUTTON_PROPS, - LIST_ITEM_TEXT_PROPS, - MENU_PROPS, - SVG_ICON_PROPS, -} from "./constants"; -import { StyledButton, StyledDropdownMenu } from "./requestAccess.styles"; +import { LIST_ITEM_TEXT_PROPS } from "../../../../../common/DropdownMenu/constants"; +import { StyledDropdownMenu } from "../../../../../common/DropdownMenu/dropdownMenu.styles"; +import { BUTTON_PROPS, MENU_PROPS, SVG_ICON_PROPS } from "./constants"; +import { StyledButton } from "./requestAccess.styles"; import { Props } from "./types"; import { getRequestAccessOptions } from "./utils"; diff --git a/app/components/Index/components/CopyCell/copyCell.styles.ts b/app/components/Index/components/CopyCell/copyCell.styles.ts deleted file mode 100644 index 47bed1a53..000000000 --- a/app/components/Index/components/CopyCell/copyCell.styles.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { CopyToClipboard as DXCopyToClipboard } from "@databiosphere/findable-ui/lib/components/common/CopyToClipboard/copyToClipboard"; -import styled from "@emotion/styled"; - -export const Cell = styled.div` - display: flex; -`; - -export const CellText = styled("span")` - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -`; - -export const CopyToClipboard = styled(DXCopyToClipboard)` - bottom: 2px; - margin-left: 4px; - position: relative; -`; diff --git a/app/components/Index/components/CopyCell/copyCell.tsx b/app/components/Index/components/CopyCell/copyCell.tsx deleted file mode 100644 index 83aad3286..000000000 --- a/app/components/Index/components/CopyCell/copyCell.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { JSX } from "react"; -import { Cell, CellText, CopyToClipboard } from "./copyCell.styles"; - -interface CopyCellProps { - value: string; -} - -export const CopyCell = ({ value }: CopyCellProps): JSX.Element => { - return ( - - {value} - - - ); -}; diff --git a/app/components/Index/components/FileDownloadMenu/constants.ts b/app/components/Index/components/FileDownloadMenu/constants.ts new file mode 100644 index 000000000..23537cc2a --- /dev/null +++ b/app/components/Index/components/FileDownloadMenu/constants.ts @@ -0,0 +1,22 @@ +import { MenuProps } from "@mui/material"; + +export const COPIED_DURATION_MS = 1000; + +export const LABEL = { + COPIED: "DRS URI copied", + COPY_DRS_URI: "Copy DRS URI", + COPY_DRS_URI_DESCRIPTION: + "Link for Terra and other tools that read DRS links.", + DOWNLOAD: "Download file", + DOWNLOADING: "Downloading", + DOWNLOAD_AVAILABLE: + "Open-access file, freely downloadable from AWS Open Data with no data transfer fees.", + DOWNLOAD_UNAVAILABLE: + "Direct file downloads are currently only available for open-access files.", + FILE_ACTIONS: "File actions for", +}; + +export const MENU_PROPS: Partial = { + anchorOrigin: { horizontal: "left", vertical: "bottom" }, + transformOrigin: { horizontal: "left", vertical: "top" }, +}; diff --git a/app/components/Index/components/FileDownloadMenu/fileDownloadMenu.tsx b/app/components/Index/components/FileDownloadMenu/fileDownloadMenu.tsx new file mode 100644 index 000000000..cb65e3aea --- /dev/null +++ b/app/components/Index/components/FileDownloadMenu/fileDownloadMenu.tsx @@ -0,0 +1,94 @@ +import { DownloadIcon } from "@databiosphere/findable-ui/lib/components/common/CustomIcon/components/DownloadIcon/downloadIcon"; +import { LoadingIcon } from "@databiosphere/findable-ui/lib/components/common/CustomIcon/components/LoadingIcon/loadingIcon"; +import { IconButton } from "@databiosphere/findable-ui/lib/components/common/IconButton/iconButton"; +import { useDownload } from "@databiosphere/findable-ui/lib/components/Index/components/AzulFileDownload/hooks/UseDownload/hook"; +import { ICON_BUTTON_PROPS } from "@databiosphere/findable-ui/lib/styles/common/mui/iconButton"; +import { Box, ListItemText, MenuItem } from "@mui/material"; +import copy from "copy-to-clipboard"; +import { JSX, useEffect, useRef, useState } from "react"; +import { LIST_ITEM_TEXT_PROPS } from "../../../common/DropdownMenu/constants"; +import { StyledDropdownMenu } from "../../../common/DropdownMenu/dropdownMenu.styles"; +import { COPIED_DURATION_MS, LABEL, MENU_PROPS } from "./constants"; + +export interface FileDownloadMenuProps { + drsUri: string; + entityName: string; + relatedEntityId: string; + relatedEntityName: string; + url?: string; +} + +export const FileDownloadMenu = ({ + drsUri, + entityName, + relatedEntityId, + relatedEntityName, + url, +}: FileDownloadMenuProps): JSX.Element => { + const { downloadRef, isRequestPending, onDownload } = useDownload({ + entityName, + relatedEntityId, + relatedEntityName, + url, + }); + const [isCopied, setIsCopied] = useState(false); + const timeoutRef = useRef>(undefined); + + useEffect(() => (): void => clearTimeout(timeoutRef.current), []); + + return ( + <> + ( + + )} + > + {({ closeMenu }): JSX.Element[] => [ + { + closeMenu(); + onDownload(); + }} + > + + , + { + copy(drsUri); + setIsCopied(true); + clearTimeout(timeoutRef.current); + timeoutRef.current = setTimeout(() => { + closeMenu(); + setIsCopied(false); + }, COPIED_DURATION_MS); + }} + > + + , + ]} + + + + ); +}; diff --git a/app/components/common/DropdownMenu/constants.ts b/app/components/common/DropdownMenu/constants.ts new file mode 100644 index 000000000..3b0272225 --- /dev/null +++ b/app/components/common/DropdownMenu/constants.ts @@ -0,0 +1,9 @@ +import { TYPOGRAPHY_PROPS } from "@databiosphere/findable-ui/lib/styles/common/mui/typography"; +import { ListItemTextProps } from "@mui/material"; + +export const LIST_ITEM_TEXT_PROPS: ListItemTextProps = { + primaryTypographyProps: { variant: TYPOGRAPHY_PROPS.VARIANT.BODY_500 }, + secondaryTypographyProps: { + variant: TYPOGRAPHY_PROPS.VARIANT.BODY_SMALL_400_2_LINES, + }, +}; diff --git a/app/components/common/DropdownMenu/dropdownMenu.styles.ts b/app/components/common/DropdownMenu/dropdownMenu.styles.ts new file mode 100644 index 000000000..8798fbb80 --- /dev/null +++ b/app/components/common/DropdownMenu/dropdownMenu.styles.ts @@ -0,0 +1,14 @@ +import { DropdownMenu } from "@databiosphere/findable-ui/lib/components/common/DropdownMenu/dropdownMenu"; +import styled from "@emotion/styled"; + +export const StyledDropdownMenu = styled(DropdownMenu)` + .MuiPaper-menu { + max-width: 324px; + + .MuiListItemText-root { + display: grid; + gap: 4px; + white-space: normal; + } + } +`; diff --git a/app/components/index.tsx b/app/components/index.tsx index 00adbdd41..5b11affb4 100644 --- a/app/components/index.tsx +++ b/app/components/index.tsx @@ -98,7 +98,7 @@ export { ManifestDownloadEntity as AnVILManifestDownloadEntity } from "./Export/ export { ExportToPlatform } from "./Export/components/AnVILExplorer/platform/ExportToPlatform/exportToPlatform"; export { BioNetworkCell } from "./Index/components/BioNetworkCell/bioNetworkCell"; export { ConsentCodesCell } from "./Index/components/ConsentCodesCell/consentCodesCell"; -export { CopyCell } from "./Index/components/CopyCell/copyCell"; +export { FileDownloadMenu } from "./Index/components/FileDownloadMenu/fileDownloadMenu"; export { BackPageHeroActions } from "./Layout/components/BackPage/components/BackPageHero/components/BackPageHeroActions/backPageHeroActions"; export { ANVILBranding } from "./Layout/components/Footer/components/Branding/components/ANVILBranding/anvilBranding"; export { HCABranding } from "./Layout/components/Footer/components/Branding/components/HCABranding/hcaBranding"; diff --git a/app/viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders.tsx b/app/viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders.tsx index 3922818fc..db92866ec 100644 --- a/app/viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders.tsx +++ b/app/viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders.tsx @@ -17,10 +17,8 @@ import { import { ExportMethod } from "@databiosphere/findable-ui/lib/components/Export/components/ExportMethod/exportMethod"; import { CurrentQuery } from "@databiosphere/findable-ui/lib/components/Export/components/ExportSummary/components/ExportCurrentQuery/exportCurrentQuery"; import { Summary } from "@databiosphere/findable-ui/lib/components/Export/components/ExportSummary/components/ExportSelectedDataSummary/exportSelectedDataSummary"; -import { AzulFileDownload } from "@databiosphere/findable-ui/lib/components/Index/components/AzulFileDownload/azulFileDownload"; import { ANCHOR_TARGET } from "@databiosphere/findable-ui/lib/components/Links/common/entities"; -import type { IdentityChipProps } from "@databiosphere/findable-ui/lib/components/Table/components/TableCell/components/IdentityCell/components/Chips/components/Chip/types"; -import type { IdentityLinkProps } from "@databiosphere/findable-ui/lib/components/Table/components/TableCell/components/IdentityCell/types"; +import { addWordBreaks } from "@databiosphere/findable-ui/lib/components/Table/components/TableCell/common/wordBreaks"; import { ViewContext } from "@databiosphere/findable-ui/lib/config/entities"; import { FileFacet } from "@databiosphere/findable-ui/lib/hooks/useFileManifest/common/entities"; import { @@ -31,7 +29,6 @@ import { import { FileManifestState } from "@databiosphere/findable-ui/lib/providers/fileManifestState"; import { SIZE } from "@databiosphere/findable-ui/lib/styles/common/constants/size"; import { CHIP_PROPS } from "@databiosphere/findable-ui/lib/styles/common/mui/chip"; -import { TYPOGRAPHY_PROPS } from "@databiosphere/findable-ui/lib/styles/common/mui/typography"; import { CategoryKeyLabel } from "@databiosphere/findable-ui/lib/viewModelBuilders/common/entities"; import { mapCategoryKeyLabel, @@ -40,8 +37,6 @@ import { import { ChipProps as MChipProps, FadeProps as MFadeProps, - Tooltip, - Typography, } from "@mui/material"; import React, { ComponentProps, ReactNode } from "react"; import { @@ -89,6 +84,7 @@ import { getDocumentId, getDonorId, getFileDataModalities, + getFileFolderPath, getFileFormat, getFileName, getFileSize, @@ -116,23 +112,23 @@ import { isProductionEnvironment } from "../../../../config/utils"; import { SUMMARY_DISPLAY_TEXT } from "./summaryMapper/constants"; import { mapExportSummary } from "./summaryMapper/summaryMapper"; -/** - * Build props for the activity IdentityCell component from the given activities response. - * The document ID is the title, in plain text as activities have no detail page. The activity type renders as a chip, - * and the subtitle is the dataset title, or the dataset count when there is more than one. - * @param response - Response model return from index/activities API endpoint. - * @returns model to be used as props for the IdentityCell component. - */ export const buildActivityIdentity = ( response: ActivitiesResponse ): React.ComponentProps => { return { - chips: getIdentityChips("activity type", [getActivityType(response)]), - subtitle: getIdentityDatasetsSubtitle(response), + maxLines: 2, title: { label: getDocumentId(response), url: "" }, }; }; +export const buildActivityType = ( + response: ActivitiesResponse +): React.ComponentProps => { + return { + value: getActivityType(response), + }; +}; + /** * Build props for aggregated data modality NTagCell component from the given response. * @param response - Response model return from API. @@ -223,24 +219,31 @@ export const buildAlertExportWarning = ( }; }; -/** - * Build props for the biosample IdentityCell component from the given biosamples response. - * The biosample ID is the title, in plain text as biosamples have no detail page. The biosample type, anatomical site - * and organism type values render as chips, and the subtitle is the dataset title, or the dataset count when there is - * more than one. - * @param response - Response model return from index/biosamples API endpoint. - * @returns model to be used as props for the IdentityCell component. - */ +export const buildAnatomicalSite = ( + response: BioSamplesResponse +): React.ComponentProps => { + return { + value: getAnatomicalSite(response), + }; +}; + export const buildBioSampleIdentity = ( response: BioSamplesResponse ): React.ComponentProps => { return { - chips: getBioSampleIdentityChips(response), - subtitle: getIdentityDatasetsSubtitle(response), + maxLines: 2, title: { label: getBioSampleId(response), url: "" }, }; }; +export const buildBioSampleType = ( + response: BioSamplesResponse +): React.ComponentProps => { + return { + value: getBioSampleType(response), + }; +}; + /** * Build biosample types NTagCell component from the given entity response. * @param response - Response model return from Azul that includes aggregated biosamples. @@ -268,6 +271,24 @@ export const buildCohortDownloadSectionProps = ( return { viewContext }; }; +export const buildConsentGroup = ( + datasetsResponse: DatasetsResponse +): React.ComponentProps => { + return { + label: getPluralizedMetadataLabel(METADATA_KEY.CONSENT_CODE), + values: getConsentGroup(datasetsResponse), + }; +}; + +export const buildDatasetAccess = ( + datasetsResponse: DatasetsResponse +): React.ComponentProps => { + return { + color: getDatasetAccessColor(datasetsResponse), + label: getDatasetAccessStatus(datasetsResponse), + }; +}; + /** * Returns AccessibilityBadge component from the given datasets response. * @param datasetsResponse - Response model return from datasets API. @@ -608,17 +629,11 @@ export const buildDatasetHero = ( }; }; -/** - * Build props for the dataset IdentityCell component from the given datasets response. - * The title links to the dataset, and the access, identifier and consent group values render as chips. - * @param datasetsResponse - Response model return from datasets API. - * @returns model to be used as props for the IdentityCell component. - */ export const buildDatasetIdentity = ( datasetsResponse: DatasetsResponse ): React.ComponentProps => { return { - chips: getDatasetIdentityChips(datasetsResponse), + maxLines: 2, title: { label: getDatasetTitle(datasetsResponse), url: buildDatasetPath(datasetsResponse), @@ -626,6 +641,15 @@ export const buildDatasetIdentity = ( }; }; +export const buildDatasetTitles = ( + response: AggregatedDatasetResponse +): React.ComponentProps => { + return { + label: getPluralizedMetadataLabel(METADATA_KEY.DATASET_NAME), + values: getAggregatedDatasetTitles(response), + }; +}; + /** * Build dataset ID NTagCell component from the given entity response. * @param response - Response model return from Azul that includes aggregated datasets. @@ -710,37 +734,15 @@ export const buildDiagnosesPhenotype = ( }; }; -/** - * Build props for the donor IdentityCell component from the given donors response. - * The donor ID is the title, in plain text as donors have no detail page. The organism type, phenotypic sex and - * reported ethnicity values render as chips, and the subtitle is the dataset title, or the dataset count when there is - * more than one. - * @param response - Response model return from index/donors API endpoint. - * @returns model to be used as props for the IdentityCell component. - */ export const buildDonorIdentity = ( response: DonorsResponse ): React.ComponentProps => { return { - chips: getDonorIdentityChips(response), - subtitle: getIdentityDatasetsSubtitle(response), + maxLines: 2, title: { label: getDonorId(response), url: "" }, }; }; -/** - * Build props for DRS URI CopyCell component from the given files response. - * @param filesResponse - Response model return from index/files API endpoint. - * @returns model to be used as props for the CopyCell component. - */ -export const buildDRSURI = ( - filesResponse: FilesResponse -): React.ComponentProps => { - return { - value: processEntityValue(filesResponse.files, "drs_uri"), - }; -}; - /** * Build props for ExportCurrentQuery component. * @param datasetsResponse - Response model returned from datasets API. @@ -1048,64 +1050,57 @@ export const buildExportToTerra = ( }; }; -/** - * Build props for file download AzulFileDownload component, with a tooltip component used to display a message when the file is not available for download. - * Downloads use azul_url but are only enabled when azul_mirror_uri is present. - * @param response - Response model returned from index/files API endpoint. - * @returns model to be used as props for the AzulFileDownload component. - */ -export const buildFileDownloadWithTooltip = ( +export const buildFileDownloadMenu = ( response: FilesResponse -): ComponentProps => { +): React.ComponentProps => { const dataset = response.datasets[0]; const mirrorUri = processEntityValue( response.files, "azul_mirror_uri", LABEL.EMPTY ); - // Only provide download URL if mirror URI exists (enables the download button) - const url = mirrorUri - ? processEntityValue(response.files, "azul_url", LABEL.EMPTY) - : undefined; - // Determine the entity name - const entityName = processEntityValue(response.files, "file_name"); - // Determine the tooltip title based on whether the file is available for download or not. - const title = url - ? "This open-access file is freely downloadable from AWS Open Data, with no data transfer fees." - : "Direct file downloads are currently only available for open-access files."; - - // Return the download component as a child component of the tooltip. return { - arrow: true, - children: ( - - ), - title, + drsUri: processEntityValue(response.files, "drs_uri"), + entityName: processEntityValue(response.files, "file_name"), + relatedEntityId: dataset.dataset_id[0], + relatedEntityName: dataset.title[0], + url: mirrorUri + ? processEntityValue(response.files, "azul_url", LABEL.EMPTY) + : undefined, + }; +}; + +export const buildFileDataModality = ( + response: FilesResponse +): React.ComponentProps => { + return { + label: getPluralizedMetadataLabel(METADATA_KEY.DATA_MODALITY), + values: getFileDataModalities(response), + }; +}; + +export const buildFileFormat = ( + response: FilesResponse +): React.ComponentProps => { + return { + value: getFileFormat(response), }; }; -/** - * Build props for the file IdentityCell component from the given files response. - * The file name is the title, in plain text as files have no detail page. The file format and data modality values - * render as chips, and the subtitle is the dataset title, or the dataset count when there is more than one. - * @param response - Response model return from index/files API endpoint. - * @returns model to be used as props for the IdentityCell component. - */ export const buildFileIdentity = ( response: FilesResponse ): React.ComponentProps => { + const folderPath = getFileFolderPath(response); return { - chips: [ - ...getIdentityChips("format", [getFileFormat(response)]), - ...getIdentityChips("data modality", getFileDataModalities(response)), - ], - subtitle: getIdentityDatasetsSubtitle(response), - title: { label: getFileName(response), url: "" }, + maxLines: 2, + subtitle: folderPath + ? { label: addWordBreaks(folderPath), url: "" } + : undefined, + title: { + TypographyProps: { sx: { wordBreak: "break-all" } }, + label: getFileName(response), + url: "", + }, }; }; @@ -1163,6 +1158,14 @@ export const buildManifestDownload = ( }; }; +export const buildOrganismType = ( + response: DonorsResponse +): React.ComponentProps => { + return { + value: getOrganismType(response), + }; +}; + /** * Build props for organism type NTagCell component from the given entity response. * @param response - Response model return from Azul that includes aggregated donors. @@ -1177,6 +1180,14 @@ export const buildOrganismTypes = ( }; }; +export const buildPhenotypicSex = ( + response: DonorsResponse +): React.ComponentProps => { + return { + value: getPhenotypicSex(response), + }; +}; + /** * Build props for phenotypic sex NTagCell component from the given donors response. * @param response - Response model return from index/donors API endpoint. @@ -1204,6 +1215,15 @@ export const buildPrepMaterialName = ( }; }; +export const buildRegisteredIdentifiers = ( + datasetsResponse: DatasetsResponse +): React.ComponentProps => { + return { + label: getPluralizedMetadataLabel(METADATA_KEY.DBGAP_ID), + values: getDatasetRegisteredIdentifiers(datasetsResponse), + }; +}; + /** * Build reported ethnicities NTagCell component from the given donors response. Naming is singular here to indicate * ethnicities are pulled from the core donor entity, even though the return value is an array. @@ -1249,22 +1269,6 @@ function getAccessibleTransition( }; } -/** - * Returns the biosample IdentityCell chips from the given biosamples response: biosample type, then anatomical site, - * then one chip per organism type. Unspecified values render no chip. - * @param response - Response model return from index/biosamples API endpoint. - * @returns IdentityCell chip props. - */ -function getBioSampleIdentityChips( - response: BioSamplesResponse -): IdentityChipProps[] { - return [ - ...getIdentityChips("biosample type", [getBioSampleType(response)]), - ...getIdentityChips("anatomical site", [getAnatomicalSite(response)]), - ...getIdentityChips("organism type", getAggregatedOrganismTypes(response)), - ]; -} - /** * Returns the dataset access chip color (success when access is granted, otherwise warning) from the given datasets * response. @@ -1386,31 +1390,6 @@ export function getDatasetId(datasetsResponse: DatasetsResponse): string { ); } -/** - * Returns the dataset IdentityCell chips from the given datasets response: access, then one chip per specified - * registered identifier, then one chip per specified consent group. - * @param datasetsResponse - Response model return from datasets API. - * @returns IdentityCell chip props. - */ -function getDatasetIdentityChips( - datasetsResponse: DatasetsResponse -): IdentityChipProps[] { - return [ - { - color: getDatasetAccessColor(datasetsResponse), - label: getIdentityChipLabel( - "access", - getDatasetAccessStatus(datasetsResponse) - ), - }, - ...getIdentityChips( - "identifier", - getDatasetRegisteredIdentifiers(datasetsResponse) - ), - ...getIdentityChips("consent group", getConsentGroup(datasetsResponse)), - ]; -} - /** * Returns dataset title from the given datasets response. * @param datasetsResponse - Response model return from datasets API. @@ -1420,20 +1399,6 @@ export function getDatasetTitle(datasetsResponse: DatasetsResponse): string { return processEntityValue(datasetsResponse.datasets, "title", LABEL.NONE); } -/** - * Returns the donor IdentityCell chips from the given donors response: organism type, then phenotypic sex, then one - * chip per reported ethnicity. Unspecified values render no chip. - * @param response - Response model return from index/donors API endpoint. - * @returns IdentityCell chip props. - */ -function getDonorIdentityChips(response: DonorsResponse): IdentityChipProps[] { - return [ - ...getIdentityChips("organism type", [getOrganismType(response)]), - ...getIdentityChips("phenotypic sex", [getPhenotypicSex(response)]), - ...getIdentityChips("reported ethnicity", getReportedEthnicities(response)), - ]; -} - /** * Returns current queries from the given selected file facets. * @param filters - filters. @@ -1668,62 +1633,6 @@ function getFormFacets(fileManifestState: FileManifestState): FormFacet { }; } -/** - * Returns an IdentityCell chip label: the field name in light ink, then its value. - * @param label - Field name, in lowercase. - * @param value - Field value. - * @returns chip label. - */ -function getIdentityChipLabel(label: string, value: string): ReactNode { - return ( - <> - - {label} - {" "} - - {value} - - - ); -} - -/** - * Returns one IdentityCell chip per specified value, each labelled with the given field name. - * @param label - Field name, in lowercase. - * @param values - Field values. - * @returns IdentityCell chip props. - */ -function getIdentityChips( - label: string, - values: string[] -): IdentityChipProps[] { - return values - .filter(isSpecifiedValue) - .map((value) => ({ label: getIdentityChipLabel(label, value) })); -} - -/** - * Returns the IdentityCell subtitle from the given response's aggregated datasets: the dataset title, or the dataset - * count (by unique dataset ID) when there is more than one. The subtitle is plain text; aggregated datasets are - * identified by dataset ID only, and the dataset detail page is keyed by the dataset's entry ID. - * @param response - Response model return from Azul that includes aggregated datasets. - * @returns IdentityCell subtitle props, or undefined when there is no specified dataset. - */ -function getIdentityDatasetsSubtitle( - response: AggregatedDatasetResponse -): IdentityLinkProps | undefined { - const datasetCount = - getAggregatedDatasetIds(response).filter(isSpecifiedValue).length; - if (datasetCount > 1) return { label: `${datasetCount} datasets`, url: "" }; - const [title] = getAggregatedDatasetTitles(response).filter(isSpecifiedValue); - if (!title) return; - return { label: title, url: "" }; -} - /** * Boolean to filter out any selected filters that have dataset ID as the category key. * @param filter - Selected filter. @@ -1822,16 +1731,6 @@ function isResponseReady(datasetsResponse: DatasetsResponse): boolean { return !isLoading; } -/** - * Returns true if the given value is not the "Unspecified" placeholder that the response processors substitute for - * null or empty values. - * @param value - Value. - * @returns true if the value is specified. - */ -function isSpecifiedValue(value: string): boolean { - return value !== LABEL.UNSPECIFIED; -} - /** * Returns true if user is authenticated. * @param viewContext - View context. diff --git a/e2e/anvil/constants.ts b/e2e/anvil/constants.ts index 744cfc267..a95bc86be 100644 --- a/e2e/anvil/constants.ts +++ b/e2e/anvil/constants.ts @@ -27,15 +27,20 @@ export const ANVIL_DATASETS_BACKPAGE_HEADER_NAMES = { }; export const ANVIL_COLUMN_NAMES = { + ACCESS: "Access", ACTIVITY: "Activity", + ACTIVITY_TYPE: "Activity Type", + ANATOMICAL_SITE: "Anatomical Site", BIOSAMPLE: "BioSample", BIOSAMPLE_TYPE: "BioSample Type", + CONSENT_GROUP: "Consent Group", DATASET: "Dataset", DIAGNOSIS: "Diagnosis", DONOR: "Donor", - DRS_URI: "DRS URI", FILE: "File", FILE_DATA_MODALITY: "Data Modality", + FILE_FORMAT: "File Format", + IDENTIFIER: "Identifier", ORGANISM_TYPE: "Organism Type", PHENOTYPE: "Phenotype", PHENOTYPIC_SEX: "Phenotypic Sex", @@ -47,6 +52,9 @@ export const PLURALIZED_METADATA_LABEL = { [ANVIL_COLUMN_NAMES.DIAGNOSIS]: "diagnoses", [ANVIL_COLUMN_NAMES.DATASET]: "dataset names", [ANVIL_COLUMN_NAMES.FILE_DATA_MODALITY]: "data modalities", + [ANVIL_COLUMN_NAMES.FILE_FORMAT]: "file formats", + [ANVIL_COLUMN_NAMES.CONSENT_GROUP]: "consent codes", + [ANVIL_COLUMN_NAMES.IDENTIFIER]: "dbGap ids", [ANVIL_COLUMN_NAMES.ORGANISM_TYPE]: "organism types", [ANVIL_COLUMN_NAMES.PHENOTYPE]: "phenotypes", [ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX]: "phenotypic sexes", @@ -65,6 +73,21 @@ export const ANVIL_DATASETS_PRESELECTED_COLUMNS_BY_NAME = { pluralizedLabel: PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.DATASET], sortable: true, }, + [ANVIL_COLUMN_NAMES.ACCESS]: { + name: ANVIL_COLUMN_NAMES.ACCESS, + sortable: false, + }, + [ANVIL_COLUMN_NAMES.IDENTIFIER]: { + name: ANVIL_COLUMN_NAMES.IDENTIFIER, + pluralizedLabel: PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.IDENTIFIER], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.CONSENT_GROUP]: { + name: ANVIL_COLUMN_NAMES.CONSENT_GROUP, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.CONSENT_GROUP], + sortable: true, + }, [ANVIL_COLUMN_NAMES.ORGANISM_TYPE]: { name: ANVIL_COLUMN_NAMES.ORGANISM_TYPE, pluralizedLabel: @@ -111,6 +134,15 @@ export const ANVIL_ACTIVITIES_PRESELECTED_COLUMNS_BY_NAME = { name: ANVIL_COLUMN_NAMES.ACTIVITY, sortable: true, }, + [ANVIL_COLUMN_NAMES.DATASET]: { + name: ANVIL_COLUMN_NAMES.DATASET, + pluralizedLabel: PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.DATASET], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.ACTIVITY_TYPE]: { + name: ANVIL_COLUMN_NAMES.ACTIVITY_TYPE, + sortable: true, + }, [ANVIL_COLUMN_NAMES.BIOSAMPLE_TYPE]: { name: ANVIL_COLUMN_NAMES.BIOSAMPLE_TYPE, pluralizedLabel: @@ -161,6 +193,25 @@ export const ANVIL_BIOSAMPLES_PRESELECTED_COLUMNS_BY_NAME = { name: ANVIL_COLUMN_NAMES.BIOSAMPLE, sortable: true, }, + [ANVIL_COLUMN_NAMES.DATASET]: { + name: ANVIL_COLUMN_NAMES.DATASET, + pluralizedLabel: PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.DATASET], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.BIOSAMPLE_TYPE]: { + name: ANVIL_COLUMN_NAMES.BIOSAMPLE_TYPE, + sortable: true, + }, + [ANVIL_COLUMN_NAMES.ANATOMICAL_SITE]: { + name: ANVIL_COLUMN_NAMES.ANATOMICAL_SITE, + sortable: true, + }, + [ANVIL_COLUMN_NAMES.ORGANISM_TYPE]: { + name: ANVIL_COLUMN_NAMES.ORGANISM_TYPE, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.ORGANISM_TYPE], + sortable: true, + }, [ANVIL_COLUMN_NAMES.PHENOTYPE]: { name: ANVIL_COLUMN_NAMES.PHENOTYPE, pluralizedLabel: PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.PHENOTYPE], @@ -204,6 +255,25 @@ export const ANVIL_DONORS_PRESELECTED_COLUMNS_BY_NAME = { name: ANVIL_COLUMN_NAMES.DONOR, sortable: true, }, + [ANVIL_COLUMN_NAMES.DATASET]: { + name: ANVIL_COLUMN_NAMES.DATASET, + pluralizedLabel: PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.DATASET], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.ORGANISM_TYPE]: { + name: ANVIL_COLUMN_NAMES.ORGANISM_TYPE, + sortable: true, + }, + [ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX]: { + name: ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX, + sortable: true, + }, + [ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY]: { + name: ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY], + sortable: true, + }, [ANVIL_COLUMN_NAMES.PHENOTYPE]: { name: ANVIL_COLUMN_NAMES.PHENOTYPE, pluralizedLabel: PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.PHENOTYPE], @@ -229,14 +299,20 @@ export const ANVIL_FILES_PRESELECTED_COLUMNS_BY_NAME = { name: ANVIL_COLUMN_NAMES.FILE, sortable: true, }, + [ANVIL_COLUMN_NAMES.DATASET]: { + name: ANVIL_COLUMN_NAMES.DATASET, + pluralizedLabel: PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.DATASET], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.FILE_FORMAT]: { + name: ANVIL_COLUMN_NAMES.FILE_FORMAT, + pluralizedLabel: PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.FILE_FORMAT], + sortable: true, + }, [ANVIL_COLUMN_NAMES.SIZE]: { name: ANVIL_COLUMN_NAMES.SIZE, sortable: true, }, - [ANVIL_COLUMN_NAMES.DRS_URI]: { - name: ANVIL_COLUMN_NAMES.DRS_URI, - sortable: false, - }, [ANVIL_COLUMN_NAMES.ORGANISM_TYPE]: { name: ANVIL_COLUMN_NAMES.ORGANISM_TYPE, pluralizedLabel: @@ -268,4 +344,10 @@ export const ANVIL_FILES_SELECTABLE_COLUMNS_BY_NAME = { pluralizedLabel: PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.DIAGNOSIS], sortable: true, }, + [ANVIL_COLUMN_NAMES.FILE_DATA_MODALITY]: { + name: ANVIL_COLUMN_NAMES.FILE_DATA_MODALITY, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.FILE_DATA_MODALITY], + sortable: true, + }, }; diff --git a/e2e/testFunctions.ts b/e2e/testFunctions.ts index ab4b3c134..2faa9d468 100644 --- a/e2e/testFunctions.ts +++ b/e2e/testFunctions.ts @@ -163,7 +163,9 @@ export async function testSortAzul( // Locator that can be clicked to sort the column const columnSortLocator = page .locator(MUI_CLASSES.TABLE_SORT_LABEL) - .filter({ hasText: columnObject.name }); + .filter({ + hasText: new RegExp(`^${escapeRegExp(columnObject.name)}$`), + }); // Locator for the column sort icon const sortIconLocator = columnSortLocator.locator( MUI_CLASSES.TABLE_SORT_LABEL_ICON diff --git a/site-config/anvil-cmg/category.ts b/site-config/anvil-cmg/category.ts index cb6cc736a..869e4931f 100644 --- a/site-config/anvil-cmg/category.ts +++ b/site-config/anvil-cmg/category.ts @@ -1,5 +1,6 @@ export const ANVIL_CMG_CATEGORY_KEY = { ACCESSIBLE: "accessible", + ACTIVITY_ACTIVITY_TYPE: "activities.activity_type", ACTIVITY_DATA_MODALITY: "activities.data_modality", ACTIVITY_DOCUMENT_ID: "activities.document_id", AZUL_FILE_DOWNLOAD: "azulFileDownload", @@ -7,6 +8,7 @@ export const ANVIL_CMG_CATEGORY_KEY = { BIOSAMPLE_BIOSAMPLE_ID: "biosamples.biosample_id", BIOSAMPLE_BIOSAMPLE_TYPE: "biosamples.biosample_type", BIOSAMPLE_TYPE: "biosample_type", + DATASET_ACCESSIBLE: "datasets.accessible", DATASET_CONSENT_GROUP: "datasets.consent_group", DATASET_ID: "datasets.dataset_id", DATASET_REGISTERED_ID: "datasets.registered_identifier", @@ -17,7 +19,6 @@ export const ANVIL_CMG_CATEGORY_KEY = { DONOR_ORGANISM_TYPE: "donors.organism_type", DONOR_PHENOTYPIC_SEX: "donors.phenotypic_sex", DONOR_REPORTED_ETHNICITY: "donors.reported_ethnicity", - DRS_URI: "files.drs_uri", FILE_DATA_MODALITY: "files.data_modality", FILE_FILE_FORMAT: "files.file_format", FILE_FILE_NAME: "files.file_name", @@ -29,6 +30,7 @@ export const ANVIL_CMG_CATEGORY_KEY = { export const ANVIL_CMG_CATEGORY_LABEL = { ACCESSIBLE: "Access", + ACTIVITY_ACTIVITY_TYPE: "Activity Type", ACTIVITY_DATA_MODALITY: "Data Modality", ACTIVITY_DOCUMENT_ID: "Activity", AZUL_FILE_DOWNLOAD: " ", @@ -36,6 +38,7 @@ export const ANVIL_CMG_CATEGORY_LABEL = { BIOSAMPLE_BIOSAMPLE_ID: "BioSample", BIOSAMPLE_BIOSAMPLE_TYPE: "BioSample Type", BIOSAMPLE_TYPE: "BioSample Type", + DATASET_ACCESSIBLE: "Access", DATASET_CONSENT_GROUP: "Consent Group", DATASET_ID: "Dataset Name", DATASET_REGISTERED_ID: "Identifier", @@ -46,7 +49,6 @@ export const ANVIL_CMG_CATEGORY_LABEL = { DONOR_ORGANISM_TYPE: "Organism Type", DONOR_PHENOTYPIC_SEX: "Phenotypic Sex", DONOR_REPORTED_ETHNICITY: "Reported Ethnicity", - DRS_URI: "DRS URI", FILE_DATA_MODALITY: "Data Modality", FILE_FILE_FORMAT: "File Format", FILE_FILE_NAME: "File", diff --git a/site-config/anvil-cmg/dev/index/activitiesEntityConfig.ts b/site-config/anvil-cmg/dev/index/activitiesEntityConfig.ts index 31142732b..e795bda69 100644 --- a/site-config/anvil-cmg/dev/index/activitiesEntityConfig.ts +++ b/site-config/anvil-cmg/dev/index/activitiesEntityConfig.ts @@ -14,6 +14,7 @@ import { } from "../../category"; import { entityListSlot } from "../ui/entityList"; import { entityViewSlot } from "../ui/entityView"; +import { getValueColumnSizing } from "./common/columns"; /** * Entity config object responsible for config related to the /activities route. @@ -40,6 +41,24 @@ export const activitiesEntityConfig: EntityConfig = { id: ANVIL_CMG_CATEGORY_KEY.ACTIVITY_DOCUMENT_ID, width: { max: "1.5fr", min: "340px" }, }, + { + componentConfig: { + component: C.NTagCell, + viewBuilder: V.buildDatasetTitles, + } as ComponentConfig, + header: ANVIL_CMG_CATEGORY_LABEL.DATASET_TITLE, + id: ANVIL_CMG_CATEGORY_KEY.DATASET_TITLE, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DATASET_TITLE), + }, + { + componentConfig: { + component: C.BasicCell, + viewBuilder: V.buildActivityType, + } as ComponentConfig, + header: ANVIL_CMG_CATEGORY_LABEL.ACTIVITY_ACTIVITY_TYPE, + id: ANVIL_CMG_CATEGORY_KEY.ACTIVITY_ACTIVITY_TYPE, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.ACTIVITY_ACTIVITY_TYPE), + }, { componentConfig: { component: C.BasicCell, @@ -47,7 +66,9 @@ export const activitiesEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.BIOSAMPLE_BIOSAMPLE_TYPE, id: ANVIL_CMG_CATEGORY_KEY.BIOSAMPLE_BIOSAMPLE_TYPE, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing( + ANVIL_CMG_CATEGORY_KEY.BIOSAMPLE_BIOSAMPLE_TYPE + ), }, { componentConfig: { @@ -56,7 +77,7 @@ export const activitiesEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.DONOR_ORGANISM_TYPE, id: ANVIL_CMG_CATEGORY_KEY.DONOR_ORGANISM_TYPE, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DONOR_ORGANISM_TYPE), }, { componentConfig: { @@ -65,7 +86,7 @@ export const activitiesEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.DONOR_PHENOTYPIC_SEX, id: ANVIL_CMG_CATEGORY_KEY.DONOR_PHENOTYPIC_SEX, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DONOR_PHENOTYPIC_SEX), }, { componentConfig: { @@ -74,7 +95,9 @@ export const activitiesEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.DONOR_REPORTED_ETHNICITY, id: ANVIL_CMG_CATEGORY_KEY.DONOR_REPORTED_ETHNICITY, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing( + ANVIL_CMG_CATEGORY_KEY.DONOR_REPORTED_ETHNICITY + ), }, { componentConfig: { @@ -83,7 +106,7 @@ export const activitiesEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.DIAGNOSIS_PHENOTYPE, id: ANVIL_CMG_CATEGORY_KEY.DIAGNOSIS_PHENOTYPE, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DIAGNOSIS_PHENOTYPE), }, { componentConfig: { @@ -92,7 +115,7 @@ export const activitiesEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.DIAGNOSE_DISEASE, id: ANVIL_CMG_CATEGORY_KEY.DIAGNOSE_DISEASE, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DIAGNOSE_DISEASE), }, { componentConfig: { @@ -101,7 +124,7 @@ export const activitiesEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.FILE_DATA_MODALITY, id: ANVIL_CMG_CATEGORY_KEY.FILE_DATA_MODALITY, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.FILE_DATA_MODALITY), }, ], tableOptions: { diff --git a/site-config/anvil-cmg/dev/index/biosamplesEntityConfig.ts b/site-config/anvil-cmg/dev/index/biosamplesEntityConfig.ts index de3e076a5..24d5e45f3 100644 --- a/site-config/anvil-cmg/dev/index/biosamplesEntityConfig.ts +++ b/site-config/anvil-cmg/dev/index/biosamplesEntityConfig.ts @@ -14,6 +14,7 @@ import { } from "../../category"; import { entityListSlot } from "../ui/entityList"; import { entityViewSlot } from "../ui/entityView"; +import { getValueColumnSizing } from "./common/columns"; /** * Entity config object responsible for config related to the /biosamples route. @@ -40,6 +41,46 @@ export const biosamplesEntityConfig: EntityConfig = { id: ANVIL_CMG_CATEGORY_KEY.BIOSAMPLE_BIOSAMPLE_ID, width: { max: "1.5fr", min: "340px" }, }, + { + componentConfig: { + component: C.NTagCell, + viewBuilder: V.buildDatasetTitles, + } as ComponentConfig, + header: ANVIL_CMG_CATEGORY_LABEL.DATASET_TITLE, + id: ANVIL_CMG_CATEGORY_KEY.DATASET_TITLE, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DATASET_TITLE), + }, + { + componentConfig: { + component: C.BasicCell, + viewBuilder: V.buildBioSampleType, + } as ComponentConfig, + header: ANVIL_CMG_CATEGORY_LABEL.BIOSAMPLE_BIOSAMPLE_TYPE, + id: ANVIL_CMG_CATEGORY_KEY.BIOSAMPLE_BIOSAMPLE_TYPE, + ...getValueColumnSizing( + ANVIL_CMG_CATEGORY_KEY.BIOSAMPLE_BIOSAMPLE_TYPE + ), + }, + { + componentConfig: { + component: C.BasicCell, + viewBuilder: V.buildAnatomicalSite, + } as ComponentConfig, + header: ANVIL_CMG_CATEGORY_LABEL.BIOSAMPLE_ANATOMICAL_SITE, + id: ANVIL_CMG_CATEGORY_KEY.BIOSAMPLE_ANATOMICAL_SITE, + ...getValueColumnSizing( + ANVIL_CMG_CATEGORY_KEY.BIOSAMPLE_ANATOMICAL_SITE + ), + }, + { + componentConfig: { + component: C.NTagCell, + viewBuilder: V.buildOrganismTypes, + } as ComponentConfig, + header: ANVIL_CMG_CATEGORY_LABEL.DONOR_ORGANISM_TYPE, + id: ANVIL_CMG_CATEGORY_KEY.DONOR_ORGANISM_TYPE, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DONOR_ORGANISM_TYPE), + }, { componentConfig: { component: C.NTagCell, @@ -47,7 +88,7 @@ export const biosamplesEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.DONOR_PHENOTYPIC_SEX, id: ANVIL_CMG_CATEGORY_KEY.DONOR_PHENOTYPIC_SEX, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DONOR_PHENOTYPIC_SEX), }, { componentConfig: { @@ -56,7 +97,9 @@ export const biosamplesEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.DONOR_REPORTED_ETHNICITY, id: ANVIL_CMG_CATEGORY_KEY.DONOR_REPORTED_ETHNICITY, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing( + ANVIL_CMG_CATEGORY_KEY.DONOR_REPORTED_ETHNICITY + ), }, { componentConfig: { @@ -65,7 +108,7 @@ export const biosamplesEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.DIAGNOSIS_PHENOTYPE, id: ANVIL_CMG_CATEGORY_KEY.DIAGNOSIS_PHENOTYPE, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DIAGNOSIS_PHENOTYPE), }, { componentConfig: { @@ -74,7 +117,7 @@ export const biosamplesEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.DIAGNOSE_DISEASE, id: ANVIL_CMG_CATEGORY_KEY.DIAGNOSE_DISEASE, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DIAGNOSE_DISEASE), }, { componentConfig: { @@ -83,7 +126,7 @@ export const biosamplesEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.FILE_DATA_MODALITY, id: ANVIL_CMG_CATEGORY_KEY.FILE_DATA_MODALITY, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.FILE_DATA_MODALITY), }, ], tableOptions: { diff --git a/site-config/anvil-cmg/dev/index/common/columns.ts b/site-config/anvil-cmg/dev/index/common/columns.ts new file mode 100644 index 000000000..7d6b08f6b --- /dev/null +++ b/site-config/anvil-cmg/dev/index/common/columns.ts @@ -0,0 +1,32 @@ +import { ColumnConfig } from "@databiosphere/findable-ui/lib/config/entities"; +import { RowData } from "@tanstack/react-table"; +import { ANVIL_CMG_CATEGORY_KEY } from "../../../category"; + +type ColumnSizing = Pick, "meta" | "width">; + +const DEFAULT_MAX_WIDTH = 280; + +// 90th percentile value length in the anvil15 catalog at ~7.4px/char, clamped to 120-400px. +const MAX_WIDTH: Record = { + [ANVIL_CMG_CATEGORY_KEY.ACTIVITY_ACTIVITY_TYPE]: 120, + [ANVIL_CMG_CATEGORY_KEY.BIOSAMPLE_ANATOMICAL_SITE]: 290, + [ANVIL_CMG_CATEGORY_KEY.BIOSAMPLE_BIOSAMPLE_TYPE]: 120, + [ANVIL_CMG_CATEGORY_KEY.DATASET_CONSENT_GROUP]: 150, + [ANVIL_CMG_CATEGORY_KEY.DATASET_REGISTERED_ID]: 120, + [ANVIL_CMG_CATEGORY_KEY.DATASET_TITLE]: 400, + [ANVIL_CMG_CATEGORY_KEY.DIAGNOSE_DISEASE]: 400, + [ANVIL_CMG_CATEGORY_KEY.DIAGNOSIS_PHENOTYPE]: 400, + [ANVIL_CMG_CATEGORY_KEY.DONOR_ORGANISM_TYPE]: 120, + [ANVIL_CMG_CATEGORY_KEY.DONOR_PHENOTYPIC_SEX]: 120, + [ANVIL_CMG_CATEGORY_KEY.DONOR_REPORTED_ETHNICITY]: 270, + [ANVIL_CMG_CATEGORY_KEY.FILE_DATA_MODALITY]: 300, + [ANVIL_CMG_CATEGORY_KEY.FILE_FILE_FORMAT]: 120, + [ANVIL_CMG_CATEGORY_KEY.FILE_FILE_SIZE]: 120, +}; + +export function getValueColumnSizing(key: string): ColumnSizing { + return { + meta: { maxLines: 2 }, + width: `fit-content(${MAX_WIDTH[key] ?? DEFAULT_MAX_WIDTH}px)`, + }; +} diff --git a/site-config/anvil-cmg/dev/index/datasetsEntityConfig.ts b/site-config/anvil-cmg/dev/index/datasetsEntityConfig.ts index 9356b9693..6be1d3ab8 100644 --- a/site-config/anvil-cmg/dev/index/datasetsEntityConfig.ts +++ b/site-config/anvil-cmg/dev/index/datasetsEntityConfig.ts @@ -20,6 +20,7 @@ import { sideColumn } from "../detail/dataset/overviewSideColumn"; import { top } from "../detail/dataset/top"; import { entityListSlot } from "../ui/datasetsEntityList"; import { entityViewSlot } from "../ui/entityView"; +import { getValueColumnSizing } from "./common/columns"; /** * Entity config object responsible for config related to the /datasets route. @@ -57,6 +58,34 @@ export const datasetsEntityConfig: EntityConfig = { id: ANVIL_CMG_CATEGORY_KEY.DATASET_TITLE, width: { max: "1.5fr", min: "340px" }, }, + { + componentConfig: { + component: C.StatusBadge, + viewBuilder: V.buildDatasetAccess, + } as ComponentConfig, + enableSorting: false, + header: ANVIL_CMG_CATEGORY_LABEL.DATASET_ACCESSIBLE, + id: ANVIL_CMG_CATEGORY_KEY.DATASET_ACCESSIBLE, + width: "max-content", + }, + { + componentConfig: { + component: C.NTagCell, + viewBuilder: V.buildRegisteredIdentifiers, + } as ComponentConfig, + header: ANVIL_CMG_CATEGORY_LABEL.DATASET_REGISTERED_ID, + id: ANVIL_CMG_CATEGORY_KEY.DATASET_REGISTERED_ID, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DATASET_REGISTERED_ID), + }, + { + componentConfig: { + component: C.NTagCell, + viewBuilder: V.buildConsentGroup, + } as ComponentConfig, + header: ANVIL_CMG_CATEGORY_LABEL.DATASET_CONSENT_GROUP, + id: ANVIL_CMG_CATEGORY_KEY.DATASET_CONSENT_GROUP, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DATASET_CONSENT_GROUP), + }, { componentConfig: { component: C.NTagCell, @@ -64,7 +93,7 @@ export const datasetsEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.DONOR_ORGANISM_TYPE, id: ANVIL_CMG_CATEGORY_KEY.DONOR_ORGANISM_TYPE, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DONOR_ORGANISM_TYPE), }, { componentConfig: { @@ -73,7 +102,7 @@ export const datasetsEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.DONOR_PHENOTYPIC_SEX, id: ANVIL_CMG_CATEGORY_KEY.DONOR_PHENOTYPIC_SEX, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DONOR_PHENOTYPIC_SEX), }, { componentConfig: { @@ -82,7 +111,9 @@ export const datasetsEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.DONOR_REPORTED_ETHNICITY, id: ANVIL_CMG_CATEGORY_KEY.DONOR_REPORTED_ETHNICITY, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing( + ANVIL_CMG_CATEGORY_KEY.DONOR_REPORTED_ETHNICITY + ), }, { componentConfig: { @@ -91,7 +122,7 @@ export const datasetsEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.DIAGNOSIS_PHENOTYPE, id: ANVIL_CMG_CATEGORY_KEY.DIAGNOSIS_PHENOTYPE, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DIAGNOSIS_PHENOTYPE), }, { componentConfig: { @@ -100,7 +131,7 @@ export const datasetsEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.DIAGNOSE_DISEASE, id: ANVIL_CMG_CATEGORY_KEY.DIAGNOSE_DISEASE, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DIAGNOSE_DISEASE), }, { componentConfig: { @@ -109,7 +140,7 @@ export const datasetsEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.FILE_DATA_MODALITY, id: ANVIL_CMG_CATEGORY_KEY.FILE_DATA_MODALITY, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.FILE_DATA_MODALITY), }, ], tableOptions: { diff --git a/site-config/anvil-cmg/dev/index/donorsEntityConfig.ts b/site-config/anvil-cmg/dev/index/donorsEntityConfig.ts index e186feffc..a66c042c7 100644 --- a/site-config/anvil-cmg/dev/index/donorsEntityConfig.ts +++ b/site-config/anvil-cmg/dev/index/donorsEntityConfig.ts @@ -14,6 +14,7 @@ import { } from "../../category"; import { entityListSlot } from "../ui/entityList"; import { entityViewSlot } from "../ui/entityView"; +import { getValueColumnSizing } from "./common/columns"; /** * Entity config object responsible for config related to the /donors route. @@ -40,6 +41,44 @@ export const donorsEntityConfig: EntityConfig = { id: ANVIL_CMG_CATEGORY_KEY.DONOR_DONOR_ID, width: { max: "1.5fr", min: "340px" }, }, + { + componentConfig: { + component: C.NTagCell, + viewBuilder: V.buildDatasetTitles, + } as ComponentConfig, + header: ANVIL_CMG_CATEGORY_LABEL.DATASET_TITLE, + id: ANVIL_CMG_CATEGORY_KEY.DATASET_TITLE, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DATASET_TITLE), + }, + { + componentConfig: { + component: C.BasicCell, + viewBuilder: V.buildOrganismType, + } as ComponentConfig, + header: ANVIL_CMG_CATEGORY_LABEL.DONOR_ORGANISM_TYPE, + id: ANVIL_CMG_CATEGORY_KEY.DONOR_ORGANISM_TYPE, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DONOR_ORGANISM_TYPE), + }, + { + componentConfig: { + component: C.BasicCell, + viewBuilder: V.buildPhenotypicSex, + } as ComponentConfig, + header: ANVIL_CMG_CATEGORY_LABEL.DONOR_PHENOTYPIC_SEX, + id: ANVIL_CMG_CATEGORY_KEY.DONOR_PHENOTYPIC_SEX, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DONOR_PHENOTYPIC_SEX), + }, + { + componentConfig: { + component: C.NTagCell, + viewBuilder: V.buildReportedEthnicity, + } as ComponentConfig, + header: ANVIL_CMG_CATEGORY_LABEL.DONOR_REPORTED_ETHNICITY, + id: ANVIL_CMG_CATEGORY_KEY.DONOR_REPORTED_ETHNICITY, + ...getValueColumnSizing( + ANVIL_CMG_CATEGORY_KEY.DONOR_REPORTED_ETHNICITY + ), + }, { componentConfig: { component: C.NTagCell, @@ -47,7 +86,7 @@ export const donorsEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.DIAGNOSIS_PHENOTYPE, id: ANVIL_CMG_CATEGORY_KEY.DIAGNOSIS_PHENOTYPE, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DIAGNOSIS_PHENOTYPE), }, { componentConfig: { @@ -56,7 +95,7 @@ export const donorsEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.DIAGNOSE_DISEASE, id: ANVIL_CMG_CATEGORY_KEY.DIAGNOSE_DISEASE, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DIAGNOSE_DISEASE), }, { componentConfig: { @@ -65,7 +104,7 @@ export const donorsEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.FILE_DATA_MODALITY, id: ANVIL_CMG_CATEGORY_KEY.FILE_DATA_MODALITY, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.FILE_DATA_MODALITY), }, ], tableOptions: { diff --git a/site-config/anvil-cmg/dev/index/filesEntityConfig.ts b/site-config/anvil-cmg/dev/index/filesEntityConfig.ts index 2fd7507fa..b42625693 100644 --- a/site-config/anvil-cmg/dev/index/filesEntityConfig.ts +++ b/site-config/anvil-cmg/dev/index/filesEntityConfig.ts @@ -6,7 +6,7 @@ import { SORT_DIRECTION, } from "@databiosphere/findable-ui/lib/config/entities"; import { EXPLORE_MODE } from "@databiosphere/findable-ui/lib/hooks/useExploreMode/types"; -import { Tooltip } from "@mui/material"; +import { TABLE_CELL_PROPS } from "@databiosphere/findable-ui/lib/styles/common/mui/tableCell"; import { FilesResponse } from "../../../../app/apis/azul/anvil-cmg/common/responses"; import * as C from "../../../../app/components"; import * as V from "../../../../app/viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders"; @@ -16,17 +16,19 @@ import { } from "../../category"; import { entityViewSlot } from "../ui/entityView"; import { filesEntityListSlot } from "../ui/filesEntityList"; +import { getValueColumnSizing } from "./common/columns"; export const downloadColumn: ColumnConfig = { componentConfig: { - component: Tooltip, - viewBuilder: V.buildFileDownloadWithTooltip, - }, + component: C.FileDownloadMenu, + viewBuilder: V.buildFileDownloadMenu, + } as ComponentConfig, enableHiding: false, enableSorting: false, header: ANVIL_CMG_CATEGORY_LABEL.AZUL_FILE_DOWNLOAD, id: ANVIL_CMG_CATEGORY_KEY.AZUL_FILE_DOWNLOAD, - width: { max: "auto", min: "76px" }, + meta: { padding: TABLE_CELL_PROPS.PADDING.CHECKBOX }, + width: "max-content", }; /** @@ -56,24 +58,32 @@ export const filesEntityConfig: EntityConfig = { id: ANVIL_CMG_CATEGORY_KEY.FILE_FILE_NAME, width: { max: "1.5fr", min: "340px" }, }, + { + componentConfig: { + component: C.NTagCell, + viewBuilder: V.buildDatasetTitles, + } as ComponentConfig, + header: ANVIL_CMG_CATEGORY_LABEL.DATASET_TITLE, + id: ANVIL_CMG_CATEGORY_KEY.DATASET_TITLE, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DATASET_TITLE), + }, { componentConfig: { component: C.BasicCell, - viewBuilder: V.buildFileSize, + viewBuilder: V.buildFileFormat, } as ComponentConfig, - header: ANVIL_CMG_CATEGORY_LABEL.FILE_FILE_SIZE, - id: ANVIL_CMG_CATEGORY_KEY.FILE_FILE_SIZE, - width: { max: "auto", min: "120px" }, + header: ANVIL_CMG_CATEGORY_LABEL.FILE_FILE_FORMAT, + id: ANVIL_CMG_CATEGORY_KEY.FILE_FILE_FORMAT, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.FILE_FILE_FORMAT), }, { componentConfig: { - component: C.CopyCell, - viewBuilder: V.buildDRSURI, - } as ComponentConfig, - enableSorting: false, - header: ANVIL_CMG_CATEGORY_LABEL.DRS_URI, - id: ANVIL_CMG_CATEGORY_KEY.DRS_URI, - width: { max: "1fr", min: "120px" }, + component: C.BasicCell, + viewBuilder: V.buildFileSize, + } as ComponentConfig, + header: ANVIL_CMG_CATEGORY_LABEL.FILE_FILE_SIZE, + id: ANVIL_CMG_CATEGORY_KEY.FILE_FILE_SIZE, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.FILE_FILE_SIZE), }, { componentConfig: { @@ -82,7 +92,7 @@ export const filesEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.DONOR_ORGANISM_TYPE, id: ANVIL_CMG_CATEGORY_KEY.DONOR_ORGANISM_TYPE, - width: { max: "1fr", min: "140px" }, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DONOR_ORGANISM_TYPE), }, { componentConfig: { @@ -91,7 +101,7 @@ export const filesEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.DONOR_PHENOTYPIC_SEX, id: ANVIL_CMG_CATEGORY_KEY.DONOR_PHENOTYPIC_SEX, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DONOR_PHENOTYPIC_SEX), }, { componentConfig: { @@ -100,7 +110,9 @@ export const filesEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.DONOR_REPORTED_ETHNICITY, id: ANVIL_CMG_CATEGORY_KEY.DONOR_REPORTED_ETHNICITY, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing( + ANVIL_CMG_CATEGORY_KEY.DONOR_REPORTED_ETHNICITY + ), }, { componentConfig: { @@ -109,7 +121,7 @@ export const filesEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.DIAGNOSIS_PHENOTYPE, id: ANVIL_CMG_CATEGORY_KEY.DIAGNOSIS_PHENOTYPE, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DIAGNOSIS_PHENOTYPE), }, { componentConfig: { @@ -118,7 +130,16 @@ export const filesEntityConfig: EntityConfig = { } as ComponentConfig, header: ANVIL_CMG_CATEGORY_LABEL.DIAGNOSE_DISEASE, id: ANVIL_CMG_CATEGORY_KEY.DIAGNOSE_DISEASE, - width: { max: "1fr", min: "200px" }, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.DIAGNOSE_DISEASE), + }, + { + componentConfig: { + component: C.NTagCell, + viewBuilder: V.buildFileDataModality, + } as ComponentConfig, + header: ANVIL_CMG_CATEGORY_LABEL.FILE_DATA_MODALITY, + id: ANVIL_CMG_CATEGORY_KEY.FILE_DATA_MODALITY, + ...getValueColumnSizing(ANVIL_CMG_CATEGORY_KEY.FILE_DATA_MODALITY), }, ], tableOptions: { @@ -127,6 +148,7 @@ export const filesEntityConfig: EntityConfig = { [ANVIL_CMG_CATEGORY_KEY.DONOR_PHENOTYPIC_SEX]: false, [ANVIL_CMG_CATEGORY_KEY.DONOR_REPORTED_ETHNICITY]: false, [ANVIL_CMG_CATEGORY_KEY.DIAGNOSE_DISEASE]: false, + [ANVIL_CMG_CATEGORY_KEY.FILE_DATA_MODALITY]: false, }, sorting: [ {