From 8ba8e5d791651e7a718e29fad400ba75e4303a3b Mon Sep 17 00:00:00 2001 From: Scott J Dickerson Date: Thu, 31 Aug 2023 23:15:55 -0400 Subject: [PATCH] Basic archetypes management table - Display a list of archetypes - Simple column components for Tags, Maintainers, Applications - Filtering on name - Action button is in place but doesn't do anything - TODO items in sources for things that need to be added later Signed-off-by: Scott J Dickerson --- client/public/locales/en/translation.json | 5 + client/src/app/api/models.ts | 7 +- .../app/pages/archetypes/archetypes-page.tsx | 286 +++++++++++++++++- 3 files changed, 294 insertions(+), 4 deletions(-) diff --git a/client/public/locales/en/translation.json b/client/public/locales/en/translation.json index 2422e2a422..db158506a4 100644 --- a/client/public/locales/en/translation.json +++ b/client/public/locales/en/translation.json @@ -114,6 +114,7 @@ "import": "Import {{what}}", "leavePage": "Leave page", "new": "New {{what}}", + "newArchetype": "Create new archetype", "newAssessment": "New assessment", "newApplication": "New application", "newBusinessService": "New business service", @@ -139,6 +140,8 @@ "small": "Small" }, "message": { + "archetypeApplicationCount": "{{count}} applications", + "archetypeNoApplications": "No applications currently match the criteria tags.", "appNotAssesedTitle": "Assessment has not been completed", "appNotAssessedBody": "In order to review an application it must be assessed first. Assess the application and try again.", "assessmentStakeholderHeader": "Select the stakeholder(s) or stakeholder group(s) associated with this assessment.", @@ -210,6 +213,7 @@ "applicationImports": "Application imports", "applicationName": "Application name", "applications": "Applications", + "archetypes": "Archetypes", "artifact": "Artifact", "artifactAssociated": "Associated artifact", "artifactNotAssociated": "No associated artifact", @@ -290,6 +294,7 @@ "label": "Label", "loading": "Loading", "lowRisk": "Low risk", + "maintainers": "Maintainers", "mavenConfig": "Maven configuration", "mediumRisk": "Medium risk", "member(s)": "Member(s)", diff --git a/client/src/app/api/models.ts b/client/src/app/api/models.ts index 058b7aa57b..2d805a6236 100644 --- a/client/src/app/api/models.ts +++ b/client/src/app/api/models.ts @@ -752,7 +752,8 @@ export interface Archetype { comments: string; criteriaTags: Tag[]; archetypeTags: Tag[]; - assessmentTags: Tag[]; - stakeholders: Stakeholder[]; - stakeholderGroups: StakeholderGroup[]; + assessmentTags?: Tag[]; + stakeholders?: Stakeholder[]; + stakeholderGroups?: StakeholderGroup[]; + applications?: Application[]; } diff --git a/client/src/app/pages/archetypes/archetypes-page.tsx b/client/src/app/pages/archetypes/archetypes-page.tsx index a4bc46dad2..63372c17c6 100644 --- a/client/src/app/pages/archetypes/archetypes-page.tsx +++ b/client/src/app/pages/archetypes/archetypes-page.tsx @@ -1,10 +1,294 @@ import React from "react"; import { useTranslation } from "react-i18next"; +import { useHistory } from "react-router-dom"; +import { + Button, + ButtonVariant, + EmptyState, + EmptyStateBody, + EmptyStateFooter, + EmptyStateHeader, + EmptyStateIcon, + Label, + LabelGroup, + PageSection, + PageSectionVariants, + Text, + TextContent, + Toolbar, + ToolbarContent, + ToolbarGroup, + ToolbarItem, +} from "@patternfly/react-core"; +import { Table, Tbody, Th, Thead, Tr, Td } from "@patternfly/react-table"; +import { CubesIcon } from "@patternfly/react-icons"; + +import { AppPlaceholder } from "@app/components/AppPlaceholder"; +import { ConditionalRender } from "@app/components/ConditionalRender"; +import { FilterToolbar, FilterType } from "@app/components/FilterToolbar"; +import { NotificationsContext } from "@app/components/NotificationsContext"; +import { + ConditionalTableBody, + TableHeaderContentWithControls, + TableRowContentWithControls, +} from "@app/components/TableControls"; +import { useLocalTableControls } from "@app/hooks/table-controls"; +import { useFetchArchetypes } from "@app/queries/archetypes"; +import { Archetype, Tag, TagCategory } from "@app/api/models"; +import { LabelCustomColor } from "@migtools/lib-ui"; +import { COLOR_HEX_VALUES_BY_NAME } from "@app/Constants"; const Archetypes: React.FC = () => { const { t } = useTranslation(); + const history = useHistory(); + const { pushNotification } = React.useContext(NotificationsContext); + + const { archetypes, isFetching, error: fetchError } = useFetchArchetypes(); + + const tableControls = useLocalTableControls({ + idProperty: "id", + items: archetypes, + isLoading: isFetching, + isSelectable: false, + expandableVariant: null, + hasActionsColumn: true, + + columnNames: { + name: t("terms.name"), + description: t("terms.description"), + tags: t("terms.tags"), + maintainers: t("terms.maintainers"), + applications: t("terms.applications"), + }, + + filterCategories: [ + { + key: "name", + title: t("terms.name"), + type: FilterType.search, + placeholderText: + t("actions.filterBy", { + what: t("terms.name").toLowerCase(), + }) + "...", + getItemValue: (archetype) => { + return archetype?.name ?? ""; + }, + }, + // TODO: Add filter for archetype tags + ], + + sortableColumns: ["name"], + getSortValues: (archetype) => ({ + name: archetype.name ?? "", + }), + initialSort: { columnKey: "name", direction: "asc" }, + + hasPagination: false, // TODO: Add pagination + }); + const { + currentPageItems, + numRenderedColumns, + propHelpers: { + toolbarProps, + filterToolbarProps, + paginationToolbarItemProps, + paginationProps, + tableProps, + getThProps, + getTdProps, + }, + } = tableControls; + + // TODO: RBAC access checks need to be added. Only Architect (and Administrator) personas + // TODO: should be able to create/edit archetypes. Every persona should be able to view + // TODO: the archetypes. + + const CreateButton = () => ( + + ); + + return ( + <> + + + {t("terms.archetypes")} + + + + } + > +
+ + + + + + + + + {/* TODO: Add pagination */} + + + + + + + + + + + } + /> + + Create a new archetype to get started. + + + + + + } + numRenderedColumns={numRenderedColumns} + > + {currentPageItems?.map((archetype, rowIndex) => ( + + + + + + + + + + + + + ))} + +
+ + + + + +
+ {archetype.name} + + {/* TODO: Truncate length and add tooltip with full text */} + {archetype.description} + + + + + + + {/* TODO: Add kebab action menu */}
- return
Very much a place holder.
; + {/* TODO: Add pagination */} +
+
+
+ + {/* TODO: Add create/edit modal */} + {/* TODO: Add duplicate confirm modal */} + {/* TODO: Add delete confirm modal */} + + ); }; export default Archetypes; + +// +// TODO: Move column components to their own files +// + +// copied from application-tag-label.tsx +export const getTagCategoryFallbackColor = (category?: TagCategory) => { + if (!category?.id) return COLOR_HEX_VALUES_BY_NAME.gray; + const colorValues = Object.values(COLOR_HEX_VALUES_BY_NAME); + return colorValues[category?.id % colorValues.length]; +}; + +// copied from application-tag-label.tsx +const TagLabel: React.FC<{ + tag: Tag; + category?: TagCategory; +}> = ({ tag, category }) => ( + + {tag.name} + +); + +// TODO: Refactor the application-tags-label.tsx so applications and archetypes can share `TagLabel` +// TODO: Sort tags? +// TODO: Group tags by categories? +const ArchetypeTagsColumn: React.FC<{ archetype: Archetype }> = ({ + archetype, +}) => ( + + {archetype.archetypeTags?.map((tag) => )} + +); + +// TODO: Don't show the full name, generate initials +// TODO: Sort individual stakeholders with stakeholder groups +// TODO: Add tooltips for each Label with the full name +const ArchetypeMaintainersColumn: React.FC<{ archetype: Archetype }> = ({ + archetype, +}) => ( + + {archetype.stakeholders?.map((sh) => )} + {archetype.stakeholderGroups?.map((shg) => ( + + ))} + +); + +// TODO: When count > 0 render a link to navigate to the application inventory assessment page +// with filters set to show the applications for the archetype? +const ArchetypeApplicationsColumn: React.FC<{ archetype: Archetype }> = ({ + archetype, +}) => { + const { t } = useTranslation(); + + return (archetype?.applications?.length ?? 0) > 0 ? ( + + {t("message.archetypeApplicationCount", { + count: archetype.applications?.length ?? 0, + })} + + ) : ( + {t("message.archetypeNoApplications")} + ); +};