Skip to content

Commit

Permalink
Merge pull request #515 from zendesk/gianluca/GG-3785-conditional-fie…
Browse files Browse the repository at this point in the history
…lds-multi-level

Fixed conditional fields
  • Loading branch information
Fredx87 authored Aug 16, 2024
2 parents fbed005 + f0e6f61 commit 75a91d9
Show file tree
Hide file tree
Showing 9 changed files with 423 additions and 61 deletions.
46 changes: 23 additions & 23 deletions assets/new-request-form-bundle.js

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions jest.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ const config = {
coverageProvider: "v8",
testEnvironment: "jsdom",
preset: "rollup-jest",
transform: {
"^.+.tsx?$": ["ts-jest", {}],
},
setupFilesAfterEnv: ["@testing-library/jest-dom"],
};

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
"rollup-plugin-sass": "1.12.18",
"sass": "1.58.3",
"semantic-release": "19.0.5",
"ts-jest": "^29.2.4",
"typescript": "^5.1.6",
"url-pattern": "1.0.3",
"vinyl": "^3.0.0",
Expand Down
4 changes: 2 additions & 2 deletions src/modules/new-request-form/NewRequestForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { Alert } from "@zendeskgarden/react-notifications";
import { useFormSubmit } from "./useFormSubmit";
import { usePrefilledTicketFields } from "./usePrefilledTicketFields";
import { Attachments } from "./fields/attachments/Attachments";
import { useEndUserConditions } from "./useEndUserConditions";
import { getVisibleFields } from "./getVisibleFields";
import { DatePicker } from "./fields/DatePicker";
import { CcField } from "./fields/cc-field/CcField";
import { CreditCard } from "./fields/CreditCard";
Expand Down Expand Up @@ -108,7 +108,7 @@ export function NewRequestForm({
prefilledOrganizationField
);
const [dueDateField, setDueDateField] = useState(prefilledDueDateField);
const visibleFields = useEndUserConditions(ticketFields, end_user_conditions);
const visibleFields = getVisibleFields(ticketFields, end_user_conditions);
const { formRefCallback, handleSubmit } = useFormSubmit(ticketFields);
const { t } = useTranslation();

Expand Down
2 changes: 1 addition & 1 deletion src/modules/new-request-form/data-types/Field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export interface Field {
id: number;
name: string;
value?: string | string[] | boolean;
error: string;
error: string | null;
label: string;
required: boolean;
description: string;
Expand Down
269 changes: 269 additions & 0 deletions src/modules/new-request-form/getVisibleFields.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
import type { EndUserCondition, Field } from "./data-types";
import { getVisibleFields } from "./getVisibleFields";

const dropdownField: Field = {
id: 123,
name: "request[custom_fields][123]",
required: false,
error: null,
label: "Dropdown Field",
description: "",
type: "tagger",
options: [
{ name: "One", value: "one" },
{ name: "Two", value: "two" },
],
};

const secondDropdownField: Field = {
id: 456,
name: "request[custom_fields][456]",
required: false,
error: null,
label: "Second Dropdown Field",
description: "",
type: "tagger",
options: [
{ name: "Three", value: "three" },
{ name: "Four", value: "four" },
],
};

const textField: Field = {
id: 789,
name: "request[custom_fields][789]",
required: false,
error: null,
label: "Text Field",
description: "",
type: "text",
options: [],
};

const integerField: Field = {
id: 101,
name: "request[custom_fields][101]",
required: false,
error: null,
label: "Number Field",
description: "",
type: "number",
options: [],
};

describe("getVisibleFields", () => {
it("should return the same fields if no end user conditions are provided", () => {
const fields = [
dropdownField,
secondDropdownField,
textField,
integerField,
];
const endUserConditions: EndUserCondition[] = [];

const result = getVisibleFields(fields, endUserConditions);

expect(result).toEqual(fields);
});

it("should show fields and set required if the value matches", () => {
const fields = [
{ ...dropdownField, value: "one" },
{ ...secondDropdownField, value: "three" },
{ ...textField, required: true },
integerField,
];
const endUserConditions: EndUserCondition[] = [
{
parent_field_id: 123,
parent_field_type: "tagger",
value: "one",
child_fields: [{ id: 456, is_required: true }],
},
{
parent_field_id: 456,
parent_field_type: "tagger",
value: "three",
child_fields: [{ id: 789, is_required: false }],
},
];

const result = getVisibleFields(fields, endUserConditions);

expect(result).toEqual([
{ ...dropdownField, value: "one" },
{ ...secondDropdownField, value: "three", required: true },
textField,
integerField,
]);
});

it("should hide fields if the value does not match", () => {
const fields = [
{ ...dropdownField, value: "one" },
{ ...secondDropdownField, value: "three" },
textField,
integerField,
];
const endUserConditions: EndUserCondition[] = [
{
parent_field_id: 123,
parent_field_type: "tagger",
value: "two",
child_fields: [{ id: 456, is_required: true }],
},
{
parent_field_id: 456,
parent_field_type: "tagger",
value: "four",
child_fields: [{ id: 789, is_required: false }],
},
];

const result = getVisibleFields(fields, endUserConditions);

expect(result).toEqual([{ ...dropdownField, value: "one" }, integerField]);
});

it("should not change required if the field is not part of some condition", () => {
const fields = [
{ ...dropdownField, value: "one", required: true },
{ ...secondDropdownField, value: "three" },
{ ...textField, required: true },
{ ...integerField, required: false },
];
const endUserConditions: EndUserCondition[] = [
{
parent_field_id: 123,
parent_field_type: "tagger",
value: "one",
child_fields: [{ id: 456, is_required: true }],
},
];

const result = getVisibleFields(fields, endUserConditions);

expect(result).toEqual([
{ ...dropdownField, value: "one", required: true },
{ ...secondDropdownField, value: "three", required: true },
{ ...textField, required: true },
{ ...integerField, required: false },
]);
});

it("should hide fields with multi level conditions when the value on parent doesn't match", () => {
const fields = [
{ ...dropdownField, value: "one" },
{ ...secondDropdownField, value: "three" },
{ ...textField, value: "text" },
integerField,
];
const endUserConditions: EndUserCondition[] = [
{
parent_field_id: 123,
parent_field_type: "tagger",
value: "two",
child_fields: [
{
id: 456,
is_required: true,
},
],
},
{
parent_field_id: 456,
parent_field_type: "tagger",
value: "three",
child_fields: [
{
id: 789,
is_required: false,
},
],
},
{
parent_field_id: 789,
parent_field_type: "text",
value: "text",
child_fields: [
{
id: 101,
is_required: true,
},
],
},
];

const result = getVisibleFields(fields, endUserConditions);

expect(result).toEqual([{ ...dropdownField, value: "one" }]);
});

it("should not hide fields with multi level conditions when a second condition is met", () => {
const fields = [
{ ...dropdownField, value: "one" },
{ ...secondDropdownField, value: "three" },
{ ...textField, value: "text" },
integerField,
];

const endUserConditions: EndUserCondition[] = [
{
parent_field_id: 123,
parent_field_type: "tagger",
value: "two",
child_fields: [
{
id: 456,
is_required: true,
},
],
},
{
parent_field_id: 456,
parent_field_type: "tagger",
value: "three",
child_fields: [
{
id: 789,
is_required: false,
},
],
},
{
parent_field_id: 789,
parent_field_type: "text",
value: "text",
child_fields: [
{
id: 101,
is_required: true,
},
],
},
{
parent_field_id: 123,
parent_field_type: "tagger",
value: "one",
child_fields: [
{
id: 789,
is_required: false,
},
{
id: 101,
is_required: true,
},
],
},
];

const result = getVisibleFields(fields, endUserConditions);

expect(result).toEqual([
{ ...dropdownField, value: "one" },
{ ...textField, value: "text" },
{ ...integerField, required: true },
]);
});
});
79 changes: 79 additions & 0 deletions src/modules/new-request-form/getVisibleFields.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type { EndUserCondition, Field } from "./data-types";

function getFieldConditions(
fieldId: number,
endUserConditions: EndUserCondition[]
): EndUserCondition[] {
return endUserConditions.filter((condition) => {
return condition.child_fields.some((child) => child.id === fieldId);
});
}

function getAppliedConditions(
fieldConditions: EndUserCondition[],
allConditions: EndUserCondition[],
fields: Field[]
): EndUserCondition[] {
return fieldConditions.filter((condition) => {
const parentField = fields.find(
(field) => field.id === condition.parent_field_id
);

if (!parentField) {
return false;
}

const parentFieldConditions = getFieldConditions(
parentField.id,
allConditions
);

// the condition is applied if the parent field value matches the condition value
// and if the parent field has no conditions or if the parent field conditions are met
return (
parentField.value === condition.value &&
(parentFieldConditions.length === 0 ||
getAppliedConditions(parentFieldConditions, allConditions, fields)
.length > 0)
);
});
}

export function getVisibleFields(
fields: Field[],
endUserConditions: EndUserCondition[]
): Field[] {
if (endUserConditions.length === 0) {
return fields;
}

return fields.reduce((acc: Field[], field) => {
const fieldConditions = getFieldConditions(field.id, endUserConditions);

if (fieldConditions.length === 0) {
return [...acc, field];
}

const appliedConditions = getAppliedConditions(
fieldConditions,
endUserConditions,
fields
);

if (appliedConditions.length > 0) {
return [
...acc,
{
...field,
required: appliedConditions.some((condition) =>
condition.child_fields.some(
(child) => child.id == field.id && child.is_required
)
),
},
];
}

return acc;
}, []);
}
Loading

0 comments on commit 75a91d9

Please sign in to comment.