Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add toggle to optionally format Calculate output for automations #2468

Merged
merged 5 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import React from "react";
import Wrapper from "../fixtures/Wrapper";
import { WarningContainer } from "../shared/Preview/WarningContainer";
import Editor from "./Editor";
import Public from "./Public/index";
import Public from "./Public";

export default {
title: "PlanX Components/Calculate",
Expand Down
12 changes: 12 additions & 0 deletions editor.planx.uk/src/@planx/components/Calculate/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import InputGroup from "ui/InputGroup";
import InputRow from "ui/InputRow";
import ModalSection from "ui/ModalSection";
import ModalSectionContent from "ui/ModalSectionContent";
import OptionButton from "ui/OptionButton";

import type { Calculate } from "./model";
import { evaluate, getVariables, parseCalculate } from "./model";
Expand Down Expand Up @@ -85,6 +86,17 @@ export default function Component(props: Props) {
onChange={formik.handleChange}
/>
</InputRow>
<OptionButton
selected={formik.values.formatOutputForAutomations}
onClick={() => {
formik.setFieldValue(
"formatOutputForAutomations",
!formik.values.formatOutputForAutomations,
);
}}
>
Format the output to automate a future Question or Checklist only
</OptionButton>
</ModalSectionContent>
<ModalSectionContent title="Formula">
<InputRow>
Expand Down
23 changes: 23 additions & 0 deletions editor.planx.uk/src/@planx/components/Calculate/Public.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from "react";
import { setup } from "testUtils";

import Calculate from "./Public";

describe("Calculate component", () => {
it("renders correctly", () => {
const handleSubmit = jest.fn();
setup(
<Calculate
output="testGroup"
formula="pickRandom([1,2])"
formatOutputForAutomations={true}
defaults={{}}
samples={{}}
handleSubmit={handleSubmit}
/>,
);

// Calculate should be auto-answered and never shown to user
expect(handleSubmit).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,27 @@ import type { PublicProps } from "@planx/components/ui";
import { useStore } from "pages/FlowEditor/lib/store";
import { useEffect } from "react";

import type { Calculate } from "../model";
import { evaluate } from "../model";
import type { Calculate } from "./model";
import { evaluate } from "./model";

export type Props = PublicProps<Calculate>;

export default function Component(props: Props) {
const passport = useStore((state) => state.computePassport().data);

useEffect(() => {
const evaluatedResult = evaluate(props.formula, passport, props.defaults);
props.handleSubmit?.({
...makeData(
props,
evaluate(props.formula, passport, props.defaults),
props.formatOutputForAutomations ? [evaluatedResult.toString()] : evaluatedResult,
props.output,
),
// don't show this component to the user, auto=true required
// for back button to skip past this component when going back
auto: true,
});
}, []);
}, [props, passport]);

return null;
}
185 changes: 185 additions & 0 deletions editor.planx.uk/src/@planx/components/Calculate/logic.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import { TYPES } from "@planx/components/types";
import { Store, vanillaStore } from "pages/FlowEditor/lib/store";

const { getState, setState } = vanillaStore;
const { upcomingCardIds, resetPreview, record, currentCard } = getState();

// Helper method
const visitedNodes = () => Object.keys(getState().breadcrumbs);

beforeEach(() => {
resetPreview();
});

test("When formatOutputForAutomations is true, Calculate writes an array and future questions are auto-answered", () => {
// Setup
setState({ flow: flowWithAutomation });
expect(upcomingCardIds()).toEqual([
"Calculate",
"Question",
]);

// Step forwards through the Calculate
record("Calculate", { data: { testGroup: ["2"] }, auto: true });
upcomingCardIds();

// The Question has been auto-answered
expect(visitedNodes()).toEqual([
"Calculate",
"Question",
]);

expect(upcomingCardIds()).toEqual([
"Group2Notice",
]);
});

test("When formatOutputForAutomations is false, Calculate writes a number and future questions are not auto-answered", () => {
// Setup
setState({ flow: flowWithoutAutomation });
expect(upcomingCardIds()).toEqual([
"Calculate",
"Question",
]);

// Step forwards through the Calculate
record("Calculate", { data: { testGroup: 2 }, auto: true });
upcomingCardIds();

// The Question has NOT been auto-answered
expect(visitedNodes()).toEqual([
"Calculate",
]);

expect(upcomingCardIds()).toEqual([
"Question"
]);
});

const flowWithAutomation: Store.flow = {
"_root": {
"edges": [
"Calculate",
"Question"
]
},
"Group2Notice": {
"data": {
"color": "#EFEFEF",
"title": "You are Group 2",
"resetButton": false
},
"type": TYPES.Notice
},
"Calculate": {
"data": {
"output": "testGroup",
"formula": "pickRandom([1,2])",
"formatOutputForAutomations": true
},
"type": TYPES.Calculate
},
"Group1Notice": {
"data": {
"color": "#EFEFEF",
"title": "You are Group 1",
"resetButton": false
},
"type": TYPES.Notice
},
"Group1Response": {
"data": {
"val": "1",
"text": "1"
},
"type": TYPES.Response,
"edges": [
"Group1Notice"
]
},
"Question": {
"data": {
"fn": "testGroup",
"text": "Which test group? "
},
"type": TYPES.Statement,
"edges": [
"Group1Response",
"Group2Response"
]
},
"Group2Response": {
"data": {
"val": "2",
"text": "2"
},
"type": TYPES.Response,
"edges": [
"Group2Notice"
]
}
};

const flowWithoutAutomation: Store.flow = {
"_root": {
"edges": [
"Calculate",
"Question"
]
},
"Group2Notice": {
"data": {
"color": "#EFEFEF",
"title": "You are Group 2",
"resetButton": false
},
"type": TYPES.Notice
},
"Calculate": {
"data": {
"output": "testGroup",
"formula": "pickRandom([1,2])",
"formatOutputForAutomations": false
},
"type": TYPES.Calculate
},
"Group1Notice": {
"data": {
"color": "#EFEFEF",
"title": "You are Group 1",
"resetButton": false
},
"type": TYPES.Notice
},
"Group1Response": {
"data": {
"val": "1",
"text": "1"
},
"type": TYPES.Response,
"edges": [
"Group1Notice"
]
},
"Question": {
"data": {
"fn": "testGroup",
"text": "Which test group? "
},
"type": TYPES.Statement,
"edges": [
"Group1Response",
"Group2Response"
]
},
"Group2Response": {
"data": {
"val": "2",
"text": "2"
},
"type": TYPES.Response,
"edges": [
"Group2Notice"
]
}
};
4 changes: 3 additions & 1 deletion editor.planx.uk/src/@planx/components/Calculate/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface Calculate extends MoreInformation {
defaults: Record<string, number>;
formula: string;
samples: Record<string, number>;
formatOutputForAutomations?: boolean;
}

export interface Input {
Expand All @@ -22,6 +23,7 @@ export const parseCalculate = (
defaults: data?.defaults || {},
formula: data?.formula || "",
samples: data?.samples || {},
formatOutputForAutomations: data?.formatOutputForAutomations || false,
});

export function getVariables(input: string): Set<string> {
Expand Down Expand Up @@ -82,7 +84,7 @@ export function evaluate(input: string, scope = {}, defaults = {}): number {
}
}

// Serialization is only necessary internally. v
// Serialization is only necessary internally.
// Mathjs can't handle keys with dots in their names e.g. `property.number`
// This complexity should never be exposed to this component's consumers.
function serialize(x: string) {
Expand Down
Loading