Skip to content

Commit

Permalink
✨ Reports - Current Landscape update risk handling
Browse files Browse the repository at this point in the history
Custom assessments changes risk and confidence to be accessed from
`Assessment` and not `Application`.  The reports page in general, and
the `Landscape` component in specific need to be updated to use risk
values from `Assessment`.

Summary of Change:
  - `Reports`
    - Fetch `Questionnaires` and `Assessments` to allow easy use
      in the Questionnaire select menu
    - Adjust the `Card`s to be clickable and selectable when we provide
      custom actions (to avoid a console warning)

  - `Landscape`
    - Use data from props instead of fetching any additional data. The
      containing component is now responsible for controlling the
      source data.
    - Aggregate risk data into buckets matching `Risk` options
    - Setup responsive layout so the donut charts wrap nicely when the
      view becomes narrow

  - `Donut`
    - Force `id` to be provided
    - Set the width to `200px`
    - Make sure content lines up centered in its container

  - Deprecated `useFetchRisks()`

Enhancement: https://github.com/konveyor/enhancements/blob/90b827b68cc367284a66bf66f087d5c263487e05/enhancements/assessment-module/README.md#changes-in-the-application-reports-view
Part Of: #1305
Follow Up: #1374

Signed-off-by: Scott J Dickerson <[email protected]>
  • Loading branch information
sjd78 committed Sep 22, 2023
1 parent 70c029b commit b97b11a
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 193 deletions.
4 changes: 2 additions & 2 deletions client/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@
"noDataStateBody": "Create a new {{what}} to start seeing data here.",
"noDataStateTitle": "No {{what}} available",
"Nquestions": "{{n}} questions",
"ofTotalApplications": "Of {{count}} application",
"ofTotalApplications_plural": "Of {{count}} applications",
"ofTotalAssessments": "Of {{count}} assessment",
"ofTotalAssessments_plural": "Of {{count}} assessments",
"selectMany": "Select {{what}}",
"selectOne": "Select a {{what}}",
"selectAn": "Select an {{what}}",
Expand Down
2 changes: 0 additions & 2 deletions client/public/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,6 @@
"noDataStateBody": "Cree un(a) nuevo(a) {{what}} para empezar a ver datos acá.",
"noDataStateTitle": "No existen {{what}} disponibles",
"Nquestions": "{{n}} preguntas",
"ofTotalApplications": "De {{count}} aplicación",
"ofTotalApplications_plural": "De {{count}} aplicaciones",
"selectMany": "Seleccione {{what}}",
"selectOne": "Seleccione un {{what}}",
"selectAn": "Seleccione una {{what}}",
Expand Down
46 changes: 28 additions & 18 deletions client/src/app/pages/reports/components/landscape/donut.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@ import { useTranslation } from "react-i18next";
import { ChartDonut } from "@patternfly/react-charts";
import { global_palette_black_300 as black } from "@patternfly/react-tokens";

import { Stack, StackItem, Text, TextContent } from "@patternfly/react-core";
import {
Bullseye,
Stack,
StackItem,
Text,
TextContent,
} from "@patternfly/react-core";

export interface IDonutProps {
id: string;
value: number;
total: number;
color: string;
Expand All @@ -15,6 +22,7 @@ export interface IDonutProps {
}

export const Donut: React.FC<IDonutProps> = ({
id,
value,
total,
color,
Expand All @@ -24,24 +32,26 @@ export const Donut: React.FC<IDonutProps> = ({
const { t } = useTranslation();

return (
<Stack>
<StackItem style={{ height: "200px", width: "200px" }}>
<ChartDonut
ariaDesc="risk-donut-chart"
title={value.toString()}
subTitle={t("composed.ofTotalApplications", {
count: total,
}).toLocaleLowerCase()}
constrainToVisibleArea={true}
data={[
{ x: riskLabel, y: value },
{ x: t("terms.other"), y: total - value },
]}
labels={({ datum }) => `${datum.x}: ${datum.y}`}
colorScale={[color, black.value]}
/>
<Stack id={id} style={{ width: "200px" }}>
<StackItem style={{ height: "200px", width: "100%" }}>
<Bullseye>
<ChartDonut
ariaDesc="risk-donut-chart"
title={value.toString()}
subTitle={t("composed.ofTotalAssessments", {
count: total,
}).toLocaleLowerCase()}
constrainToVisibleArea={true}
data={[
{ x: riskLabel, y: value },
{ x: t("terms.other"), y: total - value },
]}
labels={({ datum }) => `${datum.x}: ${datum.y}`}
colorScale={[color, black.value]}
/>
</Bullseye>
</StackItem>
<StackItem>
<StackItem style={{ width: "100%" }}>
<TextContent className="pf-v5-u-text-align-center">
<Text component="h3">{riskLabel}</Text>
<Text component="small">{riskDescription}</Text>
Expand Down
151 changes: 76 additions & 75 deletions client/src/app/pages/reports/components/landscape/landscape.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,29 @@
import React, { useContext, useMemo } from "react";
import React, { useMemo } from "react";
import { useTranslation } from "react-i18next";

import { Skeleton, Split, SplitItem } from "@patternfly/react-core";

import { ConditionalRender } from "@app/components/ConditionalRender";
import { StateError } from "@app/components/StateError";
import { Flex, FlexItem, Skeleton } from "@patternfly/react-core";

import { RISK_LIST } from "@app/Constants";
import { Assessment, AssessmentRisk } from "@app/api/models";

import { ApplicationSelectionContext } from "../../application-selection-context";
import { NoApplicationSelectedEmptyState } from "../no-application-selected-empty-state";
import { Assessment, Questionnaire } from "@app/api/models";
import { ConditionalRender } from "@app/components/ConditionalRender";
import { Donut } from "./donut";
import { useFetchRisks } from "@app/queries/risks";

interface ILandscapeData {
low: number;
medium: number;
high: number;
interface IAggregateRiskData {
green: number;
yellow: number;
red: number;
unknown: number;
unassessed: number;
assessmentCount: number;
}

const extractLandscapeData = (
totalApps: number,
data: AssessmentRisk[]
): ILandscapeData => {
const aggregateRiskData = (assessments: Assessment[]): IAggregateRiskData => {
let low = 0;
let medium = 0;
let high = 0;
let unassessed = 0;
let unknown = 0;

data.forEach((elem) => {
switch (elem.risk) {
assessments?.forEach((assessment) => {
switch (assessment.risk) {
case "green":
low++;
break;
Expand All @@ -41,93 +33,102 @@ const extractLandscapeData = (
case "red":
high++;
break;
case "unknown":
unknown++;
break;
}
});

unassessed = totalApps - low - medium - high;
return { low, medium, high, unassessed };
return {
green: low,
yellow: medium,
red: high,
unknown,
unassessed: assessments.length - low - medium - high,
assessmentCount: assessments.length,
};
};

interface ILandscapeProps {
/**
* The selected questionnaire or `null` if _all questionnaires_ is selected.
*/
questionnaire: Questionnaire | null;

/**
* The set of assessments for the selected questionnaire. Risk values will be
* aggregated from the individual assessment risks.
*/
assessments: Assessment[];
}

export const Landscape: React.FC<ILandscapeProps> = ({ assessments }) => {
export const Landscape: React.FC<ILandscapeProps> = ({
questionnaire,
assessments,
}) => {
const { t } = useTranslation();

// Context
const { allItems: applications } = useContext(ApplicationSelectionContext);

const {
risks: assessmentRisks,
isFetching,
error: fetchError,
} = useFetchRisks(applications.map((app) => app.id!));

const landscapeData = useMemo(() => {
if (applications.length > 0 && assessmentRisks) {
return extractLandscapeData(applications.length, assessmentRisks);
} else {
return undefined;
}
}, [applications, assessmentRisks]);

if (fetchError) {
return <StateError />;
}

if (!isFetching && !landscapeData) {
return <NoApplicationSelectedEmptyState />;
}
const landscapeData = useMemo(
() => aggregateRiskData(assessments),
[assessments]
);

return (
<ConditionalRender
when={isFetching}
when={!questionnaire && !assessments}
then={
<div style={{ height: 200, width: 400 }}>
<Skeleton height="75%" width="100%" />
</div>
}
>
{landscapeData && (
<Split hasGutter>
<SplitItem>
<Flex
justifyContent={{ default: "justifyContentSpaceAround" }}
spaceItems={{ default: "spaceItemsNone" }}
gap={{ default: "gapMd" }}
>
<FlexItem>
<Donut
value={landscapeData.high}
total={applications.length}
color={RISK_LIST["red"].hexColor}
id="landscape-donut-red"
value={landscapeData.red}
total={landscapeData.assessmentCount}
color={RISK_LIST.red.hexColor}
riskLabel={t("colors.red")}
// riskDescription={}
riskDescription={questionnaire?.riskMessages?.red ?? ""}
/>
</SplitItem>
<SplitItem>
</FlexItem>
<FlexItem>
<Donut
value={landscapeData.medium}
total={applications.length}
color={RISK_LIST["yellow"].hexColor}
id="landscape-donut-yellow"
value={landscapeData.yellow}
total={landscapeData.assessmentCount}
color={RISK_LIST.yellow.hexColor}
riskLabel={t("colors.yellow")}
// riskDescription={}
riskDescription={questionnaire?.riskMessages?.yellow ?? ""}
/>
</SplitItem>
<SplitItem>
</FlexItem>
<FlexItem>
<Donut
value={landscapeData.high}
total={applications.length}
color={RISK_LIST["green"].hexColor}
id="landscape-donut-green"
value={landscapeData.green}
total={landscapeData.assessmentCount}
color={RISK_LIST.green.hexColor}
riskLabel={t("colors.green")}
// riskDescription={}
riskDescription={questionnaire?.riskMessages?.green ?? ""}
/>
</SplitItem>
<SplitItem>
</FlexItem>
<FlexItem>
<Donut
id="landscape-donut-unassessed"
value={landscapeData.unassessed}
total={applications.length}
color={RISK_LIST["unknown"].hexColor}
total={landscapeData.assessmentCount}
color={RISK_LIST.unknown.hexColor}
riskLabel={`${t("terms.unassessed")}/${t("terms.unknown")}`}
// riskDescription={}
riskDescription={questionnaire?.riskMessages?.unknown ?? ""}
/>
</SplitItem>
</Split>
</FlexItem>
</Flex>
)}
</ConditionalRender>
);
Expand Down
Loading

0 comments on commit b97b11a

Please sign in to comment.