Skip to content

Commit 36fdf4d

Browse files
authored
✨ Add upload YAML questionnaire form (#1290)
Closes [Import questionnaires](#1261) Signed-off-by: ibolton336 <[email protected]>
1 parent f8e2b7e commit 36fdf4d

File tree

3 files changed

+174
-2
lines changed

3 files changed

+174
-2
lines changed

client/public/locales/en/translation.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,10 @@
9696
"discardAssessment": "The assessment for <1>{{applicationName}}</1> will be discarded, as well as the review result. Do you wish to continue?",
9797
"leavePage": "Are you sure you want to leave this page? Be sure to save your changes, or they will be lost.",
9898
"pageError": "Oops! Something went wrong.",
99-
"refreshPage": "Try to refresh your page or contact your admin."
99+
"refreshPage": "Try to refresh your page or contact your admin.",
100+
"maxfileSize": "Max file size of 1MB exceeded. Upload a smaller file.",
101+
"dragAndDropFile": "Drag and drop your file here or upload one.",
102+
"uploadYamlFile": "Upload your YAML file"
100103
},
101104
"title": {
102105
"applicationAnalysis": "Application analysis",
@@ -362,6 +365,7 @@
362365
"unknown": "Unknown",
363366
"unsuitableForContainers": "Unsuitable for containers",
364367
"uploadApplicationFile": "Upload your application file",
368+
"uploadYamlFile": "Upload your YAML file",
365369
"url": "URL",
366370
"user": "User",
367371
"version": "Version",

client/src/app/pages/assessment-management/assessment-settings/assessment-settings-page.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import { getAxiosErrorMessage } from "@app/utils/utils";
5050
import { Questionnaire } from "@app/api/models";
5151
import { useHistory } from "react-router-dom";
5252
import { Paths } from "@app/Paths";
53+
import { ImportQuestionnaireForm } from "@app/pages/assessment/import-questionnaire-form/import-questionnaire-form";
5354

5455
const AssessmentSettings: React.FC = () => {
5556
const { t } = useTranslation();
@@ -382,7 +383,7 @@ const AssessmentSettings: React.FC = () => {
382383
isOpen={isImportModal}
383384
onClose={() => setIsImportModal(false)}
384385
>
385-
<Text>TODO Import questionnaire component</Text>
386+
<ImportQuestionnaireForm onSaved={() => setIsImportModal(false)} />
386387
</Modal>
387388
<Modal
388389
id="download.template.modal"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import React, { useState } from "react";
2+
import { AxiosResponse } from "axios";
3+
import { useTranslation } from "react-i18next";
4+
import * as yup from "yup";
5+
6+
import {
7+
ActionGroup,
8+
Button,
9+
ButtonVariant,
10+
FileUpload,
11+
Form,
12+
FormHelperText,
13+
HelperText,
14+
HelperTextItem,
15+
} from "@patternfly/react-core";
16+
17+
import { HookFormPFGroupController } from "@app/components/HookFormPFFields";
18+
import { useForm } from "react-hook-form";
19+
import { FileLoadError, IReadFile } from "@app/api/models";
20+
import { yupResolver } from "@hookform/resolvers/yup";
21+
import { useCreateFileMutation } from "@app/queries/targets";
22+
23+
export interface ImportQuestionnaireFormProps {
24+
onSaved: (response?: AxiosResponse) => void;
25+
}
26+
export interface ImportQuestionnaireFormValues {
27+
yamlFile: IReadFile;
28+
}
29+
30+
export const yamlFileSchema: yup.SchemaOf<IReadFile> = yup.object({
31+
fileName: yup.string().required(),
32+
fullFile: yup.mixed<File>(),
33+
loadError: yup.mixed<FileLoadError>(),
34+
loadPercentage: yup.number(),
35+
loadResult: yup.mixed<"danger" | "success" | undefined>(),
36+
data: yup.string(),
37+
responseID: yup.number(),
38+
});
39+
40+
export const ImportQuestionnaireForm: React.FC<
41+
ImportQuestionnaireFormProps
42+
> = ({ onSaved }) => {
43+
const { t } = useTranslation();
44+
45+
const [filename, setFilename] = useState<string>();
46+
const [isFileRejected, setIsFileRejected] = useState(false);
47+
const validationSchema: yup.SchemaOf<ImportQuestionnaireFormValues> = yup
48+
.object()
49+
.shape({
50+
yamlFile: yamlFileSchema,
51+
});
52+
const methods = useForm<ImportQuestionnaireFormValues>({
53+
resolver: yupResolver(validationSchema),
54+
mode: "onChange",
55+
});
56+
57+
const {
58+
handleSubmit,
59+
formState: { isSubmitting, isValidating, isValid, isDirty },
60+
getValues,
61+
setValue,
62+
control,
63+
watch,
64+
setFocus,
65+
clearErrors,
66+
trigger,
67+
reset,
68+
} = methods;
69+
70+
const { mutateAsync: createYamlFileAsync } = useCreateFileMutation();
71+
72+
const handleFileUpload = async (file: File) => {
73+
setFilename(file.name);
74+
const formFile = new FormData();
75+
formFile.append("file", file);
76+
77+
const newYamlFile: IReadFile = {
78+
fileName: file.name,
79+
fullFile: file,
80+
};
81+
82+
return createYamlFileAsync({
83+
formData: formFile,
84+
file: newYamlFile,
85+
});
86+
};
87+
88+
const onSubmit = (values: ImportQuestionnaireFormValues) => {
89+
console.log("values", values);
90+
onSaved();
91+
};
92+
return (
93+
<Form onSubmit={handleSubmit(onSubmit)}>
94+
<HookFormPFGroupController
95+
control={control}
96+
name="yamlFile"
97+
label={t("terms.uploadYamlFile")}
98+
fieldId="yamlFile"
99+
helperText={t("dialog.uploadYamlFile")}
100+
renderInput={({ field: { onChange, name }, fieldState: { error } }) => (
101+
<FileUpload
102+
id={`${name}-file-upload`}
103+
name={name}
104+
value={filename}
105+
filename={filename}
106+
filenamePlaceholder={t("dialog.dragAndDropFile")}
107+
dropzoneProps={{
108+
accept: {
109+
"text/yaml": [".yml", ".yaml"],
110+
},
111+
maxSize: 1000000,
112+
onDropRejected: (event) => {
113+
const currentFile = event[0];
114+
if (currentFile.file.size > 1000000) {
115+
methods.setError(name, {
116+
type: "custom",
117+
message: t("dialog.maxFileSize"),
118+
});
119+
}
120+
setIsFileRejected(true);
121+
},
122+
}}
123+
validated={isFileRejected || error ? "error" : "default"}
124+
onFileInputChange={async (_, file) => {
125+
console.log("uploading file", file);
126+
//TODO: handle new api here. This is just a placeholder.
127+
try {
128+
await handleFileUpload(file);
129+
setFocus(name);
130+
clearErrors(name);
131+
trigger(name);
132+
} catch (err) {
133+
//Handle new api error here
134+
}
135+
}}
136+
onClearClick={() => {
137+
//TODO
138+
console.log("clearing file");
139+
}}
140+
browseButtonText="Upload"
141+
/>
142+
)}
143+
/>
144+
145+
{isFileRejected && (
146+
<FormHelperText>
147+
<HelperText>
148+
<HelperTextItem variant="error">
149+
You should select a YAML file.
150+
</HelperTextItem>
151+
</HelperText>
152+
</FormHelperText>
153+
)}
154+
<ActionGroup>
155+
<Button
156+
type="submit"
157+
aria-label="submit"
158+
id="import-questionnaire-submit-button"
159+
variant={ButtonVariant.primary}
160+
isDisabled={!isValid || isSubmitting || isValidating || !isDirty}
161+
>
162+
{t("actions.import")}
163+
</Button>
164+
</ActionGroup>
165+
</Form>
166+
);
167+
};

0 commit comments

Comments
 (0)