Skip to content

Commit

Permalink
feat: Add Model Navigator link to dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
amattu2 committed Jan 27, 2025
1 parent c82e573 commit bd3acc2
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 25 deletions.
13 changes: 8 additions & 5 deletions src/components/DataSubmissions/MetadataUpload.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { render, waitFor } from "@testing-library/react";
import { MockedProvider, MockedResponse } from "@apollo/client/testing";
import { axe } from "jest-axe";
import userEvent from "@testing-library/user-event";
import { MemoryRouter } from "react-router-dom";
import { Context, ContextState, Status as AuthStatus } from "../Contexts/AuthContext";
import MetadataUpload from "./MetadataUpload";
import { CREATE_BATCH, CreateBatchResp, UPDATE_BATCH, UpdateBatchResp } from "../../graphql";
Expand Down Expand Up @@ -99,11 +100,13 @@ const TestParent: FC<ParentProps> = ({
mocks = [],
children,
}: ParentProps) => (
<Context.Provider value={context}>
<MockedProvider mocks={mocks} showWarnings>
{children}
</MockedProvider>
</Context.Provider>
<MemoryRouter>
<Context.Provider value={context}>
<MockedProvider mocks={mocks} showWarnings>
{children}
</MockedProvider>
</Context.Provider>
</MemoryRouter>
);

describe("Accessibility", () => {
Expand Down
25 changes: 17 additions & 8 deletions src/components/DataSubmissions/MetadataUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import FlowWrapper from "./FlowWrapper";
import { Logger } from "../../utils";
import { hasPermission } from "../../config/AuthPermissions";
import { TOOLTIP_TEXT } from "../../config/DashboardTooltips";
import NavigatorLink from "./NavigatorLink";

const StyledUploadTypeText = styled(Typography)(() => ({
color: "#083A50",
Expand Down Expand Up @@ -311,20 +312,28 @@ const MetadataUpload = ({ submission, readOnly, onCreateBatch, onUpload }: Props
[selectedFiles, readOnly, canUpload, isUploading]
);

return (
<FlowWrapper
index={1}
title="Upload Metadata"
titleAdornment={
const Adornments: ReactElement = useMemo(
() => (
<Stack direction="row" alignItems="center" spacing={1.25}>
<StyledTooltip
placement="right"
title={TOOLTIP_TEXT.FILE_UPLOAD.UPLOAD_METADATA}
open={undefined}
disableHoverListener={false}
/>
}
actions={Actions}
>
{/* TODO: Implement design */}
{submission?.dataCommons && submission?.modelVersion && (
<span>
({submission.dataCommons} Data Model: <NavigatorLink submission={submission} />)
</span>
)}
</Stack>
),
[submission?.dataCommons, submission?.modelVersion]
);

return (
<FlowWrapper index={1} title="Upload Metadata" titleAdornment={Adornments} actions={Actions}>
<Stack direction="row" alignItems="center" spacing={1.25}>
<StyledUploadActionWrapper direction="row">
<StyledMetadataText variant="body2">Metadata Files</StyledMetadataText>
Expand Down
147 changes: 147 additions & 0 deletions src/components/DataSubmissions/NavigatorLink.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import React from "react";
import { render } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import { axe } from "jest-axe";
import NavigatorLink, { NavigatorLinkProps } from "./NavigatorLink";

type TestParentProps = {
children: React.ReactNode;
};

const TestParent: React.FC<TestParentProps> = ({ children }) => (
<MemoryRouter>{children}</MemoryRouter>
);

describe("Accessibility", () => {
it("should not have any violations", async () => {
const { container, getByTestId } = render(
<NavigatorLink submission={{ status: "New", dataCommons: "test", modelVersion: "1.0.0" }} />,
{
wrapper: TestParent,
}
);

expect(getByTestId("navigator-link")).toBeInTheDocument(); // Sanity check
expect(await axe(container)).toHaveNoViolations();
});

it("should not have any violations when disabled", async () => {
const { container, getByTestId } = render(
<NavigatorLink submission={{ status: "Canceled", dataCommons: "", modelVersion: "" }} />, // Disabled by invalid status and empty props
{
wrapper: TestParent,
}
);

expect(getByTestId("navigator-link-disabled")).toBeInTheDocument(); // Sanity check
expect(await axe(container)).toHaveNoViolations();
});
});

describe("Basic Functionality", () => {
it("should prepend a 'v' to the version if it is missing", () => {
const { getByText } = render(
<NavigatorLink submission={{ status: "New", dataCommons: "test", modelVersion: "1.0.0" }} />,
{
wrapper: TestParent,
}
);

expect(getByText("v1.0.0")).toBeInTheDocument();
});

it("should not prepend a 'v' to the version if it is present already", () => {
const { getByText } = render(
<NavigatorLink submission={{ status: "New", dataCommons: "test", modelVersion: "v1.0.0" }} />,
{
wrapper: TestParent,
}
);

expect(getByText("v1.0.0")).toBeInTheDocument();
});

it("should not crash when the submission object is null", () => {
expect(() =>
render(<NavigatorLink submission={null as NavigatorLinkProps["submission"]} />, {
wrapper: TestParent,
})
).not.toThrow();
});

it("should not generate a link with invalid dependent props", () => {
const { getByTestId, rerender } = render(
<NavigatorLink submission={{ status: "New", dataCommons: "", modelVersion: "1.0.0" }} />,
{
wrapper: TestParent,
}
);

expect(getByTestId("navigator-link-disabled")).toBeInTheDocument(); // Bad DC

rerender(
<NavigatorLink
submission={{ status: "In Progress", dataCommons: "valid", modelVersion: "" }}
/>
);

expect(getByTestId("navigator-link-disabled")).toBeInTheDocument(); // Bad Version
});
});

describe("Implementation Requirements", () => {
it("should render a hyperlink with the correct href", () => {
const { getByTestId } = render(
<NavigatorLink submission={{ status: "New", dataCommons: "test", modelVersion: "1.0.0" }} />,
{
wrapper: TestParent,
}
);

expect(getByTestId("navigator-link")).toHaveAttribute("href", "/model-navigator/test/1.0.0");
});

it("should render a hyperlink with a target of '_blank'", () => {
const { getByTestId } = render(
<NavigatorLink submission={{ status: "New", dataCommons: "test", modelVersion: "1.0.0" }} />,
{
wrapper: TestParent,
}
);

expect(getByTestId("navigator-link")).toHaveAttribute("target", "_blank");
});

it.each<SubmissionStatus>([
"New",
"In Progress",
"Withdrawn",
"Submitted",
"Released",
"Completed",
"Rejected",
])("should be enabled when the status is %s", (status) => {
const { getByTestId } = render(
<NavigatorLink submission={{ status, dataCommons: "test", modelVersion: "1.0.0" }} />,
{
wrapper: TestParent,
}
);

expect(getByTestId("navigator-link")).toBeInTheDocument();
});

it.each<SubmissionStatus>(["Canceled", "Deleted"])(
"should be disabled when the status is %s",
(status) => {
const { getByTestId } = render(
<NavigatorLink submission={{ status, dataCommons: "test", modelVersion: "1.0.0" }} />,
{
wrapper: TestParent,
}
);

expect(getByTestId("navigator-link-disabled")).toBeInTheDocument();
}
);
});
60 changes: 60 additions & 0 deletions src/components/DataSubmissions/NavigatorLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { FC, memo, useMemo } from "react";
import { Link } from "react-router-dom";

export type NavigatorLinkProps = {
/**
* The Data Submission object to generate the link for.
* At minimum, this object should contain the status, dataCommons, and modelVersion fields.
*/
submission: Submission | Pick<Submission, "status" | "dataCommons" | "modelVersion">;
};

/**
* The states that result in the NavigatorLink being disabled.
*/
const DISABLED_STATES: SubmissionStatus[] = ["Canceled", "Deleted"];

/**
* A React Router Link wrapper that links to the Model Navigator for the given submission.
*
* - If the submission is in a disabled state, the link will be disabled.
* - If the `modelVersion` is not prefixed with a 'v', it will be added.
*
* @returns The NavigatorLink component
*/
const NavigatorLink: FC<NavigatorLinkProps> = ({ submission }) => {
const { status, dataCommons, modelVersion } = submission || {};

const formattedVersion = useMemo<string>(() => {
if (!modelVersion || typeof modelVersion !== "string") {
return "";
}
if (modelVersion.charAt(0) === "v") {
return modelVersion;
}

return `v${modelVersion}`;
}, [modelVersion]);

if (!status || !dataCommons || !modelVersion || DISABLED_STATES.includes(status)) {
return <span data-testid="navigator-link-disabled">{formattedVersion}</span>;
}

return (
<Link
to={`/model-navigator/${dataCommons}/${modelVersion}`}
target="_blank"
data-testid="navigator-link"
>
{formattedVersion}
</Link>
);
};

export default memo<NavigatorLinkProps>(
NavigatorLink,
(prev, next) =>
prev?.submission?.status === next?.submission?.status &&
prev?.submission?.dataCommons === next?.submission?.dataCommons &&
prev?.submission?.modelVersion === next?.submission?.modelVersion
);
14 changes: 2 additions & 12 deletions src/content/dataSubmissions/DataSubmissionsListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { useColumnVisibility } from "../../hooks/useColumnVisibility";
import DataSubmissionListFilters, {
FilterForm,
} from "../../components/DataSubmissions/DataSubmissionListFilters";
import NavigatorLink from "../../components/DataSubmissions/NavigatorLink";

type T = ListSubmissionsResp["listSubmissions"]["submissions"][number];

Expand Down Expand Up @@ -132,18 +133,7 @@ const columns: Column<T>[] = [
},
{
label: "DM Version",
renderValue: (a) => {
const inactiveStates: SubmissionStatus[] = ["Canceled", "Deleted"];
if (inactiveStates.includes(a.status)) {
return a.modelVersion;
}

return (
<Link to={`/model-navigator/${a.dataCommons}/${a.modelVersion}`} target="_blank">
{a.modelVersion}
</Link>
);
},
renderValue: (a) => <NavigatorLink submission={a} />,
field: "modelVersion",
hideable: true,
sx: {
Expand Down

0 comments on commit bd3acc2

Please sign in to comment.