diff --git a/src/components/DisplayModal.tsx b/src/components/DisplayModal.tsx index b82138fde3..bbd9960cd3 100644 --- a/src/components/DisplayModal.tsx +++ b/src/components/DisplayModal.tsx @@ -1,30 +1,20 @@ import styled from "@emotion/styled"; -import Modal, { ModalSize } from "@leafygreen-ui/modal"; +import Modal, { ModalProps } from "@leafygreen-ui/modal"; import { Body, BodyProps, H3 } from "@leafygreen-ui/typography"; import { size as tokenSize, zIndex } from "constants/tokens"; -export interface DisplayModalProps { - "data-cy"?: string; - open?: boolean; - setOpen?: ( - open: boolean - ) => void | React.Dispatch>; - size?: ModalSize; +type DisplayModalProps = Omit & { title?: React.ReactNode | string; - children: React.ReactNode; subtitle?: string; -} +}; export const DisplayModal: React.FC = ({ children, - "data-cy": dataCy, - open, - setOpen, - size, subtitle, title, + ...rest }) => ( - + {title &&

{title}

} {subtitle && ( {subtitle} diff --git a/src/components/Table/BaseTable.tsx b/src/components/Table/BaseTable.tsx index e18f0805c6..dd284caa4e 100644 --- a/src/components/Table/BaseTable.tsx +++ b/src/components/Table/BaseTable.tsx @@ -1,3 +1,4 @@ +import { css } from "@leafygreen-ui/emotion"; import { Cell, ExpandedContent, @@ -43,7 +44,16 @@ export const BaseTable = ({ {table.getRowModel().rows.map((row) => ( - + div { + max-height: unset; + } + `} + > {row.getVisibleCells().map((cell) => ( {flexRender(cell.column.columnDef.cell, cell.getContext())} diff --git a/src/pages/preferences/preferencesTabs/notificationTab/UserSubscriptions.tsx b/src/pages/preferences/preferencesTabs/notificationTab/UserSubscriptions.tsx index a5db552319..6281c337ec 100644 --- a/src/pages/preferences/preferencesTabs/notificationTab/UserSubscriptions.tsx +++ b/src/pages/preferences/preferencesTabs/notificationTab/UserSubscriptions.tsx @@ -1,4 +1,4 @@ -import { useRef, useState } from "react"; +import { useMemo, useRef, useState } from "react"; import { useMutation } from "@apollo/client"; import styled from "@emotion/styled"; import Button from "@leafygreen-ui/button"; @@ -30,7 +30,12 @@ import { Selector, } from "gql/generated/types"; import { DELETE_SUBSCRIPTIONS } from "gql/mutations"; -import { notificationMethodToCopy } from "types/subscription"; +import { useSpruceConfig } from "hooks"; +import { + NotificationMethods, + notificationMethodToCopy, +} from "types/subscription"; +import { jiraLinkify } from "utils/string/jiraLinkify"; import { ClearSubscriptions } from "./ClearSubscriptions"; import { getResourceRoute, useSubscriptionData } from "./utils"; @@ -38,6 +43,8 @@ const { gray } = palette; export const UserSubscriptions: React.FC<{}> = () => { const dispatchToast = useToastContext(); + const spruceConfig = useSpruceConfig(); + const jiraHost = spruceConfig?.jira?.host; const [deleteSubscriptions] = useMutation< DeleteSubscriptionsMutation, @@ -64,6 +71,79 @@ export const UserSubscriptions: React.FC<{}> = () => { const [rowSelection, setRowSelection] = useState({}); const tableContainerRef = useRef(null); + + const columns = useMemo( + () => [ + { + accessorKey: "resourceType", + cell: ({ getValue }) => { + const resourceType = getValue(); + return resourceTypeToCopy[resourceType] ?? resourceType; + }, + ...getColumnTreeSelectFilterProps({ + "data-cy": "status-filter-popover", + tData: resourceTypeTreeData, + title: "Type", + }), + }, + { + header: "ID", + accessorKey: "selectors", + cell: ({ + getValue, + row: { + original: { resourceType }, + }, + }) => { + const selectors = getValue(); + const resourceSelector = selectors.find( + (s: Selector) => s.type !== "object" && s.type !== "requester" + ); + const { data: selectorId } = resourceSelector ?? {}; + const route = getResourceRoute(resourceType, resourceSelector); + + return route ? ( + {selectorId} + ) : ( + selectorId + ); + }, + }, + { + accessorKey: "trigger", + ...getColumnTreeSelectFilterProps({ + "data-cy": "trigger-filter-popover", + tData: triggerTreeData, + title: "Event", + }), + cell: ({ getValue }) => { + const trigger = getValue(); + return triggerToCopy[trigger] ?? trigger; + }, + }, + { + header: "Notify by", + accessorKey: "subscriber.type", + cell: ({ getValue }) => { + const subscriberType = getValue(); + return notificationMethodToCopy[subscriberType] ?? subscriberType; + }, + }, + { + header: "Target", + accessorKey: "subscriber", + cell: ({ getValue }) => { + const subscriber = getValue(); + const text = getSubscriberText(subscriber); + return subscriber.type === NotificationMethods.JIRA_COMMENT + ? jiraLinkify(text, jiraHost) + : text; + }, + }, + ], + [jiraHost] + ); + const table = useLeafyGreenTable({ columns, containerRef: tableContainerRef, @@ -142,69 +222,6 @@ export const UserSubscriptions: React.FC<{}> = () => { ); }; -const columns = [ - { - accessorKey: "resourceType", - cell: ({ getValue }) => { - const resourceType = getValue(); - return resourceTypeToCopy?.[resourceType] ?? resourceType; - }, - ...getColumnTreeSelectFilterProps({ - "data-cy": "status-filter-popover", - tData: resourceTypeTreeData, - title: "Type", - }), - }, - { - header: "ID", - accessorKey: "selectors", - cell: ({ - getValue, - row: { - original: { resourceType }, - }, - }) => { - const selectors = getValue(); - const resourceSelector = selectors.find( - (s: Selector) => s.type !== "object" && s.type !== "requester" - ); - const { data: selectorId } = resourceSelector ?? {}; - const route = getResourceRoute(resourceType, resourceSelector); - - return route ? ( - {selectorId} - ) : ( - selectorId - ); - }, - }, - { - accessorKey: "trigger", - ...getColumnTreeSelectFilterProps({ - "data-cy": "trigger-filter-popover", - tData: triggerTreeData, - title: "Event", - }), - cell: ({ getValue }) => { - const trigger = getValue(); - return triggerToCopy?.[trigger] ?? trigger; - }, - }, - { - header: "Notify by", - accessorKey: "subscriber.type", - cell: ({ getValue }) => { - const subscriberType = getValue(); - return notificationMethodToCopy[subscriberType] ?? subscriberType; - }, - }, - { - header: "Target", - accessorKey: "subscriber", - cell: ({ getValue }) => getSubscriberText(getValue()), - }, -]; - const InteractiveWrapper = styled.div` display: flex; justify-content: space-between; diff --git a/src/pages/version/ParametersModal/ParametersModal.stories.tsx b/src/pages/version/ParametersModal/ParametersModal.stories.tsx new file mode 100644 index 0000000000..753cad8639 --- /dev/null +++ b/src/pages/version/ParametersModal/ParametersModal.stories.tsx @@ -0,0 +1,20 @@ +import { CustomStoryObj, CustomMeta } from "test_utils/types"; +import { ParametersModal } from "."; + +export default { + component: ParametersModal, +} satisfies CustomMeta; + +export const Default: CustomStoryObj = { + render: (args) => ( + + ), +}; diff --git a/src/pages/version/ParametersModal/ParametersModal.test.tsx b/src/pages/version/ParametersModal/ParametersModal.test.tsx new file mode 100644 index 0000000000..d366b199c1 --- /dev/null +++ b/src/pages/version/ParametersModal/ParametersModal.test.tsx @@ -0,0 +1,30 @@ +import { render, screen, userEvent } from "test_utils"; +import { ParametersModal } from "."; + +const parameters = [ + { key: "Key 1", value: "Value 1" }, + { key: "Key 2", value: "Value 2" }, +]; + +describe("parameters modal", () => { + it("modal is closed by default", () => { + render(); + expect(screen.queryByDataCy("parameters-modal")).toBeNull(); + }); + + it("link does not render if there are no parameters", () => { + render(); + expect(screen.queryByDataCy("parameters-link")).toBeNull(); + }); + + it("can click the link to open the modal and view parameters", async () => { + const user = userEvent.setup(); + render(); + await user.click(screen.getByDataCy("parameters-link")); + await screen.findByDataCy("parameters-modal"); + expect(screen.getByText(parameters[0].key)).toBeInTheDocument(); + expect(screen.getByText(parameters[0].value)).toBeInTheDocument(); + expect(screen.getByText(parameters[1].key)).toBeInTheDocument(); + expect(screen.getByText(parameters[1].value)).toBeInTheDocument(); + }); +}); diff --git a/src/pages/version/ParametersModal.tsx b/src/pages/version/ParametersModal/index.tsx similarity index 56% rename from src/pages/version/ParametersModal.tsx rename to src/pages/version/ParametersModal/index.tsx index 869d920a89..b61223e348 100644 --- a/src/pages/version/ParametersModal.tsx +++ b/src/pages/version/ParametersModal/index.tsx @@ -1,10 +1,10 @@ -import { useState } from "react"; +import { useRef, useState } from "react"; import styled from "@emotion/styled"; -import Badge from "@leafygreen-ui/badge"; +import { useLeafyGreenTable, LGColumnDef } from "@leafygreen-ui/table/new"; import { DisplayModal } from "components/DisplayModal"; import { MetadataItem } from "components/MetadataCard"; import { StyledLink } from "components/styles"; -import { size } from "constants/tokens"; +import { BaseTable } from "components/Table/BaseTable"; import { Parameter } from "gql/generated/types"; interface ParametersProps { @@ -13,6 +13,15 @@ interface ParametersProps { export const ParametersModal: React.FC = ({ parameters }) => { const [showModal, setShowModal] = useState(false); + + const tableContainerRef = useRef(null); + + const table = useLeafyGreenTable({ + containerRef: tableContainerRef, + data: parameters, + columns, + }); + return ( <> {parameters !== undefined && parameters.length > 0 && ( @@ -26,23 +35,32 @@ export const ParametersModal: React.FC = ({ parameters }) => { )} - {parameters?.map((param) => ( - - {param.key}: {param.value} - - ))} + + + ); }; -const StyledBadge = styled(Badge)` - :not(:last-of-type) { - margin-right: ${size.s}; - } +const OverflowContainer = styled.div` + max-height: 600px; + overflow-y: scroll; `; + +const columns: LGColumnDef[] = [ + { + accessorKey: "key", + header: "Key", + }, + { + accessorKey: "value", + header: "Value", + }, +];