Skip to content

Commit

Permalink
Create mock single app assessment flow from questionnaire
Browse files Browse the repository at this point in the history
Signed-off-by: ibolton336 <ibolton@redhat.com>

Take/retake flow for assessments

Share questions and answers tables

Hide answer key in assessment review screen

Navigate back to assessment actions after assessment

Signed-off-by: ibolton336 <ibolton@redhat.com>

Separate out archived questionnaires

Add continue button logic

Add delete assessment mock and functionality

Move shared questionnaire data

Signed-off-by: ibolton336 <ibolton@redhat.com>

Add comments

Signed-off-by: ibolton336 <ibolton@redhat.com>

Use RQ for patchAssessment

Signed-off-by: ibolton336 <ibolton@redhat.com>

Refactor assessment / questionnaire summary

Signed-off-by: ibolton336 <ibolton@redhat.com>

wire up delete for mock apps

Signed-off-by: ibolton336 <ibolton@redhat.com>

cleanup

Signed-off-by: ibolton336 <ibolton@redhat.com>

Fix invalidate queries after assessment patch

Signed-off-by: ibolton336 <ibolton@redhat.com>

Fix css

Signed-off-by: ibolton336 <ibolton@redhat.com>

Updates to match api

Signed-off-by: ibolton336 <ibolton@redhat.com>

Match hub status with api

Signed-off-by: ibolton336 <ibolton@redhat.com>

Test question updates

Signed-off-by: ibolton336 <ibolton@redhat.com>

Fix questionnaire by ID

Signed-off-by: ibolton336 <ibolton@redhat.com>

update tests

Signed-off-by: ibolton336 <ibolton@redhat.com>
  • Loading branch information
ibolton336 committed Sep 11, 2023
1 parent f52d7e2 commit b3c085b
Show file tree
Hide file tree
Showing 32 changed files with 1,860 additions and 839 deletions.
3 changes: 2 additions & 1 deletion client/src/app/Paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export enum Paths {
applicationsImportsDetails = "/applications/application-imports/:importId",
applicationsAssessment = "/applications/assessment/:assessmentId",
assessmentActions = "/applications/assessment-actions/:applicationId",
assessmentSummary = "/applications/assessment-summary/:assessmentId",
applicationsReview = "/applications/application/:applicationId/review",
applicationsAnalysis = "/applications/analysis",
archetypes = "/archetypes",
Expand Down Expand Up @@ -40,7 +41,7 @@ export enum Paths {
proxies = "/proxies",
migrationTargets = "/migration-targets",
assessment = "/assessment",
questionnaire = "/questionnaire",
questionnaire = "/questionnaire/:questionnaireId",
jira = "/jira",
}

Expand Down
14 changes: 13 additions & 1 deletion client/src/app/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,23 @@ const AssessmentSettings = lazy(
"./pages/assessment-management/assessment-settings/assessment-settings-page"
)
);

const Questionnaire = lazy(
() => import("./pages/assessment-management/questionnaire/questionnaire-page")
);

const AssessmentActions = lazy(
() =>
import("./pages/applications/assessment-actions/assessment-actions-page")
);
const Archetypes = lazy(() => import("./pages/archetypes/archetypes-page"));

const AssessmentSummary = lazy(
() =>
import(
"./pages/applications/application-assessment/components/assessment-summary/assessment-summary-page"
)
);
export interface IRoute {
path: string;
comp: React.ComponentType<any>;

Check warning on line 62 in client/src/app/Routes.tsx

View workflow job for this annotation

GitHub Actions / unit-test (18.x)

Unexpected any. Specify a different type
Expand Down Expand Up @@ -77,7 +85,11 @@ export const devRoutes: IRoute[] = [
comp: AssessmentActions,
exact: false,
},

{
path: Paths.assessmentSummary,
comp: AssessmentSummary,
exact: false,
},
{
path: Paths.applicationsReview,
comp: Reviews,
Expand Down
3 changes: 2 additions & 1 deletion client/src/app/api/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export interface Application {
binary?: string;
migrationWave: Ref | null;
assessments?: Ref[];
assessed?: boolean;
}

export interface Review {
Expand Down Expand Up @@ -696,7 +697,7 @@ export interface Thresholds {
unknown: number;
yellow: number;
}
export type AssessmentStatus = "EMPTY" | "STARTED" | "COMPLETE";
export type AssessmentStatus = "empty" | "started" | "complete";
export type Risk = "GREEN" | "AMBER" | "RED" | "UNKNOWN";

export interface InitialAssessment {
Expand Down
18 changes: 14 additions & 4 deletions client/src/app/api/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,15 +237,25 @@ export const getAssessments = (filters: {
.then((response) => response.data);
};

export const getAssessmentsByAppId = (
applicationId?: number | string
): Promise<Assessment[]> => {
return axios
.get(`${APPLICATIONS}/${applicationId}/assessments`)
.then((response) => response.data);
};

export const createAssessment = (
obj: InitialAssessment
): Promise<Assessment> => {
return axios.post(`${ASSESSMENTS}`, obj).then((response) => response.data);
return axios
.post(`${APPLICATIONS}/${obj?.application?.id}/assessments`, obj)
.then((response) => response.data);
};

export const patchAssessment = (obj: Assessment): AxiosPromise<Assessment> => {
export const updateAssessment = (obj: Assessment): Promise<Assessment> => {
return axios
.patch(`${ASSESSMENTS}/${obj.id}`, obj)
.put(`${ASSESSMENTS}/${obj.id}`, obj)
.then((response) => response.data);
};

Expand Down Expand Up @@ -732,7 +742,7 @@ export const getQuestionnaires = (): Promise<Questionnaire[]> =>
export const getQuestionnaireById = (
id: number | string
): Promise<Questionnaire> =>
axios.get(`${QUESTIONNAIRES}/id/${id}`).then((response) => response.data);
axios.get(`${QUESTIONNAIRES}/${id}`).then((response) => response.data);

export const createQuestionnaire = (
obj: Questionnaire
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,23 @@ import spacing from "@patternfly/react-styles/css/utilities/Spacing/spacing";
import { IconedStatus } from "@app/components/IconedStatus";
import { TimesCircleIcon } from "@patternfly/react-icons";
import { WarningTriangleIcon } from "@patternfly/react-icons";

export interface IAnswerTableProps {
answers: Answer[];
hideAnswerKey?: boolean;
}

const AnswerTable: React.FC<IAnswerTableProps> = ({ answers }) => {
const AnswerTable: React.FC<IAnswerTableProps> = ({
answers,
hideAnswerKey,
}) => {
const { t } = useTranslation();

const tableControls = useLocalTableControls({
idProperty: "text",
items: answers,
items: hideAnswerKey
? answers.filter((answer) => answer.selected)
: answers,
columnNames: {
choice: "Answer choice",
weight: "Weight",
Expand Down Expand Up @@ -99,10 +106,10 @@ const AnswerTable: React.FC<IAnswerTableProps> = ({ answers }) => {
>
Tags to be applied:
</Text>
{answer?.autoAnswerFor?.map((tag: any) => {
{answer?.autoAnswerFor?.map((tag, index) => {
return (
<div style={{ flex: "0 0 6em" }}>
<Label color="grey">{tag.tag}</Label>
<div key={index} style={{ flex: "0 0 6em" }}>
<Label color="grey">{tag.tag.name}</Label>
</div>
);
})}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import React, { useState, useMemo } from "react";
import {
Tabs,
Tab,
SearchInput,
Toolbar,
ToolbarItem,
ToolbarContent,
TextContent,
PageSection,
PageSectionVariants,
Breadcrumb,
BreadcrumbItem,
Button,
Text,
} from "@patternfly/react-core";
import AngleLeftIcon from "@patternfly/react-icons/dist/esm/icons/angle-left-icon";
import { Link } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { Paths } from "@app/Paths";
import { ConditionalRender } from "@app/components/ConditionalRender";
import { AppPlaceholder } from "@app/components/AppPlaceholder";
import QuestionsTable from "@app/components/questions-table/questions-table";
import { Assessment, Questionnaire } from "@app/api/models";
import QuestionnaireSectionTabTitle from "./components/questionnaire-section-tab-title";
import { AxiosError } from "axios";
import { formatPath } from "@app/utils/utils";

export enum SummaryType {
Assessment = "Assessment",
Questionnaire = "Questionnaire",
}

interface QuestionnaireSummaryProps {
isFetching: boolean;
fetchError: AxiosError | null;
summaryData: Assessment | Questionnaire | undefined;
summaryType: SummaryType;
}

const QuestionnaireSummary: React.FC<QuestionnaireSummaryProps> = ({
summaryData,
summaryType,
isFetching,
fetchError,
}) => {
const { t } = useTranslation();

const [activeSectionIndex, setActiveSectionIndex] = useState<"all" | number>(
"all"
);

const handleTabClick = (
_event: React.MouseEvent<any> | React.KeyboardEvent | MouseEvent,
tabKey: string | number
) => {
setActiveSectionIndex(tabKey as "all" | number);
};

const [searchValue, setSearchValue] = useState("");

const filteredSummaryData = useMemo<Assessment | Questionnaire | null>(() => {
if (!summaryData) return null;

return {
...summaryData,
sections: summaryData?.sections.map((section) => ({
...section,
questions: section.questions.filter(({ text, explanation }) =>
[text, explanation].some(
(text) => text?.toLowerCase().includes(searchValue.toLowerCase())
)
),
})),
};
}, [summaryData, searchValue]);

const allQuestions =
summaryData?.sections.flatMap((section) => section.questions) || [];
const allMatchingQuestions =
filteredSummaryData?.sections.flatMap((section) => section.questions) || [];

if (!summaryData) {
return <div>No data available.</div>;
}
const BreadcrumbPath =
summaryType === SummaryType.Assessment ? (
<Breadcrumb>
<BreadcrumbItem>
<Link
to={formatPath(Paths.assessmentActions, {
applicationId: (summaryData as Assessment)?.application?.id,
})}
>
Assessment
</Link>
</BreadcrumbItem>
<BreadcrumbItem to="#" isActive>
{summaryData?.name}
</BreadcrumbItem>
</Breadcrumb>
) : (
<Breadcrumb>
<BreadcrumbItem>
<Link to={Paths.assessment}>Assessment</Link>
</BreadcrumbItem>
<BreadcrumbItem to="#" isActive>
{summaryData?.name}
</BreadcrumbItem>
</Breadcrumb>
);
return (
<>
<PageSection variant={PageSectionVariants.light}>
<TextContent>
<Text component="h1">{summaryType}</Text>
</TextContent>
{BreadcrumbPath}
</PageSection>
<PageSection>
<ConditionalRender when={isFetching} then={<AppPlaceholder />}>
<div
style={{
backgroundColor: "var(--pf-v5-global--BackgroundColor--100)",
}}
>
<Toolbar>
<ToolbarContent>
<ToolbarItem widths={{ default: "300px" }}>
<SearchInput
placeholder="Search questions"
value={searchValue}
onChange={(_event, value) => setSearchValue(value)}
onClear={() => setSearchValue("")}
resultsCount={
(searchValue && allMatchingQuestions.length) || undefined
}
/>
</ToolbarItem>
</ToolbarContent>
</Toolbar>

<Link
to={
summaryType === SummaryType.Assessment
? formatPath(Paths.assessmentActions, {
applicationId: (summaryData as Assessment)?.application
?.id,
})
: Paths.assessment
}
>
<Button variant="link" icon={<AngleLeftIcon />}>
Back to {summaryType.toLowerCase()}
</Button>
</Link>
<div className="tabs-vertical-container">
<Tabs
activeKey={activeSectionIndex}
onSelect={handleTabClick}
isVertical
aria-label="Tabs for summaryData sections"
role="region"
>
{[
<Tab
key="all"
eventKey="all"
title={
<QuestionnaireSectionTabTitle
isSearching={!!searchValue}
sectionName="All questions"
unfilteredQuestions={allQuestions}
filteredQuestions={allMatchingQuestions}
/>
}
>
<QuestionsTable
fetchError={fetchError}
questions={allMatchingQuestions}
isSearching={!!searchValue}
data={summaryData}
isAllQuestionsTab
hideAnswerKey={summaryType === SummaryType.Assessment}
/>
</Tab>,
...(summaryData?.sections.map((section, index) => {
const filteredQuestions =
filteredSummaryData?.sections[index]?.questions || [];
return (
<Tab
key={index}
eventKey={index}
title={
<QuestionnaireSectionTabTitle
isSearching={!!searchValue}
sectionName={section.name}
unfilteredQuestions={section.questions}
filteredQuestions={filteredQuestions}
/>
}
>
<QuestionsTable
fetchError={fetchError}
questions={filteredQuestions}
isSearching={!!searchValue}
data={summaryData}
hideAnswerKey={summaryType === SummaryType.Assessment}
/>
</Tab>
);
}) || []),
]}
</Tabs>
</div>
</div>
</ConditionalRender>
</PageSection>
</>
);
};

export default QuestionnaireSummary;
Loading

0 comments on commit b3c085b

Please sign in to comment.