Skip to content

Commit

Permalink
Basic archetypes management table
Browse files Browse the repository at this point in the history
  - 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 <[email protected]>
  • Loading branch information
sjd78 committed Sep 1, 2023
1 parent d09bda6 commit b6fd2f7
Show file tree
Hide file tree
Showing 3 changed files with 291 additions and 4 deletions.
5 changes: 5 additions & 0 deletions client/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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.",
Expand Down Expand Up @@ -210,6 +213,7 @@
"applicationImports": "Application imports",
"applicationName": "Application name",
"applications": "Applications",
"archetypes": "Archetypes",
"artifact": "Artifact",
"artifactAssociated": "Associated artifact",
"artifactNotAssociated": "No associated artifact",
Expand Down Expand Up @@ -290,6 +294,7 @@
"label": "Label",
"loading": "Loading",
"lowRisk": "Low risk",
"maintainers": "Maintainers",
"mavenConfig": "Maven configuration",
"mediumRisk": "Medium risk",
"member(s)": "Member(s)",
Expand Down
7 changes: 4 additions & 3 deletions client/src/app/api/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
}
283 changes: 282 additions & 1 deletion client/src/app/pages/archetypes/archetypes-page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,291 @@
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;

// Note: Only Architect (and Administrator) personas should be able to access this
// page, therefore no explicit RBAC access checks to be done within the component

const CreateButton = () => (
<Button
type="button"
id="create-new-archetype"
aria-label="Create new archetype"
variant={ButtonVariant.primary}
onClick={() => {}} // TODO: Add create archetype modal
>
{t("dialog.title.newArchetype")}
</Button>
);

return (
<>
<PageSection variant={PageSectionVariants.light}>
<TextContent>
<Text component="h1">{t("terms.archetypes")}</Text>
</TextContent>
</PageSection>
<PageSection>
<ConditionalRender
when={isFetching && !(archetypes || fetchError)}
then={<AppPlaceholder />}
>
<div
style={{
backgroundColor: "var(--pf-v5-global--BackgroundColor--100)",
}}
>
<Toolbar {...toolbarProps}>
<ToolbarContent>
<FilterToolbar {...filterToolbarProps} />
<ToolbarGroup variant="button-group">
<ToolbarItem>
<CreateButton />
</ToolbarItem>
</ToolbarGroup>
{/* TODO: Add pagination */}
</ToolbarContent>
</Toolbar>

<Table
{...tableProps}
id="archetype-table"
aria-label="Archetype table"
>
<Thead>
<Tr>
<TableHeaderContentWithControls {...tableControls}>
<Th {...getThProps({ columnKey: "name" })} />
<Th {...getThProps({ columnKey: "description" })} />
<Th {...getThProps({ columnKey: "tags" })} />
<Th {...getThProps({ columnKey: "maintainers" })} />
<Th {...getThProps({ columnKey: "applications" })} />
</TableHeaderContentWithControls>
</Tr>
</Thead>
<ConditionalTableBody
isLoading={isFetching}
isError={!!fetchError}
isNoData={currentPageItems.length === 0}
noDataEmptyState={
<EmptyState variant="sm">
<EmptyStateHeader
titleText="No archetypes have been created"
headingLevel="h2"
icon={<EmptyStateIcon icon={CubesIcon} />}
/>
<EmptyStateBody>
Create a new archetype to get started.
</EmptyStateBody>
<EmptyStateFooter>
<CreateButton />
</EmptyStateFooter>
</EmptyState>
}
numRenderedColumns={numRenderedColumns}
>
{currentPageItems?.map((archetype, rowIndex) => (
<Tbody key={archetype.id}>
<Tr>
<TableRowContentWithControls
{...tableControls}
item={archetype}
rowIndex={rowIndex}
>
<Td {...getTdProps({ columnKey: "name" })}>
{archetype.name}
</Td>
<Td {...getTdProps({ columnKey: "description" })}>
{/* TODO: Truncate length and add tooltip with full text */}
{archetype.description}
</Td>
<Td {...getTdProps({ columnKey: "tags" })}>
<ArchetypeTagsColumn archetype={archetype} />
</Td>
<Td {...getTdProps({ columnKey: "maintainers" })}>
<ArchetypeMaintainersColumn archetype={archetype} />
</Td>
<Td {...getTdProps({ columnKey: "applications" })}>
<ArchetypeApplicationsColumn archetype={archetype} />
</Td>
<Td>{/* TODO: Add kebab action menu */}</Td>
</TableRowContentWithControls>
</Tr>
</Tbody>
))}
</ConditionalTableBody>
</Table>

return <div>Very much a place holder.</div>;
{/* TODO: Add pagination */}
</div>
</ConditionalRender>
</PageSection>

{/* action modals */}
</>
);
};

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 }) => (
<LabelCustomColor
color={category?.colour || getTagCategoryFallbackColor(category)}
>
{tag.name}
</LabelCustomColor>
);

// 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,
}) => (
<LabelGroup>
{archetype.archetypeTags?.map((tag) => <TagLabel key={tag.id} tag={tag} />)}
</LabelGroup>
);

// 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,
}) => (
<LabelGroup>
{archetype.stakeholders?.map((sh) => <Label key={sh.id}>{sh.name}</Label>)}
{archetype.stakeholderGroups?.map((shg) => (
<Label key={shg.id}>{shg.name}</Label>
))}
</LabelGroup>
);

// 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 ? (
<Text>
{t("message.archetypeApplicationCount", {
count: archetype.applications?.length ?? 0,
})}
</Text>
) : (
<Text>{t("message.archetypeNoApplications")}</Text>
);
};

0 comments on commit b6fd2f7

Please sign in to comment.