Skip to content

Commit 447838d

Browse files
committed
chore: refactoring frontend forms
chore: almost feature complete chore: refactoring tests chore: serialized file info test: frontend tests
1 parent 6685c55 commit 447838d

12 files changed

Lines changed: 217 additions & 148 deletions

File tree

bc_obps/registration/schema/operation.py

Lines changed: 23 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from dataclasses import dataclass
1+
import json
22
from uuid import UUID
33
from registration.models.bc_obps_regulated_operation import BcObpsRegulatedOperation
44
from typing import List, Optional
@@ -12,6 +12,16 @@
1212
from registration.models import Operator, User
1313
from ninja.types import DictStrAny
1414

15+
16+
def serialize_document(doc: Document | None) -> Optional[str]:
17+
# Something similar to dataURL to allow passing metadata along with the filename.
18+
# Required because RJSF expects a string for a file field.
19+
if doc:
20+
name = doc.file.name.split('/')[-1]
21+
return json.dumps({"name": name, "id": doc.id, "status": doc.status})
22+
return None
23+
24+
1525
#### Operation schemas
1626

1727

@@ -27,17 +37,11 @@ class OperationRegistrationOut(ModelSchema):
2737

2838
@staticmethod
2939
def resolve_boundary_map(obj: Operation) -> Optional[str]:
30-
boundary_map = obj.get_boundary_map()
31-
if boundary_map:
32-
return boundary_map.file.name # type: ignore
33-
return None
40+
return serialize_document(obj.get_boundary_map())
3441

3542
@staticmethod
3643
def resolve_process_flow_diagram(obj: Operation) -> Optional[str]:
37-
process_flow_diagram = obj.get_process_flow_diagram()
38-
if process_flow_diagram:
39-
return process_flow_diagram.file.name # type: ignore
40-
return None
44+
return serialize_document(obj.get_process_flow_diagram())
4145

4246
@staticmethod
4347
def resolve_operation_has_multiple_operators(obj: Operation) -> bool:
@@ -135,19 +139,6 @@ class OptedOutOperationDetailIn(Schema):
135139
final_reporting_year: Optional[int] = None
136140

137141

138-
@dataclass
139-
class DocumentOut(ModelSchema):
140-
name: str
141-
142-
@staticmethod
143-
def resolve_name(obj: Document) -> str:
144-
return obj.file.name.split('/')[-1] # type: ignore
145-
146-
class Meta:
147-
model = Document
148-
fields = ['id', 'status']
149-
150-
151142
class OperationOut(ModelSchema):
152143
naics_code_id: Optional[int] = Field(None, alias="naics_code.id")
153144
secondary_naics_code_id: Optional[int] = Field(None, alias="secondary_naics_code.id")
@@ -211,21 +202,21 @@ class Meta:
211202

212203

213204
class OperationOutWithDocuments(OperationOut):
214-
boundary_map: Optional[DocumentOut] = None
215-
process_flow_diagram: Optional[DocumentOut] = None
216-
new_entrant_application: Optional[DocumentOut] = None
205+
boundary_map: Optional[str] = None
206+
process_flow_diagram: Optional[str] = None
207+
new_entrant_application: Optional[str] = None
217208

218209
@staticmethod
219-
def resolve_boundary_map(obj: Operation) -> Optional[Document]:
220-
return obj.get_boundary_map()
210+
def resolve_boundary_map(obj: Operation) -> Optional[str]:
211+
return serialize_document(obj.get_boundary_map())
221212

222213
@staticmethod
223-
def resolve_process_flow_diagram(obj: Operation) -> Optional[Document]:
224-
return obj.get_process_flow_diagram()
214+
def resolve_process_flow_diagram(obj: Operation) -> Optional[str]:
215+
return serialize_document(obj.get_process_flow_diagram())
225216

226217
@staticmethod
227-
def resolve_new_entrant_application(obj: Operation) -> Optional[Document]:
228-
return obj.get_new_entrant_application()
218+
def resolve_new_entrant_application(obj: Operation) -> Optional[str]:
219+
return serialize_document(obj.get_new_entrant_application())
229220

230221

231222
class OperationCreateOut(ModelSchema):
@@ -261,10 +252,7 @@ class OperationNewEntrantApplicationOut(ModelSchema):
261252

262253
@staticmethod
263254
def resolve_new_entrant_application(obj: Operation) -> Optional[str]:
264-
new_entrant_application = obj.get_new_entrant_application()
265-
if new_entrant_application:
266-
return new_entrant_application.file.name # type: ignore
267-
return None
255+
return serialize_document(obj.get_new_entrant_application())
268256

269257
class Meta:
270258
model = Operation

bciers/apps/administration/app/components/operations/OperationInformationForm.tsx

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,7 @@ const OperationInformationForm = ({
6060
const role = useSessionRole();
6161
const searchParams = useSearchParams();
6262

63-
const [fileWidgetContext, submitWithFiles] = useFileUploadWidget(
64-
`registration/operations/${operationId}`,
65-
"POST",
66-
`/operations/${operationId}`,
67-
);
63+
const [fileWidgetContext, submitWithFiles] = useFileUploadWidget();
6864

6965
const isRedirectedFromContacts = searchParams.get("from_contacts") as string;
7066

@@ -105,16 +101,12 @@ const OperationInformationForm = ({
105101
setError(undefined);
106102
const pathToRevalidate = `/operations/${operationId}`;
107103

108-
const response = await submitWithFiles(data.formData);
109-
110-
// const response = await actionHandler(
111-
// `registration/operations/${operationId}`,
112-
// "PUT",
113-
// pathToRevalidate,
114-
// {
115-
// body: JSON.stringify(data.formData),
116-
// },
117-
// );
104+
const response = await submitWithFiles(
105+
data.formData,
106+
`registration/operations/${operationId}`,
107+
"POST",
108+
`/operations/${operationId}`,
109+
);
118110

119111
if (response?.error) {
120112
// Users get this error when they select a contact that's missing address information. We include a link to the Contacts page because the user has to fix the error from there, not here in the operation form.

bciers/apps/administration/app/components/operations/OperationInformationPage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ const OperationInformationPage = async ({
1919
operation = await getOperationWithDocuments(operationId);
2020
} else throw new Error(`Invalid operation id: ${operationId}`);
2121

22-
console.log(operation);
23-
2422
if (operation?.error) throw new Error("Error fetching operation information");
2523

2624
const formSchema = await createAdministrationOperationInformationSchema(
@@ -38,6 +36,8 @@ const OperationInformationPage = async ({
3836

3937
const uiSchema = await createAdministrationOperationInformationUiSchema();
4038

39+
console.log(operation);
40+
4141
return (
4242
<>
4343
<NewTabBanner />

bciers/apps/administration/tests/components/operations/OperationInformationForm.test.tsx

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,6 @@ vi.mock("@bciers/actions/api", () => ({
5151
getBusinessStructures: vi.fn(),
5252
}));
5353

54-
const mockDataUri =
55-
"data:application/pdf;name=testpdf.pdf;scanstatus=Clean;base64,ZHVtbXk=";
56-
const mockDataUri2 =
57-
"data:application/pdf;name=testpdf2.pdf;scanstatus=Clean;base64,ZHVtbXk=";
58-
5954
// Just using a simple schema for testing purposes
6055
const testSchema: RJSFSchema = {
6156
type: "object",
@@ -151,8 +146,8 @@ const formData = {
151146
secondary_naics_code_id: 2,
152147
operation_has_multiple_operators: true,
153148
activities: [1, 2],
154-
boundary_map: mockDataUri,
155-
process_flow_diagram: mockDataUri2,
149+
boundary_map: { id: 1, name: "testboundary.pdf", status: "Clean" },
150+
process_flow_diagram: { id: 2, name: "processflow.pdf", status: "Clean" },
156151
multiple_operators_array: [
157152
{
158153
mo_is_extraprovincial_company: false,
@@ -940,8 +935,12 @@ describe("the OperationInformationForm component", () => {
940935
registration_purpose: RegistrationPurposes.REPORTING_OPERATION,
941936
regulated_products: [1],
942937
operation_representatives: [1],
943-
boundary_map: mockDataUri,
944-
process_flow_diagram: mockDataUri,
938+
boundary_map: { id: 1, name: "testboundary.pdf", status: "Clean" },
939+
process_flow_diagram: {
940+
id: 2,
941+
name: "processflow.pdf",
942+
status: "Clean",
943+
},
945944
};
946945
useSessionRole.mockReturnValue(FrontEndRoles.INDUSTRY_USER_ADMIN);
947946

@@ -991,8 +990,12 @@ describe("the OperationInformationForm component", () => {
991990
type: "Single Facility Operation",
992991
naics_code_id: 1,
993992
secondary_naics_code_id: 2,
994-
process_flow_diagram: mockDataUri,
995-
boundary_map: mockDataUri,
993+
process_flow_diagram: {
994+
id: 2,
995+
name: "processflow.pdf",
996+
status: "Clean",
997+
},
998+
boundary_map: { id: 1, name: "testboundary.pdf", status: "Clean" },
996999
operation_has_multiple_operators: false,
9971000
registration_purpose: "Reporting Operation",
9981001
operation_representatives: [2],
@@ -1042,8 +1045,8 @@ describe("the OperationInformationForm component", () => {
10421045
registration_purpose: RegistrationPurposes.REPORTING_OPERATION,
10431046
regulated_products: [1],
10441047
operation_representatives: [],
1045-
boundary_map: mockDataUri,
1046-
process_flow_diagram: mockDataUri,
1048+
boundary_map: { id: 1, name: "testboundary.pdf", status: "Clean" },
1049+
process_flow_diagram: { id: 2, name: "processflow.pdf", status: "Clean" },
10471050
status: OperationStatus.REGISTERED,
10481051
};
10491052
useSessionRole.mockReturnValue(FrontEndRoles.INDUSTRY_USER_ADMIN);

bciers/apps/registration/app/components/operations/registration/NewEntrantOperationForm.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
"use client";
22

3-
import { actionHandler } from "@bciers/actions";
43
import { IChangeEvent } from "@rjsf/core";
54
import MultiStepBase from "@bciers/components/form/MultiStepBase";
65
import { newEntrantOperationUiSchema } from "apps/registration/app/data/jsonSchema/operationRegistration/newEntrantOperation";
76
import {
87
NewEntrantOperationFormData,
98
OperationRegistrationFormProps,
109
} from "apps/registration/app/components/operations/registration/types";
10+
import { useFileUploadWidget } from "@bciers/components/form/widgets/FileWidget";
1111

1212
interface NewEntrantOperationFormProps extends OperationRegistrationFormProps {
1313
formData: NewEntrantOperationFormData;
@@ -21,14 +21,20 @@ const NewEntrantOperationForm = ({
2121
steps,
2222
}: NewEntrantOperationFormProps) => {
2323
const baseUrl = `/register-an-operation/${operation}`;
24+
25+
const [fileWidgetContext, submitWithFiles] = useFileUploadWidget();
26+
2427
const handleSubmit = async (e: IChangeEvent) => {
2528
const endpoint = `registration/operations/${operation}/registration/new-entrant-application`;
2629
// errors are handled in MultiStepBase
27-
const response = await actionHandler(endpoint, "PUT", `${baseUrl}`, {
28-
body: JSON.stringify({
30+
const response = await submitWithFiles(
31+
{
2932
new_entrant_application: e.formData.new_entrant_application,
30-
}),
31-
});
33+
},
34+
endpoint,
35+
"POST",
36+
`${baseUrl}`,
37+
);
3238
return response;
3339
};
3440

@@ -43,6 +49,7 @@ const NewEntrantOperationForm = ({
4349
step={step}
4450
steps={steps}
4551
uiSchema={newEntrantOperationUiSchema}
52+
formContext={fileWidgetContext}
4653
/>
4754
);
4855
};

bciers/apps/registration/app/components/operations/registration/OperationInformationForm.tsx

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"use client";
22

33
import MultiStepBase from "@bciers/components/form/MultiStepBase";
4-
import { actionHandler } from "@bciers/actions";
54
import { RJSFSchema, UiSchema } from "@rjsf/utils";
65
import { useState } from "react";
76
import { IChangeEvent } from "@rjsf/core";
@@ -19,6 +18,7 @@ import { eioOperationInformationSchema } from "@/administration/app/data/jsonSch
1918
import ConfirmChangeOfFieldModal from "@/registration/app/components/operations/registration/ConfirmChangeOfFieldModal";
2019
import useKey from "@bciers/utils/src/useKey";
2120
import { Dict } from "@bciers/types/dictionary";
21+
import { useFileUploadWidget } from "@bciers/components/form/widgets/FileWidget";
2222

2323
interface OperationInformationFormProps {
2424
rawFormData: Dict;
@@ -56,6 +56,7 @@ const OperationInformationForm = ({
5656
const searchParams = useSearchParams();
5757
const continueRegistration =
5858
searchParams.get("continueRegistration") === "true";
59+
const [fileWidgetContext, submitWithFiles] = useFileUploadWidget();
5960
const [currentUiSchema, setCurrentUiSchema] = useState({
6061
...uiSchema,
6162
section1: {
@@ -149,22 +150,23 @@ const OperationInformationForm = ({
149150
continueRegistration || !selectedOperation;
150151

151152
const handleSubmit = async (e: IChangeEvent) => {
153+
console.error(
154+
"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~",
155+
createUnnestedFormData(e.formData, ["section1", "section2", "section3"]),
156+
);
157+
152158
const formData = e.formData;
153159
const isCreating = !formData?.section1?.operation;
154-
const postEndpoint = `registration/operations`;
155-
const putEndpoint = `registration/operations/${formData?.section1?.operation}/registration/operation`;
156-
const body = JSON.stringify(
160+
const creatingEndpoint = `registration/operations`;
161+
const updatingEndpoint = `registration/operations/${formData?.section1?.operation}/registration/operation`;
162+
163+
const response = await submitWithFiles(
157164
createUnnestedFormData(formData, ["section1", "section2", "section3"]),
158-
);
159-
const response = await actionHandler(
160-
isCreating ? postEndpoint : putEndpoint,
161-
isCreating ? "POST" : "PUT",
165+
isCreating ? creatingEndpoint : updatingEndpoint,
166+
"POST",
162167
isCreating
163168
? ""
164169
: `/register-an-operation/${formData?.section1?.operation}/${step}`,
165-
{
166-
body,
167-
},
168170
).then((resolve) => {
169171
if (resolve?.error) {
170172
return { error: resolve.error };
@@ -360,6 +362,7 @@ const OperationInformationForm = ({
360362
}}
361363
uiSchema={currentUiSchema}
362364
customValidate={customValidate}
365+
formContext={fileWidgetContext}
363366
/>
364367
</>
365368
);

bciers/apps/registration/tests/components/operations/registration/NewEntrantOperationForm.test.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ useRouter.mockReturnValue({
2424
push: mockPush,
2525
});
2626

27-
export const mockDataUri =
28-
"data:application/pdf;name=testpdf.pdf;scanstatus=Clean;base64,ZHVtbXk=";
2927
const mockFile = new File(["test"], "test.pdf", { type: "application/pdf" });
3028

3129
describe("the NewEntrantOperationForm component", () => {
@@ -63,7 +61,11 @@ describe("the NewEntrantOperationForm component", () => {
6361
render(
6462
<NewEntrantOperationForm
6563
formData={{
66-
new_entrant_application: mockDataUri,
64+
new_entrant_application: JSON.stringify({
65+
id: 1,
66+
name: "test_file.pdf",
67+
status: "Unscanned",
68+
}),
6769
}}
6870
operation="002d5a9e-32a6-4191-938c-2c02bfec592d"
6971
schema={newEntrantOperationSchema}
@@ -166,14 +168,19 @@ describe("the NewEntrantOperationForm component", () => {
166168
expect(actionHandler).toHaveBeenCalledTimes(1);
167169

168170
// date_of_first_shipment is no longer sent in the request body for 2025+ registrations
169-
expect(actionHandler).toHaveBeenCalledWith(
171+
const callArgs = actionHandler.mock.calls[0];
172+
expect(callArgs[0]).toBe(
170173
"registration/operations/002d5a9e-32a6-4191-938c-2c02bfec592d/registration/new-entrant-application",
171-
"PUT",
174+
);
175+
expect(callArgs[1]).toBe("POST");
176+
expect(callArgs[2]).toBe(
172177
"/register-an-operation/002d5a9e-32a6-4191-938c-2c02bfec592d",
173-
{
174-
body: '{"new_entrant_application":"data:application/pdf;name=test.pdf;base64,dGVzdA=="}',
175-
},
176178
);
179+
180+
const submittedFormData = Object.fromEntries(callArgs[3].body);
181+
expect(submittedFormData.payload).toEqual("123");
182+
expect(typeof submittedFormData.new_entrant_application).toBe("File");
183+
177184
await waitFor(() => {
178185
expect(mockPush).toHaveBeenCalledWith(
179186
"/register-an-operation/002d5a9e-32a6-4191-938c-2c02bfec592d/5",

0 commit comments

Comments
 (0)