Skip to content

Commit

Permalink
✨ Add dependencies table and drawer
Browse files Browse the repository at this point in the history
Signed-off-by: ibolton336 <ibolton@redhat.com>
  • Loading branch information
ibolton336 committed Jul 6, 2023
1 parent a8e6de5 commit b81903a
Show file tree
Hide file tree
Showing 11 changed files with 518 additions and 82 deletions.
2 changes: 2 additions & 0 deletions client/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@
"jobFunction": "Job function",
"jobFunctionDeleted": "Job function deleted",
"jobFunctions": "Job functions",
"language": "Language",
"label": "Label",
"loading": "Loading",
"lowRisk": "Low risk",
"mavenConfig": "Maven configuration",
Expand Down
1 change: 1 addition & 0 deletions client/src/app/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,4 +241,5 @@ export enum TableURLParamKeyPrefix {
issuesAffectedApps = "ia",
issuesAffectedFiles = "if",
issuesRemainingIncidents = "ii",
dependencyApplications = "da",
}
23 changes: 20 additions & 3 deletions client/src/app/api/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -557,10 +557,27 @@ export interface TrackerProjectIssuetype {
export interface AnalysisDependency {
createTime: string;
name: string;
provider: string;
version: string;
// TODO where did these properties go?
// indirect?: boolean;
// applications: { id: number; name: string }[];
sha: string;
applications: number;
labels: string[];
}

export interface AnalysisAppDependency {
id: number;
name: string;
description: string;
businessService: string;
dependency: {
id: number;
name: string;
version: string;
provider: string;
indirect: boolean;
//TODO: Glean from labels somehow
// management?: string;
};
}

interface AnalysisIssuesCommonFields {
Expand Down
19 changes: 15 additions & 4 deletions client/src/app/api/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
BaseAnalysisRuleReport,
BaseAnalysisIssueReport,
AnalysisIssue,
AnalysisAppReport,
AnalysisFileReport,
AnalysisIncident,
Application,
Expand Down Expand Up @@ -47,6 +46,8 @@ import {
TrackerProjectIssuetype,
Fact,
UnstructuredFact,
AnalysisAppDependency,
AnalysisAppReport,
} from "./models";
import { QueryKey } from "@tanstack/react-query";
import { serializeRequestParamsForHub } from "@app/shared/hooks/table-controls";
Expand Down Expand Up @@ -86,14 +87,19 @@ export const RULESETS = HUB + "/rulesets";
export const FILES = HUB + "/files";
export const CACHE = HUB + "/cache/m2";

export const ANALYSIS_DEPENDENCIES = HUB + "/analyses/dependencies";
export const ANALYSIS_DEPENDENCIES = HUB + "/analyses/report/dependencies";
export const ANALYSIS_REPORT_RULES = HUB + "/analyses/report/rules";
export const ANALYSIS_REPORT_ISSUES_APPS =
HUB + "/analyses/report/issues/applications";
export const ANALYSIS_REPORT_APP_ISSUES =
HUB + "/analyses/report/applications/:applicationId/issues";
export const ANALYSIS_REPORT_ISSUE_FILES =
HUB + "/analyses/report/issues/:issueId/files";

export const ANALYSIS_REPORT_APP_DEPENDENCIES =
HUB + "/analyses/report/dependencies/applications";

export const ANALYSIS_REPORT_FILES = HUB + "/analyses/report/issues/:id/files";
export const ANALYSIS_ISSUES = HUB + "/analyses/issues";
export const ANALYSIS_ISSUE_INCIDENTS =
HUB + "/analyses/issues/:issueId/incidents";
Expand Down Expand Up @@ -616,8 +622,7 @@ export const getIssueReports = (
ANALYSIS_REPORT_APP_ISSUES.replace(
"/:applicationId/",
`/${String(applicationId)}/`
),
params
)
);

export const getIssues = (params: HubRequestParams = {}) =>
Expand Down Expand Up @@ -653,6 +658,12 @@ export const getIncidents = (issueId?: number, params: HubRequestParams = {}) =>
export const getDependencies = (params: HubRequestParams = {}) =>
getHubPaginatedResult<AnalysisDependency>(ANALYSIS_DEPENDENCIES, params);

export const getAppDependencies = (params: HubRequestParams = {}) =>
getHubPaginatedResult<AnalysisAppDependency>(
ANALYSIS_REPORT_APP_DEPENDENCIES,
params
);

// Tickets
export const createTickets = (payload: New<Ticket>, applications: Ref[]) => {
const promises: AxiosPromise[] = [];
Expand Down
107 changes: 91 additions & 16 deletions client/src/app/pages/dependencies/dependencies.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import * as React from "react";
import {
Button,
Label,
LabelGroup,
PageSection,
PageSectionVariants,
Text,
Expand Down Expand Up @@ -34,24 +37,67 @@ import {
import { useFetchDependencies } from "@app/queries/dependencies";
import { useSelectionState } from "@migtools/lib-ui";
import { getHubRequestParams } from "@app/shared/hooks/table-controls";
import { PageDrawerContent } from "@app/shared/page-drawer-context";
import { DependencyAppsDetailDrawer } from "./dependency-apps-detail-drawer";
import { useSharedAffectedApplicationFilterCategories } from "../issues/helpers";
import spacing from "@patternfly/react-styles/css/utilities/Spacing/spacing";

export const Dependencies: React.FC = () => {
const { t } = useTranslation();

const allAffectedApplicationsFilterCategories =
useSharedAffectedApplicationFilterCategories();

const tableControlState = useTableControlUrlParams({
columnNames: {
name: "Dependency name",
foundIn: "Found in",
provider: "Language",
labels: "Labels",
sha: "SHA",
version: "Version",
},
sortableColumns: ["name", "version"],
initialSort: null,
sortableColumns: ["name", "foundIn", "labels"],
initialSort: { columnKey: "name", direction: "asc" },
filterCategories: [
...allAffectedApplicationsFilterCategories,
{
key: "name",
title: t("terms.name"),
type: FilterType.search,
filterGroup: "Dependency",
placeholderText:
t("actions.filterBy", {
what: t("terms.name").toLowerCase(),
}) + "...",
getServerFilterValue: (value) => (value ? [`*${value[0]}*`] : []),
},
{
key: "provider",
title: t("terms.language"),
type: FilterType.search,
filterGroup: "Dependency",
placeholderText:
t("actions.filterBy", {
what: t("terms.language").toLowerCase(),
}) + "...",
getServerFilterValue: (value) => (value ? [`*${value[0]}*`] : []),
},
{
key: "version",
title: t("terms.label"),
type: FilterType.search,
filterGroup: "Dependency",
placeholderText:
t("actions.filterBy", {
what: t("terms.label").toLowerCase(),
}) + "...",
getServerFilterValue: (value) => (value ? [`*${value[0]}*`] : []),
},
{
key: "sha",
title: "SHA",
type: FilterType.search,
filterGroup: "Dependency",
placeholderText:
t("actions.filterBy", {
what: t("terms.name").toLowerCase(),
Expand All @@ -72,7 +118,8 @@ export const Dependencies: React.FC = () => {
...tableControlState, // Includes filterState, sortState and paginationState
hubSortFieldKeys: {
name: "name",
version: "version",
foundIn: "applications",
labels: "labels",
},
})
);
Expand Down Expand Up @@ -101,7 +148,7 @@ export const Dependencies: React.FC = () => {
getTdProps,
getClickableTrProps,
},
activeRowDerivedState: { activeRowItem, clearActiveRow },
activeRowDerivedState: { activeRowItem, clearActiveRow, setActiveRowItem },
} = tableControls;

return (
Expand Down Expand Up @@ -135,7 +182,10 @@ export const Dependencies: React.FC = () => {
<TableHeaderContentWithControls {...tableControls}>
<Th {...getThProps({ columnKey: "name" })} />
<Th {...getThProps({ columnKey: "foundIn" })} />
<Th {...getThProps({ columnKey: "provider" })} />
<Th {...getThProps({ columnKey: "labels" })} />
<Th {...getThProps({ columnKey: "version" })} />
<Th {...getThProps({ columnKey: "sha" })} />
</TableHeaderContentWithControls>
</Tr>
</Thead>
Expand All @@ -161,16 +211,45 @@ export const Dependencies: React.FC = () => {
width={10}
{...getTdProps({ columnKey: "foundIn" })}
>
{/* TODO - the applications property disappeared in the API? */}
{/*dependency.applications.length} applications*/}
TODO
<Button
className={spacing.pl_0}
variant="link"
onClick={(_) => {
if (
activeRowItem &&
activeRowItem === dependency
) {
clearActiveRow();
} else {
setActiveRowItem(dependency);
}
}}
>
{`${dependency.applications} application(s)`}
</Button>
</Td>
<Td
width={10}
{...getTdProps({ columnKey: "provider" })}
>
{dependency.provider}
</Td>
<Td width={10} {...getTdProps({ columnKey: "labels" })}>
<LabelGroup>
{dependency?.labels?.map((label) => {
return <Label>{label}</Label>;
})}
</LabelGroup>
</Td>
<Td
width={10}
{...getTdProps({ columnKey: "version" })}
>
{dependency.version}
</Td>
<Td width={10} {...getTdProps({ columnKey: "sha" })}>
{dependency.sha}
</Td>
</TableRowContentWithControls>
</Tr>
</Tbody>
Expand All @@ -185,14 +264,10 @@ export const Dependencies: React.FC = () => {
/>
</div>
</PageSection>
<PageDrawerContent
isExpanded={!!activeRowItem}
onCloseClick={clearActiveRow}
focusKey={activeRowItem?.name}
pageKey="analysis-dependencies"
>
TODO details about dependency {activeRowItem?.name} here!
</PageDrawerContent>
<DependencyAppsDetailDrawer
dependency={activeRowItem || null}
onCloseClick={() => setActiveRowItem(null)}
></DependencyAppsDetailDrawer>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import * as React from "react";
import {
IPageDrawerContentProps,
PageDrawerContent,
} from "@app/shared/page-drawer-context";
import {
TextContent,
Text,
Title,
Tabs,
TabTitleText,
Tab,
} from "@patternfly/react-core";
import spacing from "@patternfly/react-styles/css/utilities/Spacing/spacing";
import { AnalysisDependency } from "@app/api/models";
import { StateNoData } from "@app/shared/components/app-table/state-no-data";
import { DependencyAppsTable } from "./dependency-apps-table";

export interface IDependencyAppsDetailDrawerProps
extends Pick<IPageDrawerContentProps, "onCloseClick"> {
dependency: AnalysisDependency | null;
}

enum TabKey {
Applications = 0,
}

export const DependencyAppsDetailDrawer: React.FC<
IDependencyAppsDetailDrawerProps
> = ({ dependency, onCloseClick }) => {
const [activeTabKey, setActiveTabKey] = React.useState<TabKey>(
TabKey.Applications
);

return (
<PageDrawerContent
isExpanded={!!dependency}
onCloseClick={onCloseClick}
focusKey={dependency?.name}
pageKey="analysis-app-dependencies"
drawerPanelContentProps={{ defaultSize: "600px" }}
>
{!dependency ? (
<StateNoData />
) : (
<>
<TextContent>
<Text component="small" className={spacing.mb_0}>
Dependencies
</Text>
<Title headingLevel="h2" size="lg" className={spacing.mtXs}>
{dependency?.name || ""}
</Title>
</TextContent>
<Tabs
activeKey={activeTabKey}
onSelect={(_event, tabKey) => setActiveTabKey(tabKey as TabKey)}
className={spacing.mtLg}
>
<Tab
eventKey={TabKey.Applications}
title={<TabTitleText>Applications</TabTitleText>}
>
{dependency ? (
<DependencyAppsTable dependency={dependency} />
) : null}
</Tab>
</Tabs>
</>
)}
</PageDrawerContent>
);
};
Loading

0 comments on commit b81903a

Please sign in to comment.