Skip to content

Commit

Permalink
✨ Archetypes management: Add navigation and placeholder page component (
Browse files Browse the repository at this point in the history
konveyor#1308)

Work done:
 - [x] Add route `/archetypes`
 - [x] Add sidebar navigation "Archetypes" under "Application Inventory"
 - [x] Very basic placeholder page component
 - [x] Archetypes page component
 - [x] Add types, rest functions and queries for basic archetypes CRUD
 - [x] Add MSW stub api for archetypes CRUD (assuming the endpoint isn't available from hub yet)
 - [x] Archetypes "placeholder" table

Resolves konveyor#1263

Full function archetypes table will be done under konveyor#1264 

Full function archetypes add/edit form will be done under konveyor#1265


----
Base display, with mock/stub data from MSW:

![image](https://github.com/konveyor/tackle2-ui/assets/3985964/d6873212-e0e4-4feb-9844-8bc354b58ef2)

---------

Signed-off-by: Scott J Dickerson <[email protected]>
  • Loading branch information
sjd78 authored Sep 1, 2023
1 parent 4ad632d commit 0a9dded
Show file tree
Hide file tree
Showing 14 changed files with 655 additions and 3 deletions.
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>;

Check warning on line 54 in client/src/app/Routes.tsx

View workflow job for this annotation

GitHub Actions / unit-test (18.x)

Unexpected any. Specify a different type
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[];
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

0 comments on commit 0a9dded

Please sign in to comment.