Skip to content

Commit

Permalink
✨ Add assessed archetypes section in drawer (#1610)
Browse files Browse the repository at this point in the history
Resolves: 
https://issues.redhat.com/browse/MTA-1834
[MTA-1878](https://issues.redhat.com/browse/MTA-1878)
[Inconsistent hover text message for Assessment and Review
columns](https://issues.redhat.com/browse/MTA-1835)

<img width="1599" alt="Screenshot 2023-12-13 at 12 15 39 PM"
src="https://github.com/konveyor/tackle2-ui/assets/11218376/d98747f3-685c-4117-aa74-47ffb4fb9e5e">
<img width="1624" alt="Screenshot 2023-12-13 at 12 15 32 PM"
src="https://github.com/konveyor/tackle2-ui/assets/11218376/4ffb62f7-26dd-48fb-b771-6a446790ad9a">

---------

Signed-off-by: ibolton336 <[email protected]>
  • Loading branch information
ibolton336 authored Dec 14, 2023
1 parent 3b5b3be commit 1a2f183
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 108 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 @@ -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 @@ export interface IIconedStatusProps {
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 @@ export const IconedStatus: React.FC<IIconedStatusProps> = ({
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 @@ export const IconedStatus: React.FC<IIconedStatusProps> = ({
},
};
const presetProps = preset && presets[preset];
const IconWithOptionalTooltip: React.FC<{ children: React.ReactElement }> = ({
children,
}) =>
presetProps?.tooltipMessage ? (
<Tooltip content={presetProps?.tooltipMessage}>{children}</Tooltip>
) : (
<>{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

0 comments on commit 1a2f183

Please sign in to comment.