Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Archetypes management: Add navigation and placeholder page component #1308

Merged
merged 7 commits into from
Sep 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 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 @@ -188,6 +191,7 @@
"sidebar": {
"administrator": "Administration",
"applicationInventory": "Application inventory",
"archetypes": "Archetypes",
"controls": "Controls",
"developer": "Migration",
"reports": "Reports",
Expand All @@ -209,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 @@ -289,6 +294,7 @@
"label": "Label",
"loading": "Loading",
"lowRisk": "Low risk",
"maintainers": "Maintainers",
"mavenConfig": "Maven configuration",
"mediumRisk": "Medium risk",
"member(s)": "Member(s)",
Expand Down
1 change: 1 addition & 0 deletions client/src/app/Paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export enum Paths {
assessmentActions = "/applications/assessment-actions/:applicationId",
applicationsReview = "/applications/application/:applicationId/review",
applicationsAnalysis = "/applications/analysis",
archetypes = "/archetypes",
controls = "/controls",
controlsBusinessServices = "/controls/business-services",
controlsStakeholders = "/controls/stakeholders",
Expand Down
8 changes: 8 additions & 0 deletions client/src/app/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ const AssessmentActions = lazy(
() =>
import("./pages/applications/assessment-actions/assessment-actions-page")
);
const Archetypes = lazy(() => import("./pages/archetypes/archetypes-page"));

export interface IRoute {
path: string;
comp: React.ComponentType<any>;
Expand Down Expand Up @@ -149,6 +151,11 @@ export const devRoutes: IRoute[] = [
comp: Questionnaire,
exact: false,
},
{
path: Paths.archetypes,
comp: Archetypes,
exact: false,
},
];

export const adminRoutes: IRoute[] = [
Expand Down Expand Up @@ -189,6 +196,7 @@ export const adminRoutes: IRoute[] = [
]
: []),
];

export const AppRoutes = () => {
const location = useLocation();

Expand Down
13 changes: 13 additions & 0 deletions client/src/app/api/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -744,3 +744,16 @@ export interface AssessmentConfidence {
applicationId: number;
confidence: number;
}

export interface Archetype {
id: number;
name: string;
description: string;
comments: string;
criteriaTags: Tag[];
archetypeTags: Tag[];
assessmentTags?: Tag[];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these are going to be CategorizedTag but probably TBD what that data model will look like in its final form.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figure there will be a bunch of those kinds of mismatches, but if we make some good guesses, we shouldn't be far off. I just named it after this mockup:
image

stakeholders?: Stakeholder[];
stakeholderGroups?: StakeholderGroup[];
applications?: Application[];
}
31 changes: 28 additions & 3 deletions client/src/app/api/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,13 @@ import {
Ref,
TrackerProject,
TrackerProjectIssuetype,
Fact,
UnstructuredFact,
AnalysisAppDependency,
AnalysisAppReport,
Rule,
Target,
HubFile,
Questionnaire,
Archetype,
InitialAssessment,
} from "./models";
import { QueryKey } from "@tanstack/react-query";
Expand Down Expand Up @@ -107,6 +106,8 @@ export const ANALYSIS_ISSUE_INCIDENTS =

export const QUESTIONNAIRES = HUB + "/questionnaires";

export const ARCHETYPES = HUB + "/archetypes";

// PATHFINDER
export const PATHFINDER = "/hub/pathfinder";
export const ASSESSMENTS = HUB + "/assessments";
Expand Down Expand Up @@ -548,7 +549,10 @@ export const getFileReports = (
)
: Promise.reject();

export const getIncidents = (issueId?: number, params: HubRequestParams = {}) =>
export const getIncidents = (
issueId?: number,
params: HubRequestParams = {}
) =>
issueId
? getHubPaginatedResult<AnalysisIncident>(
ANALYSIS_ISSUE_INCIDENTS.replace("/:issueId/", `/${String(issueId)}/`),
Expand Down Expand Up @@ -740,3 +744,24 @@ export const updateQuestionnaire = (
// TODO: of 204 - NoContext) ... the return type does not make sense.
export const deleteQuestionnaire = (id: number): Promise<Questionnaire> =>
axios.delete(`${QUESTIONNAIRES}/${id}`);

// ---------------------------------------
// Archetypes
//
export const getArchetypes = (): Promise<Archetype[]> =>
axios.get(ARCHETYPES).then(({ data }) => data);

export const getArchetypeById = (id: number): Promise<Archetype> =>
axios.get(`${ARCHETYPES}/${id}`).then(({ data }) => data);

// success with code 201 and created entity as response data
export const createArchetype = (archetype: Archetype): Promise<Archetype> =>
axios.post(ARCHETYPES, archetype);

// success with code 204 and therefore no response content
export const updateArchetype = (archetype: Archetype): Promise<void> =>
axios.put(`${ARCHETYPES}/${archetype.id}`, archetype);

// success with code 204 and therefore no response content
export const deleteArchetype = (id: number): Promise<void> =>
axios.delete(`${ARCHETYPES}/${id}`);
8 changes: 8 additions & 0 deletions client/src/app/layout/SidebarApp/SidebarApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ export const SidebarApp: React.FC = () => {
{t("sidebar.applicationInventory")}
</NavLink>
</NavItem>
<NavItem>
<NavLink
to={Paths.archetypes + search}
activeClassName="pf-m-current"
>
{t("sidebar.archetypes")}
</NavLink>
</NavItem>
<NavItem>
<NavLink
to={Paths.reports + search}
Expand Down
225 changes: 225 additions & 0 deletions client/src/app/pages/archetypes/archetypes-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import {
Button,
ButtonVariant,
EmptyState,
EmptyStateBody,
EmptyStateFooter,
EmptyStateHeader,
EmptyStateIcon,
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 ArchetypeApplicationsColumn from "./components/archetype-applications-column";
import ArchetypeDescriptionColumn from "./components/archetype-description-column";
import ArchetypeMaintainersColumn from "./components/archetype-maintainers-column";
import ArchetypeTagsColumn from "./components/archetype-tags-column";

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,
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 = () => (
<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" })}>
<ArchetypeDescriptionColumn archetype={archetype} />
</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>

{/* TODO: Add pagination */}
</div>
</ConditionalRender>
</PageSection>

{/* TODO: Add create/edit modal */}
{/* TODO: Add duplicate confirm modal */}
{/* TODO: Add delete confirm modal */}
</>
);
};

export default Archetypes;
Loading