Skip to content

Commit

Permalink
Merge pull request #401 from CBIIT/CRDCDH-1206
Browse files Browse the repository at this point in the history
CRDCDH-1206 Submitted Data > Support "Data Files" Viewing
  • Loading branch information
Alejandro-Vega authored Jun 24, 2024
2 parents a1e82cb + 2bc61c7 commit c6bd370
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 26 deletions.
60 changes: 57 additions & 3 deletions src/components/DataSubmissions/ExportNodeDataButton.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe("Basic Functionality", () => {
getSubmissionNodes: {
total: 1,
properties: [],
nodes: [{ nodeType, nodeID: "example-node-id", props: "" }],
nodes: [{ nodeType, nodeID: "example-node-id", props: "", status: null }],
},
},
};
Expand Down Expand Up @@ -239,6 +239,7 @@ describe("Basic Functionality", () => {
{
nodeType: ["aaaa"] as unknown as string,
nodeID: 123 as unknown as string,
status: null,
props: "this is not JSON",
},
],
Expand Down Expand Up @@ -275,7 +276,7 @@ describe("Implementation Requirements", () => {
jest.resetAllMocks();
});

it("should have a tooltip present on the button", async () => {
it("should have a tooltip present on the button for Metadata", async () => {
const { getByTestId, findByRole } = render(
<TestParent mocks={[]}>
<ExportNodeDataButton
Expand All @@ -292,6 +293,52 @@ describe("Implementation Requirements", () => {
expect(tooltip).toHaveTextContent("Export submitted metadata for selected node type");
});

it("should have a tooltip present on the button for Data Files", async () => {
const { getByTestId, findByRole } = render(
<TestParent mocks={[]}>
<ExportNodeDataButton
submission={{ _id: "data-file-tooltip-id", name: "test-tooltip" }}
nodeType="Data File"
/>
</TestParent>
);

UserEvent.hover(getByTestId("export-node-data-button"));

const tooltip = await findByRole("tooltip");
expect(tooltip).toBeInTheDocument();
expect(tooltip).toHaveTextContent("Export a list of all uploaded data files");
});

it("should change the tooltip when the nodeType prop changes", async () => {
const { getByTestId, findByRole, rerender } = render(
<TestParent mocks={[]}>
<ExportNodeDataButton
submission={{ _id: "example-tooltip-id", name: "test-tooltip" }}
nodeType="sample"
/>
</TestParent>
);

UserEvent.hover(getByTestId("export-node-data-button"));

const tooltip = await findByRole("tooltip");
expect(tooltip).toHaveTextContent("Export submitted metadata for selected node type");

rerender(
<TestParent mocks={[]}>
<ExportNodeDataButton
submission={{ _id: "example-tooltip-id", name: "test-tooltip" }}
nodeType="Data File"
/>
</TestParent>
);

UserEvent.hover(getByTestId("export-node-data-button"));

expect(tooltip).toHaveTextContent("Export a list of all uploaded data files");
});

it.each<{ name: string; nodeType: string; date: Date; expected: string }>([
{
name: "Brain",
Expand Down Expand Up @@ -357,7 +404,14 @@ describe("Implementation Requirements", () => {
getSubmissionNodes: {
total: 1,
properties: ["a"],
nodes: [{ nodeType, nodeID: "example-node-id", props: JSON.stringify({ a: 1 }) }],
nodes: [
{
nodeType,
nodeID: "example-node-id",
props: JSON.stringify({ a: 1 }),
status: null,
},
],
},
},
},
Expand Down
21 changes: 14 additions & 7 deletions src/components/DataSubmissions/ExportNodeDataButton.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useMemo, useState } from "react";
import { useLazyQuery } from "@apollo/client";
import { IconButtonProps, IconButton, styled } from "@mui/material";
import { CloudDownload } from "@mui/icons-material";
Expand Down Expand Up @@ -53,6 +53,14 @@ export const ExportNodeDataButton: React.FC<Props> = ({
const { enqueueSnackbar } = useSnackbar();
const [loading, setLoading] = useState<boolean>(false);

const tooltip = useMemo<string>(
() =>
nodeType?.toLocaleLowerCase() === "data file"
? "Export a list of all uploaded data files"
: "Export submitted metadata for selected node type",
[nodeType]
);

const [getSubmissionNodes] = useLazyQuery<GetSubmissionNodesResp, GetSubmissionNodesInput>(
GET_SUBMISSION_NODES,
{
Expand Down Expand Up @@ -95,7 +103,10 @@ export const ExportNodeDataButton: React.FC<Props> = ({
try {
const filteredName = filterAlphaNumeric(submission.name?.trim()?.replaceAll(" ", "-"), "-");
const filename = `${filteredName}_${nodeType}_${dayjs().format("YYYYMMDDHHmm")}.tsv`;
const csvArray = d.getSubmissionNodes.nodes.map((node) => JSON.parse(node.props));
const csvArray = d.getSubmissionNodes.nodes.map((node) => ({
...JSON.parse(node.props),
status: node.status,
}));

downloadBlob(unparse(csvArray, { delimiter: "\t" }), filename, "text/tab-separated-values");
} catch (err) {
Expand All @@ -108,11 +119,7 @@ export const ExportNodeDataButton: React.FC<Props> = ({
};

return (
<StyledTooltip
title="Export submitted metadata for selected node type"
placement="top"
data-testid="export-node-data-tooltip"
>
<StyledTooltip title={tooltip} placement="top" data-testid="export-node-data-tooltip">
<span>
<StyledIconButton
onClick={handleClick}
Expand Down
51 changes: 45 additions & 6 deletions src/components/DataSubmissions/SubmittedDataFilters.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,12 @@ describe("SubmittedDataFilters cases", () => {

const muiSelectBox = within(getByTestId("data-content-node-filter")).getByRole("button");

await waitFor(() => expect(muiSelectBox).toHaveTextContent("FIRST"));
await waitFor(() => expect(muiSelectBox).toHaveTextContent(/first/i));
});

it("should NOT show the nodeType 'Data File'", async () => {
// NOTE: This test used to be the inverse, but we now want to ensure that Data Files are shown
// Data Files are a special case, as they're common across all Data Models / Data Commons
it("should show the nodeType 'data file' if present", async () => {
const mocks: MockedResponse<SubmissionStatsResp>[] = [
{
request: {
Expand All @@ -163,7 +165,7 @@ describe("SubmittedDataFilters cases", () => {
stats: [
{ ...baseStatistic, nodeName: "Participant", total: 3 },
{ ...baseStatistic, nodeName: "Data File", total: 2 },
{ ...baseStatistic, nodeName: "File", total: 1 },
{ ...baseStatistic, nodeName: "Sample", total: 1 },
],
},
},
Expand All @@ -184,9 +186,46 @@ describe("SubmittedDataFilters cases", () => {
await waitFor(() => {
// Sanity check that the box is open
expect(() => getAllByText(/participant/i)).not.toThrow();
expect(() => getByText(/file/i)).not.toThrow();
// This should throw an error
expect(() => getByText(/data file/i)).toThrow();
expect(() => getByText(/sample/i)).not.toThrow();
expect(() => getByText(/data file/i)).not.toThrow();
});
});

it("should visually render the nodeName as lowercase", async () => {
const mocks: MockedResponse<SubmissionStatsResp>[] = [
{
request: {
query: SUBMISSION_STATS,
},
variableMatcher: () => true,
result: {
data: {
submissionStats: {
stats: [
{ ...baseStatistic, nodeName: "NODE_NAME", total: 1 },
{ ...baseStatistic, nodeName: "Upper_Case", total: 1 },
{ ...baseStatistic, nodeName: "lower_case", total: 1 },
],
},
},
},
},
];

const { getByTestId } = render(
<TestParent mocks={mocks}>
<SubmittedDataFilters submissionId="id-test-filtering-lower-case" />
</TestParent>
);

const muiSelectBox = within(getByTestId("data-content-node-filter")).getByRole("button");

UserEvent.click(muiSelectBox);

await waitFor(() => {
expect(getByTestId("nodeType-NODE_NAME")).toHaveTextContent("node_name");
expect(getByTestId("nodeType-Upper_Case")).toHaveTextContent("upper_case");
expect(getByTestId("nodeType-lower_case")).toHaveTextContent("lower_case");
});
});

Expand Down
5 changes: 2 additions & 3 deletions src/components/DataSubmissions/SubmittedDataFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ export const SubmittedDataFilters: FC<SubmittedDataFiltersProps> = ({
cloneDeep(data?.submissionStats?.stats)
?.sort(compareNodeStats)
?.reverse()
?.map((stat) => stat.nodeName)
?.filter((nodeType) => nodeType?.toLowerCase() !== "data file"),
?.map((stat) => stat.nodeName),
[data?.submissionStats?.stats]
);

Expand Down Expand Up @@ -94,7 +93,7 @@ export const SubmittedDataFilters: FC<SubmittedDataFiltersProps> = ({
>
{nodeTypes?.map((nodeType) => (
<MenuItem key={nodeType} value={nodeType} data-testid={`nodeType-${nodeType}`}>
{nodeType}
{nodeType.toLowerCase()}
</MenuItem>
))}
</StyledSelect>
Expand Down
60 changes: 60 additions & 0 deletions src/content/dataSubmissions/SubmittedData.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ describe("SubmittedData > Table", () => {
"col.2": "value-2",
"col.3": "value-3",
}),
status: "New",
},
],
},
Expand All @@ -262,6 +263,65 @@ describe("SubmittedData > Table", () => {
});
});

it("should append the 'Status' column to any node type", async () => {
const submissionID = "example-status-column-id";

const mocks: MockedResponse[] = [
mockSubmissionQuery,
{
request: {
query: GET_SUBMISSION_NODES,
variables: {
_id: submissionID,
sortDirection: "desc",
first: 20,
offset: 0,
nodeType: "example-node",
},
},
result: {
data: {
getSubmissionNodes: {
total: 2,
properties: ["col-xyz"],
nodes: [
{
nodeType: "example-node",
nodeID: "example-node-id",
props: JSON.stringify({
"col-xyz": "value-1",
}),
status: "New",
},
{
nodeType: "example-node2",
nodeID: "example-node-id2",
props: JSON.stringify({
"col-xyz": "value-2",
}),
status: null,
},
],
},
},
},
},
];

const { getByTestId, getByText } = render(
<TestParent mocks={mocks}>
<SubmittedData submissionId={submissionID} submissionName={undefined} />
</TestParent>
);

await waitFor(() => {
expect(getByTestId("generic-table-header-col-xyz")).toBeInTheDocument();
expect(getByTestId("generic-table-header-Status")).toBeInTheDocument();
expect(getByText("value-1")).toBeInTheDocument();
expect(getByText("New")).toBeInTheDocument();
});
});

// NOTE: We're asserting that the columns ARE built using getSubmissionNodes.properties
// instead of the keys of nodes.[x].props JSON object
it("should NOT build the columns based off of the nodes.[X].props JSON object", async () => {
Expand Down
17 changes: 12 additions & 5 deletions src/content/dataSubmissions/SubmittedData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { safeParse } from "../../utils";
import { ExportNodeDataButton } from "../../components/DataSubmissions/ExportNodeDataButton";

type T = Pick<SubmissionNode, "nodeType" | "nodeID"> & {
type T = Pick<SubmissionNode, "nodeType" | "nodeID" | "status"> & {
props: Record<string, string>;
};

Expand Down Expand Up @@ -97,16 +97,22 @@ const SubmittedData: FC<Props> = ({ submissionId, submissionName }) => {

// Only update columns if the nodeType has changed
if (prevFilterRef.current.nodeType !== filterRef.current.nodeType) {
setTotalData(d.getSubmissionNodes.total);
setColumns(
d.getSubmissionNodes.properties.map((prop: string, index: number) => ({
const cols: Column<T>[] = d.getSubmissionNodes.properties.map(
(prop: string, index: number) => ({
label: prop,
renderValue: (d) => d?.props?.[prop] || "",
// NOTE: prop is not actually a keyof T, but it's a value of prop.props
field: prop as unknown as keyof T,
default: index === 0 ? true : undefined,
}))
})
);
cols.push({
label: "Status",
renderValue: (d) => d?.status || "",
field: "status",
});
setTotalData(d.getSubmissionNodes.total);
setColumns(cols);

prevFilterRef.current = filterRef.current;
}
Expand All @@ -116,6 +122,7 @@ const SubmittedData: FC<Props> = ({ submissionId, submissionName }) => {
nodeType: node.nodeType,
nodeID: node.nodeID,
props: safeParse(node.props),
status: node.status,
}))
);
setLoading(false);
Expand Down
3 changes: 2 additions & 1 deletion src/graphql/getSubmissionNodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const query = gql`
nodes {
nodeType
nodeID
status
props
}
}
Expand Down Expand Up @@ -52,6 +53,6 @@ export type Response = {
*
* @note Unused values are omitted from the query. See the type definition for additional fields.
*/
nodes: Pick<SubmissionNode, "nodeType" | "nodeID" | "props">[];
nodes: Pick<SubmissionNode, "nodeType" | "nodeID" | "props" | "status">[];
};
};
2 changes: 1 addition & 1 deletion src/types/Submissions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ type SubmissionNode = {
submissionID: string;
nodeType: string;
nodeID: string;
status: string;
status: ValidationStatus;
createdAt: string;
updatedAt: string;
validatedAt: string;
Expand Down

0 comments on commit c6bd370

Please sign in to comment.