From f00d8a78ccccbdb8af441916495d7b383f0387ff Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Fri, 11 Oct 2024 00:15:30 +0200 Subject: [PATCH 01/20] feat: Initial button for additional number options --- src/pages/policy/input/ParameterEditor.jsx | 72 +++++++++++++--------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/src/pages/policy/input/ParameterEditor.jsx b/src/pages/policy/input/ParameterEditor.jsx index 2d221f3cb..c865077d1 100644 --- a/src/pages/policy/input/ParameterEditor.jsx +++ b/src/pages/policy/input/ParameterEditor.jsx @@ -25,7 +25,7 @@ import { IntervalMap } from "algorithms/IntervalMap"; import { cmpDates, nextDay, prevDay } from "lang/stringDates"; import moment from "dayjs"; import StableInputNumber from "controls/StableInputNumber"; -import { UndoOutlined } from "@ant-design/icons"; +import { RightOutlined, UndoOutlined } from "@ant-design/icons"; const { RangePicker } = DatePicker; /** @@ -393,35 +393,47 @@ function ValueSetter(props) { const isCurrency = Object.keys(currencyMap).includes(parameter.unit); const maximumFractionDigits = isCurrency ? 2 : 16; return ( - { - const n = +value; - const isInteger = Number.isInteger(n); - return n.toLocaleString(localeCode(metadata.countryId), { - minimumFractionDigits: userTyping || isInteger ? 0 : 2, - maximumFractionDigits: userTyping ? 16 : maximumFractionDigits, - }); - }} - defaultValue={Number(startValue) * scale} - onPressEnter={(_, value) => - changeHandler(+value.toFixed(maximumFractionDigits) / scale) - } - /> + + { + const n = +value; + const isInteger = Number.isInteger(n); + return n.toLocaleString(localeCode(metadata.countryId), { + minimumFractionDigits: userTyping || isInteger ? 0 : 2, + maximumFractionDigits: userTyping ? 16 : maximumFractionDigits, + }); + }} + defaultValue={Number(startValue) * scale} + onPressEnter={(_, value) => + changeHandler(+value.toFixed(maximumFractionDigits) / scale) + } + /> + + + + ); } } From 3e71a9e65a76767d802485e3a7b2a1323812b1af Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Fri, 11 Oct 2024 00:23:08 +0200 Subject: [PATCH 02/20] feat: Add click handler, refactor into new component --- src/pages/policy/input/ParameterEditor.jsx | 40 +++++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/src/pages/policy/input/ParameterEditor.jsx b/src/pages/policy/input/ParameterEditor.jsx index c865077d1..251b647dc 100644 --- a/src/pages/policy/input/ParameterEditor.jsx +++ b/src/pages/policy/input/ParameterEditor.jsx @@ -424,20 +424,42 @@ function ValueSetter(props) { changeHandler(+value.toFixed(maximumFractionDigits) / scale) } /> - - - + ); } } +function AdvancedValueSetter(props) { + + const [isExpanded, setIsExpanded] = useState(false); + + function handleExpand() { + setIsExpanded((prev) => !prev); + } + + return ( + <> + { + isExpanded ? ( +

Expanded Placeholder

+ ) : ( + + + + ) + } + + ) +} + /** * Checks whether or not an input date is a boundary date - * the first or last day of a fixed period (e.g., Jan. 1 or From de49b71a35a37bcc022ff0ecc0ba79832cc024b3 Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Fri, 11 Oct 2024 00:34:39 +0200 Subject: [PATCH 03/20] feat: Add infinity buttons --- src/pages/policy/input/ParameterEditor.jsx | 40 ++++++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/pages/policy/input/ParameterEditor.jsx b/src/pages/policy/input/ParameterEditor.jsx index 251b647dc..0a31289fb 100644 --- a/src/pages/policy/input/ParameterEditor.jsx +++ b/src/pages/policy/input/ParameterEditor.jsx @@ -25,7 +25,8 @@ import { IntervalMap } from "algorithms/IntervalMap"; import { cmpDates, nextDay, prevDay } from "lang/stringDates"; import moment from "dayjs"; import StableInputNumber from "controls/StableInputNumber"; -import { RightOutlined, UndoOutlined } from "@ant-design/icons"; +import { LeftOutlined, RightOutlined, UndoOutlined } from "@ant-design/icons"; +import style from "../../../style"; const { RangePicker } = DatePicker; /** @@ -442,9 +443,42 @@ function AdvancedValueSetter(props) { <> { isExpanded ? ( -

Expanded Placeholder

+ + + + + + + + + + + ) : ( - + @@ -462,6 +470,7 @@ function AdvancedValueSetter(props) { aspectRatio: 1, fontFamily: style.fonts.BODY_FONT }} + onClick={() => handleValueInput(-Infinity)} > -∞ From a6a051d9658160851a800831299fba149376c2ca Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Fri, 11 Oct 2024 21:10:13 +0200 Subject: [PATCH 05/20] fix: Update sanitizeStringToPython to handle infinite values --- src/data/reformDefinitionCode.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/data/reformDefinitionCode.js b/src/data/reformDefinitionCode.js index ff397dcb7..cdf44ad0b 100644 --- a/src/data/reformDefinitionCode.js +++ b/src/data/reformDefinitionCode.js @@ -262,8 +262,7 @@ export function doesParamNameContainNumber(paramName) { /** * Utility function to sanitize a string and ensure that it's valid Python; - * currently converts JS 'null', 'true', and 'false' to Python - * 'None', 'True', and 'False' + * currently converts JS 'null', 'true', 'false', '"Infinity"', and '"-Infinity"' to Python * @param {String} string * @returns {String} */ @@ -271,5 +270,7 @@ export function sanitizeStringToPython(string) { return string .replace(/true/g, "True") .replace(/false/g, "False") - .replace(/null/g, "None"); + .replace(/null/g, "None") + .replace(/"Infinity"/g, ".inf") + .replace(/"-Infinity"/g, "-.inf"); } From 280ed93ebe4b76fd5110d9358161265284cfd9c9 Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Fri, 11 Oct 2024 21:55:01 +0200 Subject: [PATCH 06/20] fix: Allow for controlled inputs in StableInputNumber, allow Infinity as input --- src/controls/StableInputNumber.jsx | 1 - src/pages/policy/input/ParameterEditor.jsx | 11 ++++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/controls/StableInputNumber.jsx b/src/controls/StableInputNumber.jsx index e3e6f87a3..2d84fdc86 100644 --- a/src/controls/StableInputNumber.jsx +++ b/src/controls/StableInputNumber.jsx @@ -37,7 +37,6 @@ import { useState } from "react"; export default function StableInputNumber(props) { const { defaultValue, onPressEnter, onBlur, ...others } = props; const [value, setValue] = useState(defaultValue); - delete others.onChange; return ( setValue(value)} onPressEnter={(_, value) => changeHandler(+value.toFixed(maximumFractionDigits) / scale) } /> - + ); } @@ -433,7 +436,8 @@ function ValueSetter(props) { function AdvancedValueSetter(props) { const { - changeHandler + changeHandler, + setValue } = props; const [isExpanded, setIsExpanded] = useState(false); @@ -443,6 +447,7 @@ function AdvancedValueSetter(props) { } function handleValueInput(value) { + setValue(value); changeHandler(value); } From 3f3832b44382e58a0d92882c8e2afe94fe85e867 Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Fri, 11 Oct 2024 23:20:36 +0200 Subject: [PATCH 07/20] fix: Add controlled input to StableInputVariable --- src/controls/StableInputNumber.jsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/controls/StableInputNumber.jsx b/src/controls/StableInputNumber.jsx index 2d84fdc86..4326ba7e7 100644 --- a/src/controls/StableInputNumber.jsx +++ b/src/controls/StableInputNumber.jsx @@ -35,12 +35,11 @@ import { useState } from "react"; * @returns a StableInputNumber component */ export default function StableInputNumber(props) { - const { defaultValue, onPressEnter, onBlur, ...others } = props; - const [value, setValue] = useState(defaultValue); + const { defaultValue, onPressEnter, onBlur, value, ...others } = props; return ( onPressEnter?.(e, value)} onBlur={(e) => onBlur?.(e, value)} {...others} From 394c0f5f90eb0cc8b3b24672941320275b4f5b4c Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Fri, 11 Oct 2024 23:24:17 +0200 Subject: [PATCH 08/20] fix: Add controlled inputs to VariableEditor --- src/pages/household/input/VariableEditor.jsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/pages/household/input/VariableEditor.jsx b/src/pages/household/input/VariableEditor.jsx index d90042ce5..488c93cbc 100644 --- a/src/pages/household/input/VariableEditor.jsx +++ b/src/pages/household/input/VariableEditor.jsx @@ -14,6 +14,7 @@ import gtag from "../../../api/analytics"; import { useEffect } from "react"; import StableInputNumber from "controls/StableInputNumber"; import { defaultYear } from "data/constants"; +import { useState } from "react"; export default function VariableEditor(props) { const [searchParams] = useSearchParams(); @@ -185,6 +186,7 @@ function HouseholdVariableEntityInput(props) { setEdited, singleEntity, } = props; + const submitValue = (value) => { value = Number.isNaN(+value) ? value : +value; let newHousehold = JSON.parse(JSON.stringify(householdInput)); @@ -246,6 +248,9 @@ function HouseholdVariableEntityInput(props) { defaultValue = variable.possibleValues[0]; } } + + const [value, setValue] = useState(defaultValue); + const mobile = useMobile(); let control; @@ -281,6 +286,8 @@ function HouseholdVariableEntityInput(props) { defaultValue={defaultValue} onPressEnter={onPressEnter} onBlur={onPressEnter} + value={value} + onChange={(value) => setValue(value)} /> ); } else if (variable.valueType === "bool") { From 15f7defa459aa6ded7f80e65bc68f2101990f5f1 Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Wed, 16 Oct 2024 22:43:24 +0200 Subject: [PATCH 09/20] fix: Move away from StableInputNumber --- src/pages/policy/input/ParameterEditor.jsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/pages/policy/input/ParameterEditor.jsx b/src/pages/policy/input/ParameterEditor.jsx index 301695ad9..7389c8878 100644 --- a/src/pages/policy/input/ParameterEditor.jsx +++ b/src/pages/policy/input/ParameterEditor.jsx @@ -4,6 +4,7 @@ import { Alert, Button, DatePicker, + InputNumber, Popover, Segmented, Space, @@ -347,6 +348,10 @@ function ValueSetter(props) { const [value, setValue] = useState(startValue); const parameter = metadata.parameters[parameterName]; + useEffect(() => { + console.log("Value changed to", value); + }, [value]) + function changeHandler(value) { reformMap.set(startDate, nextDay(endDate), value); let data = {}; @@ -394,9 +399,11 @@ function ValueSetter(props) { const scale = isPercent ? 100 : 1; const isCurrency = Object.keys(currencyMap).includes(parameter.unit); const maximumFractionDigits = isCurrency ? 2 : 16; + + return ( - setValue(value)} - onPressEnter={(_, value) => - changeHandler(+value.toFixed(maximumFractionDigits) / scale) + onPressEnter={() => { + console.log(value); + changeHandler(+value.toFixed(maximumFractionDigits) / scale); + } } /> From 6e60b103a052e6b9bf43874a8e44841e77142453 Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Wed, 16 Oct 2024 23:23:44 +0200 Subject: [PATCH 10/20] feat: Create wrapper for Response.json() and implement --- metadata_fetch.mjs | 8 ++------ src/PolicyEngine.jsx | 7 ++++--- src/api/call.js | 4 ++-- src/api/parameters.js | 3 ++- src/api/userPolicies.js | 5 +++-- src/api/variables.js | 3 ++- src/data/wrappedJson.js | 14 ++++++++++++++ src/pages/APIDocumentationPage.jsx | 3 ++- src/pages/HouseholdPage.jsx | 7 ++++--- src/pages/StatusPage.jsx | 3 ++- src/pages/UserProfilePage.jsx | 3 ++- src/pages/household/output/EarningsVariation.jsx | 5 +++-- src/pages/household/output/MarginalTaxRates.jsx | 5 +++-- src/pages/policy/PolicySearch.jsx | 5 +++-- src/pages/policy/output/FetchAndDisplayImpact.jsx | 3 ++- 15 files changed, 50 insertions(+), 28 deletions(-) diff --git a/metadata_fetch.mjs b/metadata_fetch.mjs index 4c426c3e4..e1d677712 100644 --- a/metadata_fetch.mjs +++ b/metadata_fetch.mjs @@ -1,11 +1,7 @@ import fetch from "node-fetch"; import fs from "fs"; import path from "path"; -import { wrappedJsonStringify } from "./src/data/wrappedJson"; - -// const fetch = require('node-fetch') -// const fs = require('fs'); -// const path = require('path'); +import { wrappedJsonStringify, wrappedResponseJson } from "./src/data/wrappedJson"; let metadataUS = null; let metadataUK = null; @@ -21,7 +17,7 @@ const filePath = path.join( async function fetchMetadata(countryId) { const res = await fetch(`https://api.policyengine.org/${countryId}/metadata`); - const metadataRaw = await res.json(); + const metadataRaw = await wrappedResponseJson(res); const metadata = metadataRaw.result; return metadata; } diff --git a/src/PolicyEngine.jsx b/src/PolicyEngine.jsx index 5db74bf1a..8ed2c72f2 100644 --- a/src/PolicyEngine.jsx +++ b/src/PolicyEngine.jsx @@ -45,6 +45,7 @@ import RedirectBlogPost from "./routing/RedirectBlogPost"; import { StatusPage } from "./pages/StatusPage"; import ManifestosComparison from "./applets/ManifestosComparison"; import CTCComparison from "./applets/CTCComparison"; +import { wrappedResponseJson } from "./data/wrappedJson"; const PolicyPage = lazy(() => import("./pages/PolicyPage")); const HouseholdPage = lazy(() => import("./pages/HouseholdPage")); @@ -133,7 +134,7 @@ export default function PolicyEngine() { useEffect(() => { if (metadata) { countryApiCall(countryId, `/policy/${baselinePolicyId}`) - .then((res) => res.json()) + .then((res) => wrappedResponseJson(res)) .then((dataHolder) => { if (dataHolder.result.label === "None") { dataHolder.result.label = null; @@ -151,7 +152,7 @@ export default function PolicyEngine() { useEffect(() => { if (metadata) { countryApiCall(countryId, `/policy/${reformPolicyId}`) - .then((res) => res.json()) + .then((res) => wrappedResponseJson(res)) .then((dataHolder) => { if (dataHolder.result.label === "None") { dataHolder.result.label = null; @@ -168,7 +169,7 @@ export default function PolicyEngine() { useEffect(() => { if (searchParams.get("renamed") && reformPolicyId) { countryApiCall(countryId, `/policy/${reformPolicyId}`) - .then((res) => res.json()) + .then((res) => wrappedResponseJson(res)) .then((dataHolder) => { setReformPolicy({ data: dataHolder.result.policy_json, diff --git a/src/api/call.js b/src/api/call.js index 7cc26eede..1fbffcb31 100644 --- a/src/api/call.js +++ b/src/api/call.js @@ -1,6 +1,6 @@ import { buildParameterTree } from "./parameters"; import { buildVariableTree, getTreeLeavesInOrder } from "./variables"; -import { wrappedJsonStringify } from "../data/wrappedJson"; +import { wrappedJsonStringify, wrappedResponseJson } from "../data/wrappedJson"; const POLICYENGINE_API = "https://api.policyengine.org"; @@ -88,7 +88,7 @@ export async function updateMetadata(countryId) { return null; } - const dataHolder = await res.json(); + const dataHolder = await wrappedResponseJson(res); let data = dataHolder.result; const variableTree = buildVariableTree( diff --git a/src/api/parameters.js b/src/api/parameters.js index c570a8216..47728da8d 100644 --- a/src/api/parameters.js +++ b/src/api/parameters.js @@ -1,6 +1,7 @@ import { IntervalMap } from "algorithms/IntervalMap"; import { countryApiCall } from "./call"; import { cmpDates } from "lang/stringDates"; +import { wrappedResponseJson } from "../data/wrappedJson"; export function buildParameterTree(parameters) { let tree = {}; @@ -86,7 +87,7 @@ export function getNewPolicyId(countryId, newPolicyData, newPolicyLabel) { submission.label = newPolicyLabel; } return countryApiCall(countryId, "/policy", submission, "POST") - .then((response) => response.json()) + .then((response) => wrappedResponseJson(response)) .then((data) => { let result = {}; if (data.status === "ok") { diff --git a/src/api/userPolicies.js b/src/api/userPolicies.js index 1d8f0343b..108152cac 100644 --- a/src/api/userPolicies.js +++ b/src/api/userPolicies.js @@ -1,3 +1,4 @@ +import { wrappedResponseJson } from "../data/wrappedJson"; import { apiCall } from "./call"; const USER_POLICY_ENDPOINT = "/user_policy"; @@ -17,7 +18,7 @@ export async function postUserPolicy(countryId, policyToAdd) { policyToAdd, "POST", ); - const resJson = await res.json(); + const resJson = await wrappedResponseJson(res); // If the record already exists... if (res.status === 200 && resJson.status === "ok") { // Update the API version and updated_date fields @@ -49,7 +50,7 @@ export async function updateUserPolicy(countryId, policyToAdd) { policyToAdd, "PUT", ); - const resJson = await res.json(); + const resJson = await wrappedResponseJson(res); if (resJson.status !== "ok") { console.error("Error while POSTing user policy:"); console.error(resJson.message); diff --git a/src/api/variables.js b/src/api/variables.js index 677bc975e..ed9a4ece1 100644 --- a/src/api/variables.js +++ b/src/api/variables.js @@ -2,6 +2,7 @@ import { countryApiCall } from "./call"; import { capitalize } from "../lang/format"; import { defaultHouseholds } from "../data/defaultHouseholds"; import { defaultYear } from "data/constants"; +import { wrappedResponseJson } from "../data/wrappedJson"; export function removePerson(situation, name) { // Remove a person from the situation @@ -401,7 +402,7 @@ export function getDefaultHouseholdId(metadata) { { data: defaultHousehold }, "POST", ) - .then((res) => res.json()) + .then((res) => wrappedResponseJson(res)) .then((dataHolder) => { return dataHolder.result.household_id; }); diff --git a/src/data/wrappedJson.js b/src/data/wrappedJson.js index 9ad8b503d..b16151d5c 100644 --- a/src/data/wrappedJson.js +++ b/src/data/wrappedJson.js @@ -38,4 +38,18 @@ export function wrappedJsonParse() { export function wrappedJsonStringify() { return JSON.stringify(...arguments, JsonReplacer); +} + +/** + * Replaces Response.json(), which unfortunately has no + * native way of passing a reviver + * @param {Response} response The response object + * @returns {Promise} The JSON object, parsed with custom reviver + */ +export function wrappedResponseJson(response) { + return new Promise((resolve, reject) => { + response.text().then((text) => { + resolve(wrappedJsonParse(text)); + }); + }); } \ No newline at end of file diff --git a/src/pages/APIDocumentationPage.jsx b/src/pages/APIDocumentationPage.jsx index 84ac829ca..354ac64bf 100644 --- a/src/pages/APIDocumentationPage.jsx +++ b/src/pages/APIDocumentationPage.jsx @@ -11,6 +11,7 @@ import { Input, Card, Divider, Tag, Drawer } from "antd"; import { Helmet } from "react-helmet"; import { defaultYear } from "data/constants"; import useDisplayCategory from "../hooks/useDisplayCategory"; +import { wrappedResponseJson } from "../data/wrappedJson"; export const exampleInputs = { us: { @@ -445,7 +446,7 @@ function APIEndpoint({ body: JSON.stringify(exampleInputJson), }, ); - const resJson = await res.json(); + const resJson = await wrappedResponseJson(res); setOutputJson(resJson); } catch (err) { console.error(err); diff --git a/src/pages/HouseholdPage.jsx b/src/pages/HouseholdPage.jsx index c9cafdd64..7cfc6f4bb 100644 --- a/src/pages/HouseholdPage.jsx +++ b/src/pages/HouseholdPage.jsx @@ -27,6 +27,7 @@ import MobileCalculatorPage from "../layout/MobileCalculatorPage.jsx"; import RecreateHouseholdPopup from "./household/output/RecreateHouseholdPopup.jsx"; import TaxYear from "./household/input/TaxYear"; import { Helmet } from "react-helmet"; +import { wrappedResponseJson } from "../data/wrappedJson.js"; export default function HouseholdPage(props) { const { @@ -100,7 +101,7 @@ export default function HouseholdPage(props) { console.error("Back-end error while attempting to get household"); } - const resJSON = await res.json(); + const resJSON = await wrappedResponseJson(res); dataHolder = { input: resJSON.result.household_json, }; @@ -158,7 +159,7 @@ export default function HouseholdPage(props) { (metadata ? metadata.current_law_id : "current-law") }`, ) - .then((res) => res.json()) + .then((res) => wrappedResponseJson(res)) .then((dataHolder) => { if (dataHolder.status === "error") { setLoading(false); @@ -178,7 +179,7 @@ export default function HouseholdPage(props) { countryId, `/household/${householdId}/policy/${policy.reform.id}`, ) - .then((res) => res.json()) + .then((res) => wrappedResponseJson(res)) .then((dataHolder) => { if (dataHolder.status === "error") { setLoading(false); diff --git a/src/pages/StatusPage.jsx b/src/pages/StatusPage.jsx index 5b8d84189..9073c8d34 100644 --- a/src/pages/StatusPage.jsx +++ b/src/pages/StatusPage.jsx @@ -13,6 +13,7 @@ import { COUNTRY_NAMES, } from "../data/countries"; import { Helmet } from "react-helmet"; +import { wrappedResponseJson } from "../data/wrappedJson"; function ApiStatus({ apiStatus, apiCategory, countryNames }) { return ( @@ -77,7 +78,7 @@ export function StatusPage() { Object.keys(body).length > 0 ? api(path, body) : api(country, path); calledApi - .then((res) => res.json()) + .then((res) => wrappedResponseJson(res)) .then((res) => { const endTime = Date.now(); const latency = endTime - startTime; diff --git a/src/pages/UserProfilePage.jsx b/src/pages/UserProfilePage.jsx index aad174706..497fc0861 100644 --- a/src/pages/UserProfilePage.jsx +++ b/src/pages/UserProfilePage.jsx @@ -26,6 +26,7 @@ import { COUNTRY_NAMES } from "../data/countries"; import moment from "moment"; import { formatCurrencyAbbr } from "../lang/format"; import ErrorPage from "../layout/ErrorPage"; +import { wrappedResponseJson } from "../data/wrappedJson"; const STATES = { EMPTY: "empty", @@ -562,7 +563,7 @@ function UsernameDisplayAndEditor(props) { try { const res = await apiCall(USER_PROFILE_PATH, body, "PUT"); - const resJson = await res.json(); + const resJson = await wrappedResponseJson(res); if (resJson.status === "ok") { const data = await apiCall( `/${countryId}/user_profile?user_id=${accessedUserProfile.user_id}`, diff --git a/src/pages/household/output/EarningsVariation.jsx b/src/pages/household/output/EarningsVariation.jsx index a680b2351..cfcacb9b9 100644 --- a/src/pages/household/output/EarningsVariation.jsx +++ b/src/pages/household/output/EarningsVariation.jsx @@ -10,6 +10,7 @@ import BaselineOnlyChart from "./EarningsVariation/BaselineOnlyChart"; import BaselineAndReformChart from "./EarningsVariation/BaselineAndReformChart"; import { getValueFromHousehold } from "../../../api/variables"; import { Helmet } from "react-helmet"; +import { wrappedResponseJson } from "../../../data/wrappedJson"; export default function EarningsVariation(props) { const { @@ -96,7 +97,7 @@ export default function EarningsVariation(props) { household: householdData, policy: policy.baseline.data, }) - .then((res) => res.json()) + .then((res) => wrappedResponseJson(res)) .then((data) => { setBaselineNetIncome(data.result); }) @@ -110,7 +111,7 @@ export default function EarningsVariation(props) { household: householdData, policy: policy.reform.data, }) - .then((res) => res.json()) + .then((res) => wrappedResponseJson(res)) .then((data) => { setReformNetIncome(data.result); }) diff --git a/src/pages/household/output/MarginalTaxRates.jsx b/src/pages/household/output/MarginalTaxRates.jsx index ee010e1b4..6cd7e6471 100644 --- a/src/pages/household/output/MarginalTaxRates.jsx +++ b/src/pages/household/output/MarginalTaxRates.jsx @@ -19,6 +19,7 @@ import useMobile from "layout/Responsive"; import Screenshottable from "layout/Screenshottable"; import { localeCode } from "lang/format"; import { Helmet } from "react-helmet"; +import { wrappedResponseJson } from "../../../data/wrappedJson"; export default function MarginalTaxRates(props) { const { @@ -82,7 +83,7 @@ export default function MarginalTaxRates(props) { household: householdData, policy: policy.baseline.data, }) - .then((res) => res.json()) + .then((res) => wrappedResponseJson(res)) .then((data) => { setBaselineMtr(data.result); }) @@ -96,7 +97,7 @@ export default function MarginalTaxRates(props) { household: householdData, policy: policy.reform.data, }) - .then((res) => res.json()) + .then((res) => wrappedResponseJson(res)) .then((data) => { setReformMtr(data.result); }) diff --git a/src/pages/policy/PolicySearch.jsx b/src/pages/policy/PolicySearch.jsx index 585087dad..f6f655296 100644 --- a/src/pages/policy/PolicySearch.jsx +++ b/src/pages/policy/PolicySearch.jsx @@ -10,6 +10,7 @@ import { } from "@ant-design/icons"; import { getNewPolicyId } from "../../api/parameters"; import style from "../../style"; +import { wrappedResponseJson } from "../../data/wrappedJson"; export default function PolicySearch(props) { const { metadata, target, policy, width, displayStack } = props; @@ -66,7 +67,7 @@ export default function PolicySearch(props) { setIsError(true); setIsStackerLoading(false); } else { - const resJson = await res.json(); + const resJson = await wrappedResponseJson(res); const policyToStack = resJson.result; // Reconcile policies; when conflicts occur, defer to newer policy @@ -107,7 +108,7 @@ export default function PolicySearch(props) { metadata.countryId, `/policies?query=${searchText}&unique_only=true`, ); - const resJson = await res.json(); + const resJson = await wrappedResponseJson(res); setPolicies( resJson.result.map((item) => { return { diff --git a/src/pages/policy/output/FetchAndDisplayImpact.jsx b/src/pages/policy/output/FetchAndDisplayImpact.jsx index df266a1e5..e7a2c3143 100644 --- a/src/pages/policy/output/FetchAndDisplayImpact.jsx +++ b/src/pages/policy/output/FetchAndDisplayImpact.jsx @@ -12,6 +12,7 @@ import { defaultYear } from "data/constants"; import { areObjectsSame } from "../../../data/areObjectsSame"; import { updateUserPolicy } from "../../../api/userPolicies"; import useCountryId from "../../../hooks/useCountryId"; +import { wrappedResponseJson } from "../../../data/wrappedJson"; // import LoadingCentered from "layout/LoadingCentered"; /** @@ -92,7 +93,7 @@ export function FetchAndDisplayImpact(props) { setSecondsElapsed((secondsElapsed) => secondsElapsed + 1); }, 1000); apiCall(url, null) - .then((res) => res.json()) + .then((res) => wrappedResponseJson(res)) .then((intermediateData) => { if (averageImpactTime === 20) { setAverageImpactTime(intermediateData.average_time || 20); From ecd89005c778c0aab13caab7386e7b212e67c8d9 Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Thu, 17 Oct 2024 00:08:12 +0200 Subject: [PATCH 11/20] fix: Corrections to VariableEditor component --- src/pages/policy/input/ParameterEditor.jsx | 24 +++++++++++++--------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/pages/policy/input/ParameterEditor.jsx b/src/pages/policy/input/ParameterEditor.jsx index 7389c8878..563ae6142 100644 --- a/src/pages/policy/input/ParameterEditor.jsx +++ b/src/pages/policy/input/ParameterEditor.jsx @@ -348,10 +348,6 @@ function ValueSetter(props) { const [value, setValue] = useState(startValue); const parameter = metadata.parameters[parameterName]; - useEffect(() => { - console.log("Value changed to", value); - }, [value]) - function changeHandler(value) { reformMap.set(startDate, nextDay(endDate), value); let data = {}; @@ -384,6 +380,19 @@ function ValueSetter(props) { } } + const isPercent = parameter.unit === "/1"; + const scale = isPercent ? 100 : 1; + const isCurrency = Object.keys(currencyMap).includes(parameter.unit); + const maximumFractionDigits = isCurrency ? 2 : 16; + + // This is necessary because technically, ValueSetter does not + // unmount when we change between parameters, leading to the possibility + // for a stale "value" state in this controlled component + useEffect(() => { + setValue(Number(startValue) * scale); + + }, [parameterName, startValue, scale]) + if (parameter.unit === "bool" || parameter.unit === "abolition") { return (
@@ -395,11 +404,6 @@ function ValueSetter(props) {
); } else { - const isPercent = parameter.unit === "/1"; - const scale = isPercent ? 100 : 1; - const isCurrency = Object.keys(currencyMap).includes(parameter.unit); - const maximumFractionDigits = isCurrency ? 2 : 16; - return ( @@ -429,9 +433,9 @@ function ValueSetter(props) { }); }} defaultValue={Number(startValue) * scale} + value={value} onChange={(value) => setValue(value)} onPressEnter={() => { - console.log(value); changeHandler(+value.toFixed(maximumFractionDigits) / scale); } } From a45ecd2d87a818683e2032bf6be02c9960be7754 Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Thu, 17 Oct 2024 00:21:43 +0200 Subject: [PATCH 12/20] fix: Correct visual bug when expanding input options --- src/pages/policy/input/ParameterEditor.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/policy/input/ParameterEditor.jsx b/src/pages/policy/input/ParameterEditor.jsx index 563ae6142..f4d27fb06 100644 --- a/src/pages/policy/input/ParameterEditor.jsx +++ b/src/pages/policy/input/ParameterEditor.jsx @@ -440,7 +440,9 @@ function ValueSetter(props) { } } /> - + {!isPercent && ( + + )} ); } @@ -467,7 +469,7 @@ function AdvancedValueSetter(props) { <> { isExpanded ? ( - + - - - - - - - - - ) : ( - - + + + + + + - ) - } + + ) : ( + + + + )} - ) + ); } /** From e9ac4e032636b392eac9916024d07e230083f11e Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Thu, 17 Oct 2024 02:21:41 +0200 Subject: [PATCH 15/20] test: Remove new JSON handlers due to Jest limitations --- metadata_fetch.mjs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/metadata_fetch.mjs b/metadata_fetch.mjs index 9acd013cf..185da052a 100644 --- a/metadata_fetch.mjs +++ b/metadata_fetch.mjs @@ -1,10 +1,6 @@ import fetch from "node-fetch"; import fs from "fs"; import path from "path"; -import { - wrappedJsonStringify, - wrappedResponseJson, -} from "./src/data/wrappedJson"; let metadataUS = null; let metadataUK = null; @@ -20,7 +16,9 @@ const filePath = path.join( async function fetchMetadata(countryId) { const res = await fetch(`https://api.policyengine.org/${countryId}/metadata`); - const metadataRaw = await wrappedResponseJson(res); + // For the time being, this is being kept as res.json(), unlike + // the rest of the repo, due to Jest's challenges with ES6 modules + const metadataRaw = await res.json(); const metadata = metadataRaw.result; return metadata; } @@ -34,7 +32,9 @@ let jsonData = { metadataUK: metadataUK, metadataCA: metadataCA, }; -jsonData = wrappedJsonStringify(jsonData); +// For the time being, this is being kept as res.json(), unlike +// the rest of the repo, due to Jest's challenges with ES6 modules +jsonData = JSON.stringify(jsonData); fs.writeFile(filePath, jsonData, (err) => { if (err) throw err; From 758527aabebff9812558d55c7b26fb877a47dfc9 Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Thu, 17 Oct 2024 21:37:49 +0200 Subject: [PATCH 16/20] feat: Add light teal highlight for open/close button --- src/pages/policy/input/ParameterEditor.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/policy/input/ParameterEditor.jsx b/src/pages/policy/input/ParameterEditor.jsx index b37e18db8..315b88aa0 100644 --- a/src/pages/policy/input/ParameterEditor.jsx +++ b/src/pages/policy/input/ParameterEditor.jsx @@ -491,6 +491,7 @@ function AdvancedValueSetter(props) {