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

✨ Add Dependencies page & drawer #1077

Merged
merged 3 commits into from
Jul 12, 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
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 @@ -27,24 +30,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();
Copy link
Member Author

Choose a reason for hiding this comment

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

Changed this shared hook name since we are using it in dependencies. Open to name suggestions if this one is confusing.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This name works for me, I can't really think of anything better than "affected application filter categories" for "filters on application properties that aren't for the app inventory page" 🙃


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.version"),
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 @@ -66,7 +112,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 @@ -95,7 +142,7 @@ export const Dependencies: React.FC = () => {
getTdProps,
getClickableTrProps,
},
activeRowDerivedState: { activeRowItem, clearActiveRow },
activeRowDerivedState: { activeRowItem, clearActiveRow, setActiveRowItem },
} = tableControls;

return (
Expand Down Expand Up @@ -129,7 +176,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 @@ -155,16 +205,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 @@ -179,14 +258,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