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 assessed archetypes section in drawer #1610

Merged
merged 6 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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 @@ -184,6 +184,10 @@
"duplicateWave": "The migration wave could not be created due to a conflict with an existing wave. Make sure the name and start/end dates are unique and try again.",
"importErrorCheckDocumentation": "For status Error imports, check the documentation to ensure your file is structured correctly.",
"insecureTracker": "Insecure mode deactivates certificate verification. Use insecure mode for instances that have self-signed certificates.",
"inheritedReviewTooltip": "This application is inheriting a review from an archetype.",
"inheritedReviewTooltip_plural": "This application is inheriting reviews from {{count}} archetypes.",
"inheritedAssessmentTooltip": "This application is inheriting an assessment from an archetype.",
"inheritedAssessmentTooltip_plural": "This application is inheriting assessments from {{count}} archetypes.",
"jiraInstanceNotConnected": "Jira instance {{name}} is not connected.",
"manageDependenciesInstructions": "Add northbound and southbound dependencies for the selected application here. Note that any selections made will be saved automatically. To undo any changes, you must manually delete the applications from the dropdowns.",
"noDataAvailableBody": "No data available to be shown here.",
Expand Down Expand Up @@ -237,6 +241,7 @@
"associatedApplications": "Associated applications",
"associatedArchetypes": "Associated archetypes",
"archetypesReviewed": "Archetypes reviewed",
"archetypesAssessed": "Archetypes assessed",
"add": "Add",
"additionalNotesOrComments": "Additional notes or comments",
"adoptionCandidateDistribution": "Application confidence and risk",
Expand Down Expand Up @@ -327,6 +332,7 @@
"inProgress": "In-progress",
"instanceType": "Instance type",
"instance": "Instance",
"inherited": "Inherited",
"issueType": "Issue type",
"jiraConfig": "Jira configuration",
"issue": "Issue",
Expand Down
40 changes: 36 additions & 4 deletions client/src/app/components/IconedStatus.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import React from "react";
import { Flex, FlexItem, Icon } from "@patternfly/react-core";
import { Flex, FlexItem, Icon, Tooltip } from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import CheckCircleIcon from "@patternfly/react-icons/dist/esm/icons/check-circle-icon";
import TimesCircleIcon from "@patternfly/react-icons/dist/esm/icons/times-circle-icon";
import InProgressIcon from "@patternfly/react-icons/dist/esm/icons/in-progress-icon";
import ExclamationCircleIcon from "@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon";
import UnknownIcon from "@patternfly/react-icons/dist/esm/icons/unknown-icon";
import QuestionCircleIcon from "@patternfly/react-icons/dist/esm/icons/question-circle-icon";

export type IconedStatusPreset =
| "InheritedReviews"
| "InheritedAssessments"
| "Canceled"
| "Completed"
| "Error"
Expand Down Expand Up @@ -35,6 +38,8 @@
icon?: React.ReactNode;
className?: string;
label?: React.ReactNode | string;
tooltipMessage?: string;
tooltipCount?: number;
}

export const IconedStatus: React.FC<IIconedStatusProps> = ({
Expand All @@ -43,9 +48,26 @@
icon,
className = "",
label,
tooltipCount = 0,
}: IIconedStatusProps) => {
const { t } = useTranslation();
const presets: IconedStatusPresetType = {
InheritedReviews: {
icon: <QuestionCircleIcon />,
status: "info",
label: t("terms.inherited"),
tooltipMessage: t("message.inheritedReviewTooltip", {
count: tooltipCount,
}),
},
InheritedAssessments: {
icon: <QuestionCircleIcon />,
status: "info",
label: t("terms.inherited"),
tooltipMessage: t("message.inheritedAssessmentTooltip", {
count: tooltipCount,
}),
},
Canceled: {
icon: <TimesCircleIcon />,
status: "info",
Expand Down Expand Up @@ -89,16 +111,26 @@
},
};
const presetProps = preset && presets[preset];
const IconWithOptionalTooltip: React.FC<{ children: React.ReactElement }> = ({
children,
}) =>
presetProps?.tooltipMessage ? (
<Tooltip content={presetProps?.tooltipMessage}>{children}</Tooltip>

Check warning on line 118 in client/src/app/components/IconedStatus.tsx

View check run for this annotation

Codecov / codecov/patch

client/src/app/components/IconedStatus.tsx#L118

Added line #L118 was not covered by tests
) : (
<>{children}</>
);

return (
<Flex
flexWrap={{ default: "nowrap" }}
spaceItems={{ default: "spaceItemsSm" }}
>
<FlexItem>
<Icon status={status || presetProps?.status} className={className}>
{icon || presetProps?.icon || <UnknownIcon />}
</Icon>
<IconWithOptionalTooltip>
<Icon status={status || presetProps?.status} className={className}>
{icon || presetProps?.icon || <UnknownIcon />}
</Icon>
</IconWithOptionalTooltip>
</FlexItem>
<FlexItem>{label || presetProps?.label}</FlexItem>
</Flex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ import {
MenuToggle,
MenuToggleElement,
Modal,
Flex,
FlexItem,
} from "@patternfly/react-core";
import { PencilAltIcon, TagIcon, EllipsisVIcon } from "@patternfly/react-icons";
import {
Expand All @@ -30,7 +28,6 @@ import {
ActionsColumn,
Tbody,
} from "@patternfly/react-table";
import { QuestionCircleIcon } from "@patternfly/react-icons/dist/esm/icons/question-circle-icon";

// @app components and utilities
import { AppPlaceholder } from "@app/components/AppPlaceholder";
Expand All @@ -44,7 +41,6 @@ import {
ConditionalTableBody,
TableRowContentWithControls,
} from "@app/components/TableControls";
import { IconedStatus } from "@app/components/IconedStatus";
import { ToolbarBulkSelector } from "@app/components/ToolbarBulkSelector";
import { ConfirmDialog } from "@app/components/ConfirmDialog";
import { NotificationsContext } from "@app/components/NotificationsContext";
Expand Down Expand Up @@ -108,14 +104,14 @@ import {
getTaskById,
} from "@app/api/rest";
import { ApplicationDependenciesForm } from "@app/components/ApplicationDependenciesFormContainer/ApplicationDependenciesForm";
import { useFetchArchetypes } from "@app/queries/archetypes";
import { useState } from "react";
import { ApplicationAnalysisStatus } from "../components/application-analysis-status";
import { ApplicationDetailDrawer } from "../components/application-detail-drawer/application-detail-drawer";
import { SimpleDocumentViewerModal } from "@app/components/SimpleDocumentViewer";
import { AnalysisWizard } from "../analysis-wizard/analysis-wizard";
import { TaskGroupProvider } from "../analysis-wizard/components/TaskGroupContext";
import { ApplicationIdentityForm } from "../components/application-identity-form/application-identity-form";
import { ApplicationReviewStatus } from "../components/application-review-status/application-review-status";

export const ApplicationsTable: React.FC = () => {
const { t } = useTranslation();
Expand Down Expand Up @@ -221,8 +217,6 @@ export const ApplicationsTable: React.FC = () => {
refetch: fetchApplications,
} = useFetchApplications();

const { archetypes } = useFetchArchetypes();

const onDeleteApplicationSuccess = (appIDCount: number) => {
pushNotification({
title: t("toastr.success.applicationDeleted", {
Expand Down Expand Up @@ -870,23 +864,6 @@ export const ApplicationsTable: React.FC = () => {
>
<Tbody>
{currentPageItems?.map((application, rowIndex) => {
const isAppReviewed = !!application.review;
const applicationArchetypes = application.archetypes?.map(
(archetypeRef) => {
return archetypes.find(
(archetype) => archetype.id === archetypeRef.id
);
}
);

const hasReviewedArchetype = applicationArchetypes?.some(
(archetype) => !!archetype?.review
);

const hasAssessedArchetype = applicationArchetypes?.some(
(archetype) => !!archetype?.assessments?.length
);

return (
<Tr
key={application.name}
Expand Down Expand Up @@ -920,50 +897,20 @@ export const ApplicationsTable: React.FC = () => {
modifier="truncate"
{...getTdProps({ columnKey: "assessment" })}
>
<Flex alignItems={{ default: "alignItemsCenter" }}>
<FlexItem>
<ApplicationAssessmentStatus
application={application}
/>
</FlexItem>
<FlexItem>
{hasAssessedArchetype ? (
<ConditionalTooltip
isTooltipEnabled={hasAssessedArchetype || false}
content={t("message.archetypeAlreadyAssessed")}
>
<QuestionCircleIcon />
</ConditionalTooltip>
) : null}
</FlexItem>
</Flex>
<ApplicationAssessmentStatus
application={application}
key={`${application?.id}-assessment-status`}
/>
</Td>
<Td
width={15}
modifier="truncate"
{...getTdProps({ columnKey: "review" })}
>
<Flex alignItems={{ default: "alignItemsCenter" }}>
<FlexItem>
<IconedStatus
preset={
isAppReviewed || hasReviewedArchetype
? "Completed"
: "NotStarted"
}
/>
</FlexItem>
<FlexItem>
{hasReviewedArchetype ? (
<ConditionalTooltip
isTooltipEnabled={hasReviewedArchetype || false}
content={t("message.archetypeAlreadyReviewed")}
>
<QuestionCircleIcon />
</ConditionalTooltip>
) : null}
</FlexItem>
</Flex>
<ApplicationReviewStatus
application={application}
key={`${application?.id}-review-status`}
/>
</Td>
<Td
width={10}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,65 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { Spinner } from "@patternfly/react-core";

import { EmptyTextMessage } from "@app/components/EmptyTextMessage";
import { Application } from "@app/api/models";
import { IconedStatus } from "@app/components/IconedStatus";
import { IconedStatus, IconedStatusPreset } from "@app/components/IconedStatus";
import { useFetchAssessmentsByItemId } from "@app/queries/assessments";
import { useFetchQuestionnaires } from "@app/queries/questionnaires";

export interface ApplicationAssessmentStatusProps {
import { useFetchArchetypes } from "@app/queries/archetypes";
interface ApplicationAssessmentStatusProps {
application: Application;
isLoading?: boolean;
}

export const ApplicationAssessmentStatus: React.FC<
ApplicationAssessmentStatusProps
> = ({ application, isLoading = false }) => {
> = ({ application }) => {
const { t } = useTranslation();

const { archetypes, isFetching } = useFetchArchetypes();

const applicationArchetypes = application.archetypes?.map((archetypeRef) => {
return archetypes?.find((archetype) => archetype.id === archetypeRef.id);
});

const hasAssessedArchetype = applicationArchetypes?.some(
(archetype) => !!archetype?.assessments?.length ?? 0 > 0
);

const {
assessments,
isFetching: isFetchingAssessmentsById,
fetchError,
} = useFetchAssessmentsByItemId(false, application.id);
const { questionnaires } = useFetchQuestionnaires();
const requiredQuestionnaireExists = questionnaires?.some(
(q) => q.required === true
);
//NOTE: Application.assessed is true if an app is assigned to an archetype and no required questionnaires exist
if (application?.assessed && requiredQuestionnaireExists) {
return <IconedStatus preset="Completed" />;
}

if (fetchError) {
return <EmptyTextMessage message={t("terms.notAvailable")} />;
}

if (isLoading || isFetchingAssessmentsById) {
if (isFetching || isFetchingAssessmentsById) {
return <Spinner size="md" />;
}

if (
assessments?.some((a) => a.status === "started" || a.status === "complete")
let statusPreset: IconedStatusPreset = "NotStarted"; // Default status
let tooltipCount: number = 0;
const isDirectlyAssessed =
application.assessed && (application.assessments?.length ?? 0) > 0;
if (isDirectlyAssessed) {
statusPreset = "Completed";
} else if (hasAssessedArchetype) {
statusPreset = "InheritedAssessments";
const assessedArchetypeCount =
applicationArchetypes?.filter(
(archetype) => archetype?.assessments?.length ?? 0 > 0
).length || 0;
tooltipCount = assessedArchetypeCount;
} else if (
assessments?.some(
(assessment) =>
assessment.status === "started" || assessment.status === "complete"
)
) {
return <IconedStatus preset="InProgress" />;
statusPreset = "InProgress";
}

return <IconedStatus preset="NotStarted" />;
return <IconedStatus preset={statusPreset} tooltipCount={tooltipCount} />;
};
Loading
Loading