From e83d0f5fa747bae673425da572e992ab687192ea Mon Sep 17 00:00:00 2001 From: Terry Tsai Date: Thu, 6 Oct 2022 12:50:25 -0700 Subject: [PATCH] move BarChart, Tooltip, and Legend as well as global styles and shared types into common directory --- agency-dashboard/package.json | 2 +- .../components/DataViz/BarChart.tsx | 2 +- .../components/DataViz/Legend.tsx | 0 .../components/DataViz/Tooltip.tsx | 2 +- common/components/DataViz/utils.test.ts | 2066 +++++++++++++++++ common/components/DataViz/utils.ts | 340 +++ .../components/GlobalStyles/Palette.ts | 0 .../components/GlobalStyles/Typography.ts | 0 .../components/GlobalStyles/constants.ts | 0 .../components/GlobalStyles/index.ts | 0 {publisher/src/shared => common}/types.ts | 0 common/utils/conversionUtils.ts | 29 + common/utils/dateUtils.test.ts | 134 ++ common/utils/dateUtils.ts | 133 ++ common/utils/helperUtils.test.ts | 127 + common/utils/helperUtils.ts | 225 ++ common/utils/index copy.ts | 20 + common/utils/index.ts | 20 + publisher/package.json | 2 +- publisher/src/analytics.ts | 2 +- .../src/components/Auth/VerificationPage.tsx | 5 +- publisher/src/components/Badge/Badge.tsx | 2 +- .../DataUpload/DataUpload.styles.tsx | 6 +- .../src/components/DataUpload/DataUpload.tsx | 2 +- .../components/DataUpload/SystemSelection.tsx | 2 +- .../src/components/DataUpload/UploadFile.tsx | 2 +- .../components/DataUpload/UploadedFiles.tsx | 2 +- publisher/src/components/DataUpload/types.ts | 2 +- .../DataViz/DatapointsView.styles.tsx | 5 +- .../src/components/DataViz/DatapointsView.tsx | 12 +- .../src/components/DataViz/utils.test.ts | 3 +- publisher/src/components/DataViz/utils.ts | 6 +- .../components/Forms/BinaryRadioButton.tsx | 6 +- publisher/src/components/Forms/Dropdown.tsx | 6 +- .../src/components/Forms/Form.styles.tsx | 6 +- .../src/components/Forms/NotReportedIcon.tsx | 5 +- .../Forms/TabbedDisaggregations.tsx | 2 +- publisher/src/components/Forms/TextInput.tsx | 7 +- .../src/components/Header/Header.styles.tsx | 7 +- publisher/src/components/Menu/Menu.styles.tsx | 6 +- publisher/src/components/Menu/Menu.tsx | 2 +- .../MetricsView/MetricsView.styles.tsx | 5 +- .../components/MetricsView/MetricsView.tsx | 6 +- publisher/src/components/Modal/Modal.tsx | 6 +- .../src/components/Onboarding/Onboarding.tsx | 5 +- .../Onboarding/OnboardingDataEntrySummary.tsx | 5 +- .../src/components/Reports/CreateReport.tsx | 10 +- .../src/components/Reports/DataEntryForm.tsx | 6 +- .../Reports/DataEntryFormComponents.tsx | 8 +- .../src/components/Reports/HelperText.tsx | 5 +- .../Reports/PublishConfirmation.tsx | 14 +- .../Reports/ReportDataEntry.styles.tsx | 6 +- .../components/Reports/ReportDataEntry.tsx | 2 +- .../components/Reports/ReportSummaryPanel.tsx | 7 +- .../src/components/Reports/Reports.styles.tsx | 7 +- .../ReviewMetrics/ReviewMetrics.styles.tsx | 6 +- .../ReviewMetrics/ReviewMetrics.tsx | 5 +- publisher/src/components/Toast/Toast.ts | 7 +- publisher/src/index.tsx | 2 +- publisher/src/mocks/PreviewDataObject.tsx | 3 +- publisher/src/pages/AccountSettings.tsx | 2 +- publisher/src/pages/Reports.tsx | 2 +- publisher/src/stores/DatapointsStore.ts | 12 +- publisher/src/stores/FormStore.ts | 6 +- publisher/src/stores/ReportStore.test.tsx | 2 +- publisher/src/stores/ReportStore.ts | 14 +- publisher/src/stores/UserStore.ts | 2 +- publisher/src/utils/dateUtils.ts | 2 +- publisher/src/utils/helperUtils.ts | 3 +- 69 files changed, 3265 insertions(+), 95 deletions(-) rename {publisher/src => common}/components/DataViz/BarChart.tsx (99%) rename {publisher/src => common}/components/DataViz/Legend.tsx (100%) rename {publisher/src => common}/components/DataViz/Tooltip.tsx (98%) create mode 100644 common/components/DataViz/utils.test.ts create mode 100644 common/components/DataViz/utils.ts rename {publisher/src => common}/components/GlobalStyles/Palette.ts (100%) rename {publisher/src => common}/components/GlobalStyles/Typography.ts (100%) rename {publisher/src => common}/components/GlobalStyles/constants.ts (100%) rename {publisher/src => common}/components/GlobalStyles/index.ts (100%) rename {publisher/src/shared => common}/types.ts (100%) create mode 100644 common/utils/conversionUtils.ts create mode 100644 common/utils/dateUtils.test.ts create mode 100644 common/utils/dateUtils.ts create mode 100644 common/utils/helperUtils.test.ts create mode 100644 common/utils/helperUtils.ts create mode 100644 common/utils/index copy.ts create mode 100644 common/utils/index.ts diff --git a/agency-dashboard/package.json b/agency-dashboard/package.json index 571874aeb..e86c41c5a 100644 --- a/agency-dashboard/package.json +++ b/agency-dashboard/package.json @@ -18,7 +18,7 @@ "web-vitals": "^2.1.4" }, "scripts": { - "start": "react-app-rewired start", + "dev": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-scripts eject", diff --git a/publisher/src/components/DataViz/BarChart.tsx b/common/components/DataViz/BarChart.tsx similarity index 99% rename from publisher/src/components/DataViz/BarChart.tsx rename to common/components/DataViz/BarChart.tsx index 23bbcca3a..f4de31689 100644 --- a/publisher/src/components/DataViz/BarChart.tsx +++ b/common/components/DataViz/BarChart.tsx @@ -27,7 +27,7 @@ import { } from "recharts"; import styled from "styled-components/macro"; -import { Datapoint } from "../../shared/types"; +import { Datapoint } from "../../types"; import { rem } from "../../utils"; import { palette } from "../GlobalStyles"; import Tooltip from "./Tooltip"; diff --git a/publisher/src/components/DataViz/Legend.tsx b/common/components/DataViz/Legend.tsx similarity index 100% rename from publisher/src/components/DataViz/Legend.tsx rename to common/components/DataViz/Legend.tsx diff --git a/publisher/src/components/DataViz/Tooltip.tsx b/common/components/DataViz/Tooltip.tsx similarity index 98% rename from publisher/src/components/DataViz/Tooltip.tsx rename to common/components/DataViz/Tooltip.tsx index 08a8d4b31..dd68ed829 100644 --- a/publisher/src/components/DataViz/Tooltip.tsx +++ b/common/components/DataViz/Tooltip.tsx @@ -19,7 +19,7 @@ import React from "react"; import { TooltipProps as RechartsTooltipProps } from "recharts"; import styled from "styled-components/macro"; -import { Datapoint } from "../../shared/types"; +import { Datapoint } from "../../types"; import { formatNumberInput } from "../../utils"; import { palette, typography } from "../GlobalStyles"; import { LegendColor } from "./Legend"; diff --git a/common/components/DataViz/utils.test.ts b/common/components/DataViz/utils.test.ts new file mode 100644 index 000000000..cabc85cdb --- /dev/null +++ b/common/components/DataViz/utils.test.ts @@ -0,0 +1,2066 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { Datapoint } from "../../types"; +import { + fillTimeGapsBetweenDatapoints, + filterByTimeRange, + filterNullDatapoints, + incrementMonth, + incrementYear, + transformData, + transformToRelativePerchanges, +} from "./utils"; + +const testDatapoints: Datapoint[] = [ + { + start_date: "Wed, 01 Jan 2020 00:00:00 GMT", + end_date: "Sat, 01 Feb 2020 00:00:00 GMT", + Pretrial: 50515, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 45427, + "Transfer or Hold": 31375, + Unknown: 29749, + }, + { + start_date: "Sat, 01 Feb 2020 00:00:00 GMT", + end_date: "Sun, 01 Mar 2020 00:00:00 GMT", + Pretrial: 54758, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 65594, + "Transfer or Hold": 89614, + Unknown: 73426, + }, + { + start_date: "Sun, 01 Mar 2020 00:00:00 GMT", + end_date: "Wed, 01 Apr 2020 00:00:00 GMT", + Pretrial: 52304, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 94209, + "Transfer or Hold": 82736, + Unknown: 62748, + }, + { + start_date: "Wed, 01 Apr 2020 00:00:00 GMT", + end_date: "Fri, 01 May 2020 00:00:00 GMT", + Pretrial: 23335, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 90737, + "Transfer or Hold": 57573, + Unknown: 93184, + }, + { + start_date: "Fri, 01 May 2020 00:00:00 GMT", + end_date: "Mon, 01 Jun 2020 00:00:00 GMT", + Pretrial: 39489, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 27098, + "Transfer or Hold": 41196, + Unknown: 28077, + }, + { + start_date: "Mon, 01 Jun 2020 00:00:00 GMT", + end_date: "Wed, 01 Jul 2020 00:00:00 GMT", + Pretrial: 66362, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 44360, + "Transfer or Hold": 61195, + Unknown: 31909, + }, + { + start_date: "Wed, 01 Jul 2020 00:00:00 GMT", + end_date: "Sat, 01 Aug 2020 00:00:00 GMT", + Pretrial: 69380, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 53465, + "Transfer or Hold": 94375, + Unknown: 13442, + }, + { + start_date: "Sat, 01 Aug 2020 00:00:00 GMT", + end_date: "Tue, 01 Sep 2020 00:00:00 GMT", + Pretrial: 30380, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 82698, + "Transfer or Hold": 41340, + Unknown: 40324, + }, + { + start_date: "Tue, 01 Sep 2020 00:00:00 GMT", + end_date: "Thu, 01 Oct 2020 00:00:00 GMT", + Pretrial: 30338, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 74647, + "Transfer or Hold": 85087, + Unknown: 28198, + }, + { + start_date: "Thu, 01 Oct 2020 00:00:00 GMT", + end_date: "Sun, 01 Nov 2020 00:00:00 GMT", + Pretrial: 35571, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 23313, + "Transfer or Hold": 41785, + Unknown: 63556, + }, + { + start_date: "Sun, 01 Nov 2020 00:00:00 GMT", + end_date: "Tue, 01 Dec 2020 00:00:00 GMT", + Pretrial: 65779, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 53664, + "Transfer or Hold": 65962, + Unknown: 59783, + }, + { + start_date: "Tue, 01 Dec 2020 00:00:00 GMT", + end_date: "Fri, 01 Jan 2021 00:00:00 GMT", + Pretrial: 46192, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 49529, + "Transfer or Hold": 78956, + Unknown: 12664, + }, + { + start_date: "Fri, 01 Jan 2021 00:00:00 GMT", + end_date: "Mon, 01 Feb 2021 00:00:00 GMT", + Pretrial: 34311, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 24082, + "Transfer or Hold": 70165, + Unknown: 13003, + }, + { + start_date: "Mon, 01 Feb 2021 00:00:00 GMT", + end_date: "Mon, 01 Mar 2021 00:00:00 GMT", + Pretrial: 94307, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 67799, + "Transfer or Hold": 80348, + Unknown: 24894, + }, + { + start_date: "Mon, 01 Mar 2021 00:00:00 GMT", + end_date: "Thu, 01 Apr 2021 00:00:00 GMT", + Pretrial: 55116, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 65802, + "Transfer or Hold": 46493, + Unknown: 47052, + }, + { + start_date: "Thu, 01 Apr 2021 00:00:00 GMT", + end_date: "Sat, 01 May 2021 00:00:00 GMT", + Pretrial: 60342, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 27224, + "Transfer or Hold": 14822, + Unknown: 35285, + }, + { + start_date: "Sat, 01 May 2021 00:00:00 GMT", + end_date: "Tue, 01 Jun 2021 00:00:00 GMT", + Pretrial: 76072, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 40997, + "Transfer or Hold": 84448, + Unknown: 86808, + }, + { + start_date: "Tue, 01 Jun 2021 00:00:00 GMT", + end_date: "Thu, 01 Jul 2021 00:00:00 GMT", + Pretrial: 55707, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 21701, + "Transfer or Hold": 97274, + Unknown: 70005, + }, + { + start_date: "Thu, 01 Jul 2021 00:00:00 GMT", + end_date: "Sun, 01 Aug 2021 00:00:00 GMT", + "Transfer or Hold": 92055, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 64106, + Pretrial: 19694, + Unknown: 51952, + }, + { + start_date: "Sun, 01 Aug 2021 00:00:00 GMT", + end_date: "Wed, 01 Sep 2021 00:00:00 GMT", + Pretrial: 19163, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 95457, + "Transfer or Hold": 50399, + Unknown: 37598, + }, + { + start_date: "Wed, 01 Sep 2021 00:00:00 GMT", + end_date: "Fri, 01 Oct 2021 00:00:00 GMT", + Pretrial: 56132, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 49555, + "Transfer or Hold": 67307, + Unknown: 16254, + }, + { + start_date: "Fri, 01 Oct 2021 00:00:00 GMT", + end_date: "Mon, 01 Nov 2021 00:00:00 GMT", + Pretrial: 74036, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 53503, + "Transfer or Hold": 29868, + Unknown: 94671, + }, + { + start_date: "Mon, 01 Nov 2021 00:00:00 GMT", + end_date: "Wed, 01 Dec 2021 00:00:00 GMT", + Sentenced: 34289, + frequency: "MONTHLY", + dataVizMissingData: 0, + Pretrial: 30696, + "Transfer or Hold": 56361, + Unknown: 18829, + }, + { + start_date: "Wed, 01 Dec 2021 00:00:00 GMT", + end_date: "Sat, 01 Jan 2022 00:00:00 GMT", + Pretrial: 21968, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 47320, + "Transfer or Hold": 43536, + Unknown: 71164, + }, + { + start_date: "Sat, 01 Jan 2022 00:00:00 GMT", + end_date: "Tue, 01 Feb 2022 00:00:00 GMT", + Pretrial: 38210, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 70537, + "Transfer or Hold": 87658, + Unknown: 84018, + }, + { + start_date: "Tue, 01 Feb 2022 00:00:00 GMT", + end_date: "Tue, 01 Mar 2022 00:00:00 GMT", + Pretrial: 37196, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 34013, + "Transfer or Hold": 35342, + Unknown: 16376, + }, + { + start_date: "Tue, 01 Mar 2022 00:00:00 GMT", + end_date: "Fri, 01 Apr 2022 00:00:00 GMT", + Pretrial: 13935, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 28989, + "Transfer or Hold": 56841, + Unknown: 52659, + }, + { + start_date: "Fri, 01 Apr 2022 00:00:00 GMT", + end_date: "Sun, 01 May 2022 00:00:00 GMT", + Pretrial: 54440, + frequency: "MONTHLY", + dataVizMissingData: 0, + Unknown: 55161, + "Transfer or Hold": 17782, + Sentenced: 90906, + }, + { + start_date: "Sun, 01 May 2022 00:00:00 GMT", + end_date: "Wed, 01 Jun 2022 00:00:00 GMT", + Pretrial: 49829, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 85046, + "Transfer or Hold": 60630, + Unknown: 10732, + }, + { + start_date: "Wed, 01 Jun 2022 00:00:00 GMT", + end_date: "Fri, 01 Jul 2022 00:00:00 GMT", + Pretrial: 15321, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 68658, + "Transfer or Hold": 73603, + Unknown: 72871, + }, + { + start_date: "Fri, 01 Jul 2022 00:00:00 GMT", + end_date: "Mon, 01 Aug 2022 00:00:00 GMT", + Pretrial: 61998, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 29566, + "Transfer or Hold": 98614, + Unknown: 63892, + }, + { + start_date: "Mon, 01 Aug 2022 00:00:00 GMT", + end_date: "Thu, 01 Sep 2022 00:00:00 GMT", + Pretrial: 49052, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 19779, + "Transfer or Hold": 38057, + Unknown: 49554, + }, + { + start_date: "Thu, 01 Sep 2022 00:00:00 GMT", + end_date: "Sat, 01 Oct 2022 00:00:00 GMT", + Sentenced: 52422, + frequency: "MONTHLY", + dataVizMissingData: 0, + Pretrial: 34244, + "Transfer or Hold": 62878, + Unknown: 48777, + }, + { + start_date: "Sat, 01 Oct 2022 00:00:00 GMT", + end_date: "Tue, 01 Nov 2022 00:00:00 GMT", + Pretrial: 67196, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 54738, + "Transfer or Hold": 95846, + Unknown: 94767, + }, + { + start_date: "Tue, 01 Nov 2022 00:00:00 GMT", + end_date: "Thu, 01 Dec 2022 00:00:00 GMT", + Pretrial: 76254, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 63070, + "Transfer or Hold": 25465, + Unknown: 81989, + }, + { + start_date: "Thu, 01 Dec 2022 00:00:00 GMT", + end_date: "Sun, 01 Jan 2023 00:00:00 GMT", + Unknown: 29537, + frequency: "MONTHLY", + dataVizMissingData: 0, + "Transfer or Hold": 52722, + Sentenced: 84901, + Pretrial: 34694, + }, +]; + +const testDatapoints2: Datapoint[] = [ + { + start_date: "Tue, 01 Mar 2022 00:00:00 GMT", + end_date: "Fri, 01 Apr 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 97164, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 36671, + Black: 26028, + "External / Unknown": 47948, + Hispanic: 57558, + "Native Hawaiian / Pacific Islander": 90632, + Other: 16338, + White: 22298, + }, + { + start_date: "Fri, 01 Apr 2022 00:00:00 GMT", + end_date: "Sun, 01 May 2022 00:00:00 GMT", + Hispanic: 66829, + frequency: "MONTHLY", + dataVizMissingData: 0, + "External / Unknown": 97373, + Black: 20261, + Asian: 63835, + "American Indian / Alaskan Native": 31596, + "Native Hawaiian / Pacific Islander": 82033, + Other: 29044, + White: 96511, + }, + { + start_date: "Sun, 01 May 2022 00:00:00 GMT", + end_date: "Wed, 01 Jun 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 79637, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 14139, + Black: 85121, + "External / Unknown": 38446, + Hispanic: 31772, + "Native Hawaiian / Pacific Islander": 88002, + Other: 33259, + White: 70561, + }, + { + start_date: "Wed, 01 Jun 2022 00:00:00 GMT", + end_date: "Fri, 01 Jul 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 45039, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 65632, + Black: 39540, + "External / Unknown": 16119, + Hispanic: 14102, + "Native Hawaiian / Pacific Islander": 52909, + Other: 60103, + White: 73688, + }, + { + start_date: "Fri, 01 Jul 2022 00:00:00 GMT", + end_date: "Mon, 01 Aug 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 80150, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 74203, + Black: 23688, + "External / Unknown": 44627, + Hispanic: 65335, + "Native Hawaiian / Pacific Islander": 56843, + Other: 58110, + White: 62313, + }, + { + start_date: "Mon, 01 Aug 2022 00:00:00 GMT", + end_date: "Thu, 01 Sep 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 21221, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 36873, + Black: 30958, + "External / Unknown": 40857, + Hispanic: 85505, + "Native Hawaiian / Pacific Islander": 66954, + Other: 93569, + White: 52647, + }, + { + start_date: "Thu, 01 Sep 2022 00:00:00 GMT", + end_date: "Sat, 01 Oct 2022 00:00:00 GMT", + Other: 69493, + frequency: "MONTHLY", + dataVizMissingData: 0, + White: 37434, + "Native Hawaiian / Pacific Islander": 87941, + "American Indian / Alaskan Native": 55887, + Asian: 69104, + Black: 73150, + "External / Unknown": 64346, + Hispanic: 33261, + }, + { + start_date: "Sat, 01 Oct 2022 00:00:00 GMT", + end_date: "Tue, 01 Nov 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 69650, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 24615, + Black: 55423, + "External / Unknown": 98968, + Hispanic: 68576, + "Native Hawaiian / Pacific Islander": 94690, + Other: 64054, + White: 81617, + }, + { + start_date: "Tue, 01 Nov 2022 00:00:00 GMT", + end_date: "Thu, 01 Dec 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 17038, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 44387, + Black: 51777, + "External / Unknown": 54533, + Hispanic: 16689, + "Native Hawaiian / Pacific Islander": 69182, + Other: 99842, + White: 29578, + }, + { + start_date: "Thu, 01 Dec 2022 00:00:00 GMT", + end_date: "Sun, 01 Jan 2023 00:00:00 GMT", + "Native Hawaiian / Pacific Islander": 89112, + frequency: "MONTHLY", + dataVizMissingData: 0, + Other: 37341, + White: 59971, + "American Indian / Alaskan Native": 18236, + Asian: 60388, + Black: 35380, + "External / Unknown": 36941, + Hispanic: 90445, + }, +]; + +const testDatapoints2Percentages: Datapoint[] = [ + { + start_date: "Tue, 01 Mar 2022 00:00:00 GMT", + end_date: "Fri, 01 Apr 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 0.24621107498790026, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 0.09292337008440668, + Black: 0.06595428203640308, + "External / Unknown": 0.12149899781318022, + Hispanic: 0.145850490450718, + "Native Hawaiian / Pacific Islander": 0.22965915512230226, + Other: 0.04140007145807413, + White: 0.05650255804701536, + }, + { + start_date: "Fri, 01 Apr 2022 00:00:00 GMT", + end_date: "Sun, 01 May 2022 00:00:00 GMT", + Hispanic: 0.13709018999675884, + frequency: "MONTHLY", + dataVizMissingData: 0, + "External / Unknown": 0.1997468624482545, + Black: 0.04156256025863519, + Asian: 0.1309484247623502, + "American Indian / Alaskan Native": 0.06481470085049294, + "Native Hawaiian / Pacific Islander": 0.16827903389253346, + Other: 0.059579635760910146, + White: 0.1979785920300647, + }, + { + start_date: "Sun, 01 May 2022 00:00:00 GMT", + end_date: "Wed, 01 Jun 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 0.18060856766386127, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 0.03206580531912722, + Black: 0.19304571854936192, + "External / Unknown": 0.08719159426403318, + Hispanic: 0.07205564513751397, + "Native Hawaiian / Pacific Islander": 0.19957953176984466, + Other: 0.07542800898994641, + White: 0.16002512830631133, + }, + { + start_date: "Wed, 01 Jun 2022 00:00:00 GMT", + end_date: "Fri, 01 Jul 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 0.1226779468964841, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 0.1787694889031738, + Black: 0.10769968294782258, + "External / Unknown": 0.04390518941416166, + Hispanic: 0.0384112526284824, + "Native Hawaiian / Pacific Islander": 0.1441143784796749, + Other: 0.16370951047579618, + White: 0.2007125502544044, + }, + { + start_date: "Fri, 01 Jul 2022 00:00:00 GMT", + end_date: "Mon, 01 Aug 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 0.17226593647975688, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 0.1594840834012152, + Black: 0.0509124828862443, + "External / Unknown": 0.09591655579890343, + Hispanic: 0.14042414173306195, + "Native Hawaiian / Pacific Islander": 0.12217233471389669, + Other: 0.12489549056567276, + White: 0.1339289744212488, + }, + { + start_date: "Mon, 01 Aug 2022 00:00:00 GMT", + end_date: "Thu, 01 Sep 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 0.04951421424971534, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 0.08603447632202789, + Black: 0.0722332144923749, + "External / Unknown": 0.09533020364735968, + Hispanic: 0.1995058144961081, + "Native Hawaiian / Pacific Islander": 0.15622141750508653, + Other: 0.21832126257629778, + White: 0.12283939671102981, + }, + { + start_date: "Thu, 01 Sep 2022 00:00:00 GMT", + end_date: "Sat, 01 Oct 2022 00:00:00 GMT", + Other: 0.14164438175681185, + frequency: "MONTHLY", + dataVizMissingData: 0, + White: 0.07629999836939684, + "Native Hawaiian / Pacific Islander": 0.17924609062892363, + "American Indian / Alaskan Native": 0.11391189851125931, + Asian: 0.14085150097020888, + Black: 0.14909827645245977, + "External / Unknown": 0.13115348867546106, + Hispanic: 0.06779436463547867, + }, + { + start_date: "Sat, 01 Oct 2022 00:00:00 GMT", + end_date: "Tue, 01 Nov 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 0.12491189810489013, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 0.04414510225200101, + Black: 0.09939687191195011, + "External / Unknown": 0.1774914677910232, + Hispanic: 0.12298576201638112, + "Native Hawaiian / Pacific Islander": 0.16981920504740913, + Other: 0.11487590410926966, + White: 0.14637378876707563, + }, + { + start_date: "Tue, 01 Nov 2022 00:00:00 GMT", + end_date: "Thu, 01 Dec 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 0.044482619978800396, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 0.11588508351913447, + Black: 0.13517881292653763, + "External / Unknown": 0.14237414692475184, + Hispanic: 0.04357145467931681, + "Native Hawaiian / Pacific Islander": 0.18061959240364883, + Other: 0.2606663777393702, + White: 0.07722191182843985, + }, + { + start_date: "Thu, 01 Dec 2022 00:00:00 GMT", + end_date: "Sun, 01 Jan 2023 00:00:00 GMT", + "Native Hawaiian / Pacific Islander": 0.20829612869144068, + frequency: "MONTHLY", + dataVizMissingData: 0, + Other: 0.08728325861238763, + White: 0.14018007825830853, + "American Indian / Alaskan Native": 0.04262600101913448, + Asian: 0.14115480091815602, + Black: 0.08269949090025104, + "External / Unknown": 0.0863482728475459, + Hispanic: 0.21141196875277574, + }, +]; + +const testDatapoints3: Datapoint[] = [ + { + Total: 28293, + start_date: "Tue, 01 Mar 2022 00:00:00 GMT", + end_date: "Fri, 01 Apr 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 56673, + start_date: "Fri, 01 Apr 2022 00:00:00 GMT", + end_date: "Sun, 01 May 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 59646, + start_date: "Sun, 01 May 2022 00:00:00 GMT", + end_date: "Wed, 01 Jun 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 95570, + start_date: "Wed, 01 Jun 2022 00:00:00 GMT", + end_date: "Fri, 01 Jul 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 23877, + start_date: "Fri, 01 Jul 2022 00:00:00 GMT", + end_date: "Mon, 01 Aug 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 42551, + start_date: "Mon, 01 Aug 2022 00:00:00 GMT", + end_date: "Thu, 01 Sep 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 77484, + start_date: "Thu, 01 Sep 2022 00:00:00 GMT", + end_date: "Sat, 01 Oct 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: null, + start_date: "Sat, 01 Oct 2022 00:00:00 GMT", + end_date: "Tue, 01 Nov 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 12312, + start_date: "Tue, 01 Nov 2022 00:00:00 GMT", + end_date: "Thu, 01 Dec 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 0, + start_date: "Thu, 01 Dec 2022 00:00:00 GMT", + end_date: "Sun, 01 Jan 2023 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, +]; + +const testDatapoints3WithoutNullDatapoints: Datapoint[] = [ + { + Total: 28293, + start_date: "Tue, 01 Mar 2022 00:00:00 GMT", + end_date: "Fri, 01 Apr 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 56673, + start_date: "Fri, 01 Apr 2022 00:00:00 GMT", + end_date: "Sun, 01 May 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 59646, + start_date: "Sun, 01 May 2022 00:00:00 GMT", + end_date: "Wed, 01 Jun 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 95570, + start_date: "Wed, 01 Jun 2022 00:00:00 GMT", + end_date: "Fri, 01 Jul 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 23877, + start_date: "Fri, 01 Jul 2022 00:00:00 GMT", + end_date: "Mon, 01 Aug 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 42551, + start_date: "Mon, 01 Aug 2022 00:00:00 GMT", + end_date: "Thu, 01 Sep 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 77484, + start_date: "Thu, 01 Sep 2022 00:00:00 GMT", + end_date: "Sat, 01 Oct 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 12312, + start_date: "Tue, 01 Nov 2022 00:00:00 GMT", + end_date: "Thu, 01 Dec 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 0, + start_date: "Thu, 01 Dec 2022 00:00:00 GMT", + end_date: "Sun, 01 Jan 2023 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, +]; + +const testDatapoints4: Datapoint[] = [ + { + Total: 52342, + start_date: "Thu, 01 Jan 2015 00:00:00 GMT", + end_date: "Fri, 01 Jan 2016 00:00:00 GMT", + frequency: "ANNUAL", + dataVizMissingData: 0, + }, + { + Total: 41241, + start_date: "Tue, 01 Jan 2019 00:00:00 GMT", + end_date: "Wed, 01 Jan 2020 00:00:00 GMT", + frequency: "ANNUAL", + dataVizMissingData: 0, + }, + { + Total: 74435, + start_date: "Sat, 01 Jan 2022 00:00:00 GMT", + end_date: "Sun, 01 Jan 2023 00:00:00 GMT", + frequency: "ANNUAL", + dataVizMissingData: 0, + }, +]; + +const testDatapoints4WithGapDatapoints: Datapoint[] = [ + { + Total: 52342, + start_date: "Thu, 01 Jan 2015 00:00:00 GMT", + end_date: "Fri, 01 Jan 2016 00:00:00 GMT", + frequency: "ANNUAL", + dataVizMissingData: 0, + }, + { + start_date: "Fri, 01 Jan 2016 00:00:00 GMT", + end_date: "Sun, 01 Jan 2017 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + start_date: "Sun, 01 Jan 2017 00:00:00 GMT", + end_date: "Mon, 01 Jan 2018 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + start_date: "Mon, 01 Jan 2018 00:00:00 GMT", + end_date: "Tue, 01 Jan 2019 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + Total: 41241, + start_date: "Tue, 01 Jan 2019 00:00:00 GMT", + end_date: "Wed, 01 Jan 2020 00:00:00 GMT", + frequency: "ANNUAL", + dataVizMissingData: 0, + }, + { + start_date: "Wed, 01 Jan 2020 00:00:00 GMT", + end_date: "Fri, 01 Jan 2021 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + start_date: "Fri, 01 Jan 2021 00:00:00 GMT", + end_date: "Sat, 01 Jan 2022 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + Total: 74435, + start_date: "Sat, 01 Jan 2022 00:00:00 GMT", + end_date: "Sun, 01 Jan 2023 00:00:00 GMT", + frequency: "ANNUAL", + dataVizMissingData: 0, + }, +]; + +const testDatapoints4WithGapDatapoints2: Datapoint[] = [ + { + start_date: "Tue, 01 Jan 2013 00:00:00 GMT", + end_date: "Wed, 01 Jan 2014 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + start_date: "Wed, 01 Jan 2014 00:00:00 GMT", + end_date: "Thu, 01 Jan 2015 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + Total: 52342, + start_date: "Thu, 01 Jan 2015 00:00:00 GMT", + end_date: "Fri, 01 Jan 2016 00:00:00 GMT", + frequency: "ANNUAL", + dataVizMissingData: 0, + }, + { + start_date: "Fri, 01 Jan 2016 00:00:00 GMT", + end_date: "Sun, 01 Jan 2017 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + start_date: "Sun, 01 Jan 2017 00:00:00 GMT", + end_date: "Mon, 01 Jan 2018 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + start_date: "Mon, 01 Jan 2018 00:00:00 GMT", + end_date: "Tue, 01 Jan 2019 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + Total: 41241, + start_date: "Tue, 01 Jan 2019 00:00:00 GMT", + end_date: "Wed, 01 Jan 2020 00:00:00 GMT", + frequency: "ANNUAL", + dataVizMissingData: 0, + }, + { + start_date: "Wed, 01 Jan 2020 00:00:00 GMT", + end_date: "Fri, 01 Jan 2021 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + start_date: "Fri, 01 Jan 2021 00:00:00 GMT", + end_date: "Sat, 01 Jan 2022 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + Total: 74435, + start_date: "Sat, 01 Jan 2022 00:00:00 GMT", + end_date: "Sun, 01 Jan 2023 00:00:00 GMT", + frequency: "ANNUAL", + dataVizMissingData: 0, + }, +]; + +const testDatapoints5: Datapoint[] = [ + { + start_date: "Thu, 01 Aug 2019 00:00:00 GMT", + end_date: "Sun, 01 Sep 2019 00:00:00 GMT", + Pretrial: 41, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 32, + "Transfer or Hold": 53, + Unknown: 12, + }, + { + start_date: "Wed, 01 Jan 2020 00:00:00 GMT", + end_date: "Sat, 01 Feb 2020 00:00:00 GMT", + Pretrial: 36541, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 79028, + Unknown: 65749, + "Transfer or Hold": 48334, + }, + { + start_date: "Sat, 01 Feb 2020 00:00:00 GMT", + end_date: "Sun, 01 Mar 2020 00:00:00 GMT", + Pretrial: 24112, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 82706, + "Transfer or Hold": 83057, + Unknown: 17415, + }, + { + start_date: "Sun, 01 Mar 2020 00:00:00 GMT", + end_date: "Wed, 01 Apr 2020 00:00:00 GMT", + Pretrial: 23767, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 90978, + "Transfer or Hold": 82590, + Unknown: 35303, + }, + { + start_date: "Wed, 01 Apr 2020 00:00:00 GMT", + end_date: "Fri, 01 May 2020 00:00:00 GMT", + Pretrial: 78458, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 46769, + "Transfer or Hold": 64874, + Unknown: 38205, + }, + { + start_date: "Fri, 01 May 2020 00:00:00 GMT", + end_date: "Mon, 01 Jun 2020 00:00:00 GMT", + Pretrial: 22677, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 61323, + "Transfer or Hold": 72938, + Unknown: 29553, + }, + { + start_date: "Mon, 01 Jun 2020 00:00:00 GMT", + end_date: "Wed, 01 Jul 2020 00:00:00 GMT", + Pretrial: 88997, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 38048, + "Transfer or Hold": 84709, + Unknown: 62951, + }, + { + start_date: "Wed, 01 Jul 2020 00:00:00 GMT", + end_date: "Sat, 01 Aug 2020 00:00:00 GMT", + Pretrial: 16324, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 16996, + "Transfer or Hold": 63347, + Unknown: 91685, + }, + { + start_date: "Sat, 01 Aug 2020 00:00:00 GMT", + end_date: "Tue, 01 Sep 2020 00:00:00 GMT", + Pretrial: 46650, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 23200, + "Transfer or Hold": 24195, + Unknown: 33681, + }, + { + start_date: "Tue, 01 Sep 2020 00:00:00 GMT", + end_date: "Thu, 01 Oct 2020 00:00:00 GMT", + Pretrial: 82010, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 14841, + "Transfer or Hold": 82407, + Unknown: 97055, + }, + { + start_date: "Thu, 01 Oct 2020 00:00:00 GMT", + end_date: "Sun, 01 Nov 2020 00:00:00 GMT", + Sentenced: 37221, + frequency: "MONTHLY", + dataVizMissingData: 0, + "Transfer or Hold": 22512, + Unknown: 49751, + Pretrial: 51938, + }, + { + start_date: "Sun, 01 Nov 2020 00:00:00 GMT", + end_date: "Tue, 01 Dec 2020 00:00:00 GMT", + Pretrial: 13252, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 89151, + "Transfer or Hold": 31343, + Unknown: 17920, + }, + { + start_date: "Tue, 01 Dec 2020 00:00:00 GMT", + end_date: "Fri, 01 Jan 2021 00:00:00 GMT", + Pretrial: 42300, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 27629, + "Transfer or Hold": 11736, + Unknown: 13635, + }, + { + start_date: "Fri, 01 Jan 2021 00:00:00 GMT", + end_date: "Mon, 01 Feb 2021 00:00:00 GMT", + Pretrial: 98369, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 46574, + "Transfer or Hold": 21353, + Unknown: 88769, + }, + { + start_date: "Mon, 01 Feb 2021 00:00:00 GMT", + end_date: "Mon, 01 Mar 2021 00:00:00 GMT", + Pretrial: 78759, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 53386, + "Transfer or Hold": 59075, + Unknown: 27134, + }, + { + start_date: "Mon, 01 Mar 2021 00:00:00 GMT", + end_date: "Thu, 01 Apr 2021 00:00:00 GMT", + Pretrial: 60053, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 34197, + "Transfer or Hold": 63531, + Unknown: 83579, + }, + { + start_date: "Thu, 01 Apr 2021 00:00:00 GMT", + end_date: "Sat, 01 May 2021 00:00:00 GMT", + Pretrial: 44759, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 17229, + "Transfer or Hold": 77299, + Unknown: 58031, + }, + { + start_date: "Sat, 01 May 2021 00:00:00 GMT", + end_date: "Tue, 01 Jun 2021 00:00:00 GMT", + Pretrial: 61558, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 59243, + "Transfer or Hold": 46498, + Unknown: 53175, + }, + { + start_date: "Tue, 01 Jun 2021 00:00:00 GMT", + end_date: "Thu, 01 Jul 2021 00:00:00 GMT", + Pretrial: 73726, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 60098, + "Transfer or Hold": 24792, + Unknown: 90376, + }, + { + start_date: "Thu, 01 Jul 2021 00:00:00 GMT", + end_date: "Sun, 01 Aug 2021 00:00:00 GMT", + Pretrial: 31102, + frequency: "MONTHLY", + dataVizMissingData: 0, + Unknown: 78794, + "Transfer or Hold": 10572, + Sentenced: 30228, + }, + { + start_date: "Sun, 01 Aug 2021 00:00:00 GMT", + end_date: "Wed, 01 Sep 2021 00:00:00 GMT", + Pretrial: 99665, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 82644, + "Transfer or Hold": 58060, + Unknown: 72892, + }, + { + start_date: "Wed, 01 Sep 2021 00:00:00 GMT", + end_date: "Fri, 01 Oct 2021 00:00:00 GMT", + Pretrial: 46416, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 33763, + "Transfer or Hold": 10680, + Unknown: 94129, + }, + { + start_date: "Fri, 01 Oct 2021 00:00:00 GMT", + end_date: "Mon, 01 Nov 2021 00:00:00 GMT", + Pretrial: 80719, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 96265, + "Transfer or Hold": 12347, + Unknown: 18056, + }, + { + start_date: "Mon, 01 Nov 2021 00:00:00 GMT", + end_date: "Wed, 01 Dec 2021 00:00:00 GMT", + Pretrial: 83549, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 77473, + "Transfer or Hold": 49179, + Unknown: 78634, + }, + { + start_date: "Wed, 01 Dec 2021 00:00:00 GMT", + end_date: "Sat, 01 Jan 2022 00:00:00 GMT", + Sentenced: 52931, + frequency: "MONTHLY", + dataVizMissingData: 0, + "Transfer or Hold": 24687, + Unknown: 92620, + Pretrial: 40598, + }, + { + start_date: "Sat, 01 Jan 2022 00:00:00 GMT", + end_date: "Tue, 01 Feb 2022 00:00:00 GMT", + Pretrial: 75641, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 59666, + "Transfer or Hold": 17668, + Unknown: 92062, + }, + { + start_date: "Tue, 01 Feb 2022 00:00:00 GMT", + end_date: "Tue, 01 Mar 2022 00:00:00 GMT", + Pretrial: 97743, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 34823, + "Transfer or Hold": 37207, + Unknown: 18960, + }, + { + start_date: "Tue, 01 Mar 2022 00:00:00 GMT", + end_date: "Fri, 01 Apr 2022 00:00:00 GMT", + Pretrial: 80843, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 92536, + "Transfer or Hold": 56184, + Unknown: 51573, + }, + { + start_date: "Fri, 01 Apr 2022 00:00:00 GMT", + end_date: "Sun, 01 May 2022 00:00:00 GMT", + Pretrial: 10958, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 48931, + "Transfer or Hold": 46672, + Unknown: 98907, + }, + { + start_date: "Sun, 01 May 2022 00:00:00 GMT", + end_date: "Wed, 01 Jun 2022 00:00:00 GMT", + Pretrial: 63710, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 26703, + "Transfer or Hold": 72473, + Unknown: 85367, + }, + { + start_date: "Wed, 01 Jun 2022 00:00:00 GMT", + end_date: "Fri, 01 Jul 2022 00:00:00 GMT", + Pretrial: 32303, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 85419, + "Transfer or Hold": 98064, + Unknown: 95473, + }, + { + start_date: "Fri, 01 Jul 2022 00:00:00 GMT", + end_date: "Mon, 01 Aug 2022 00:00:00 GMT", + Unknown: 90809, + frequency: "MONTHLY", + dataVizMissingData: 0, + "Transfer or Hold": 18233, + Sentenced: 38446, + Pretrial: 60273, + }, + { + start_date: "Mon, 01 Aug 2022 00:00:00 GMT", + end_date: "Thu, 01 Sep 2022 00:00:00 GMT", + Pretrial: 51711, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 25203, + "Transfer or Hold": 12208, + Unknown: 37137, + }, + { + start_date: "Thu, 01 Sep 2022 00:00:00 GMT", + end_date: "Sat, 01 Oct 2022 00:00:00 GMT", + Pretrial: 92358, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 99972, + "Transfer or Hold": 54671, + Unknown: 49974, + }, + { + start_date: "Sat, 01 Oct 2022 00:00:00 GMT", + end_date: "Tue, 01 Nov 2022 00:00:00 GMT", + Pretrial: null, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: null, + "Transfer or Hold": null, + Unknown: null, + }, + { + start_date: "Tue, 01 Nov 2022 00:00:00 GMT", + end_date: "Thu, 01 Dec 2022 00:00:00 GMT", + Unknown: 4000, + frequency: "MONTHLY", + dataVizMissingData: 0, + Pretrial: null, + Sentenced: null, + "Transfer or Hold": 1, + }, + { + start_date: "Thu, 01 Dec 2022 00:00:00 GMT", + end_date: "Sun, 01 Jan 2023 00:00:00 GMT", + Pretrial: null, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: null, + "Transfer or Hold": 0, + Unknown: null, + }, +]; + +const testDatapoints5Transformed: Datapoint[] = [ + { + start_date: "Fri, 01 Sep 2017 00:00:00 GMT", + end_date: "Sun, 01 Oct 2017 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Sun, 01 Oct 2017 00:00:00 GMT", + end_date: "Wed, 01 Nov 2017 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Wed, 01 Nov 2017 00:00:00 GMT", + end_date: "Fri, 01 Dec 2017 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Fri, 01 Dec 2017 00:00:00 GMT", + end_date: "Mon, 01 Jan 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Mon, 01 Jan 2018 00:00:00 GMT", + end_date: "Thu, 01 Feb 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Thu, 01 Feb 2018 00:00:00 GMT", + end_date: "Thu, 01 Mar 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Thu, 01 Mar 2018 00:00:00 GMT", + end_date: "Sun, 01 Apr 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Sun, 01 Apr 2018 00:00:00 GMT", + end_date: "Tue, 01 May 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Tue, 01 May 2018 00:00:00 GMT", + end_date: "Fri, 01 Jun 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Fri, 01 Jun 2018 00:00:00 GMT", + end_date: "Sun, 01 Jul 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Sun, 01 Jul 2018 00:00:00 GMT", + end_date: "Wed, 01 Aug 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Wed, 01 Aug 2018 00:00:00 GMT", + end_date: "Sat, 01 Sep 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Sat, 01 Sep 2018 00:00:00 GMT", + end_date: "Mon, 01 Oct 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Mon, 01 Oct 2018 00:00:00 GMT", + end_date: "Thu, 01 Nov 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Thu, 01 Nov 2018 00:00:00 GMT", + end_date: "Sat, 01 Dec 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Sat, 01 Dec 2018 00:00:00 GMT", + end_date: "Tue, 01 Jan 2019 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Tue, 01 Jan 2019 00:00:00 GMT", + end_date: "Fri, 01 Feb 2019 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Fri, 01 Feb 2019 00:00:00 GMT", + end_date: "Fri, 01 Mar 2019 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Fri, 01 Mar 2019 00:00:00 GMT", + end_date: "Mon, 01 Apr 2019 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Mon, 01 Apr 2019 00:00:00 GMT", + end_date: "Wed, 01 May 2019 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Wed, 01 May 2019 00:00:00 GMT", + end_date: "Sat, 01 Jun 2019 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Sat, 01 Jun 2019 00:00:00 GMT", + end_date: "Mon, 01 Jul 2019 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Mon, 01 Jul 2019 00:00:00 GMT", + end_date: "Thu, 01 Aug 2019 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Thu, 01 Aug 2019 00:00:00 GMT", + end_date: "Sun, 01 Sep 2019 00:00:00 GMT", + Pretrial: 0.2971014492753623, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.2318840579710145, + "Transfer or Hold": 0.38405797101449274, + Unknown: 0.08695652173913043, + }, + { + start_date: "Sun, 01 Sep 2019 00:00:00 GMT", + end_date: "Tue, 01 Oct 2019 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Tue, 01 Oct 2019 00:00:00 GMT", + end_date: "Fri, 01 Nov 2019 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Fri, 01 Nov 2019 00:00:00 GMT", + end_date: "Sun, 01 Dec 2019 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Sun, 01 Dec 2019 00:00:00 GMT", + end_date: "Wed, 01 Jan 2020 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Wed, 01 Jan 2020 00:00:00 GMT", + end_date: "Sat, 01 Feb 2020 00:00:00 GMT", + Pretrial: 0.15911466044275688, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.3441206695347744, + Unknown: 0.2862983993172278, + "Transfer or Hold": 0.21046627070524096, + }, + { + start_date: "Sat, 01 Feb 2020 00:00:00 GMT", + end_date: "Sun, 01 Mar 2020 00:00:00 GMT", + Pretrial: 0.11632013121713541, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.3989869265280525, + "Transfer or Hold": 0.4006802064740219, + Unknown: 0.0840127357807902, + }, + { + start_date: "Sun, 01 Mar 2020 00:00:00 GMT", + end_date: "Wed, 01 Apr 2020 00:00:00 GMT", + Pretrial: 0.10216301721988669, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.39107110618213703, + "Transfer or Hold": 0.355015087818843, + Unknown: 0.15175078877913326, + }, + { + start_date: "Wed, 01 Apr 2020 00:00:00 GMT", + end_date: "Fri, 01 May 2020 00:00:00 GMT", + Pretrial: 0.343652816833548, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.204852259686561, + "Transfer or Hold": 0.28415372351142765, + Unknown: 0.1673411999684634, + }, + { + start_date: "Fri, 01 May 2020 00:00:00 GMT", + end_date: "Mon, 01 Jun 2020 00:00:00 GMT", + Pretrial: 0.12159836131502325, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.32882551973017465, + "Transfer or Hold": 0.39110734566279337, + Unknown: 0.15846877329200873, + }, + { + start_date: "Mon, 01 Jun 2020 00:00:00 GMT", + end_date: "Wed, 01 Jul 2020 00:00:00 GMT", + Pretrial: 0.32397298920660345, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.13850494166469485, + "Transfer or Hold": 0.3083635172275714, + Unknown: 0.2291585519011303, + }, + { + start_date: "Wed, 01 Jul 2020 00:00:00 GMT", + end_date: "Sat, 01 Aug 2020 00:00:00 GMT", + Pretrial: 0.0866675161399932, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.0902353041114509, + "Transfer or Hold": 0.33632241760108733, + Unknown: 0.48677476214746856, + }, + { + start_date: "Sat, 01 Aug 2020 00:00:00 GMT", + end_date: "Tue, 01 Sep 2020 00:00:00 GMT", + Pretrial: 0.36523495607785417, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.1816388206003476, + "Transfer or Hold": 0.1894289338114401, + Unknown: 0.2636972895103581, + }, + { + start_date: "Tue, 01 Sep 2020 00:00:00 GMT", + end_date: "Thu, 01 Oct 2020 00:00:00 GMT", + Pretrial: 0.296801091515781, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.05371082793788204, + "Transfer or Hold": 0.29823786792514284, + Unknown: 0.3512502126211941, + }, + { + start_date: "Thu, 01 Oct 2020 00:00:00 GMT", + end_date: "Sun, 01 Nov 2020 00:00:00 GMT", + Sentenced: 0.23058195289365763, + frequency: "MONTHLY", + dataVizMissingData: 0, + "Transfer or Hold": 0.1394605444115424, + Unknown: 0.30820458177943527, + Pretrial: 0.32175292091536467, + }, + { + start_date: "Sun, 01 Nov 2020 00:00:00 GMT", + end_date: "Tue, 01 Dec 2020 00:00:00 GMT", + Pretrial: 0.08737620824706922, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.5878113749950549, + "Transfer or Hold": 0.2066580512441813, + Unknown: 0.11815436551369457, + }, + { + start_date: "Tue, 01 Dec 2020 00:00:00 GMT", + end_date: "Fri, 01 Jan 2021 00:00:00 GMT", + Pretrial: 0.4438614900314795, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.28991605456453307, + "Transfer or Hold": 0.1231479538300105, + Unknown: 0.14307450157397691, + }, + { + start_date: "Fri, 01 Jan 2021 00:00:00 GMT", + end_date: "Mon, 01 Feb 2021 00:00:00 GMT", + Pretrial: 0.38566247819183347, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.18259659302530729, + "Transfer or Hold": 0.08371591555093799, + Unknown: 0.34802501323192125, + }, + { + start_date: "Mon, 01 Feb 2021 00:00:00 GMT", + end_date: "Mon, 01 Mar 2021 00:00:00 GMT", + Pretrial: 0.3606941022376508, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.24449288769612648, + "Transfer or Hold": 0.2705469100634749, + Unknown: 0.12426610000274783, + }, + { + start_date: "Mon, 01 Mar 2021 00:00:00 GMT", + end_date: "Thu, 01 Apr 2021 00:00:00 GMT", + Pretrial: 0.2488109048723898, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.14168462048392444, + "Transfer or Hold": 0.26322091481604243, + Unknown: 0.34628355982764336, + }, + { + start_date: "Thu, 01 Apr 2021 00:00:00 GMT", + end_date: "Sat, 01 May 2021 00:00:00 GMT", + Pretrial: 0.22683688259560708, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.08731590630353034, + "Transfer or Hold": 0.39174834531061536, + Unknown: 0.2940988657902472, + }, + { + start_date: "Sat, 01 May 2021 00:00:00 GMT", + end_date: "Tue, 01 Jun 2021 00:00:00 GMT", + Pretrial: 0.2792075256039261, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.2687074212832352, + "Transfer or Hold": 0.21090015149178587, + Unknown: 0.24118490162105283, + }, + { + start_date: "Tue, 01 Jun 2021 00:00:00 GMT", + end_date: "Thu, 01 Jul 2021 00:00:00 GMT", + Pretrial: 0.29609786659812365, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.24136518442359595, + "Transfer or Hold": 0.0995694640791672, + Unknown: 0.36296748489911324, + }, + { + start_date: "Thu, 01 Jul 2021 00:00:00 GMT", + end_date: "Sun, 01 Aug 2021 00:00:00 GMT", + Pretrial: 0.2063890216064129, + frequency: "MONTHLY", + dataVizMissingData: 0, + Unknown: 0.5228672293889686, + "Transfer or Hold": 0.07015448319796146, + Sentenced: 0.20058926580665712, + }, + { + start_date: "Sun, 01 Aug 2021 00:00:00 GMT", + end_date: "Wed, 01 Sep 2021 00:00:00 GMT", + Pretrial: 0.3181532332463984, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.26381834955516326, + "Transfer or Hold": 0.18534065842859468, + Unknown: 0.23268775876984368, + }, + { + start_date: "Wed, 01 Sep 2021 00:00:00 GMT", + end_date: "Fri, 01 Oct 2021 00:00:00 GMT", + Pretrial: 0.25091357277228793, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.1825145414837719, + "Transfer or Hold": 0.05773347460375808, + Unknown: 0.508838411140182, + }, + { + start_date: "Fri, 01 Oct 2021 00:00:00 GMT", + end_date: "Mon, 01 Nov 2021 00:00:00 GMT", + Pretrial: 0.3892191892452275, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.4641804934735543, + "Transfer or Hold": 0.059536036492162, + Unknown: 0.0870642807890562, + }, + { + start_date: "Mon, 01 Nov 2021 00:00:00 GMT", + end_date: "Wed, 01 Dec 2021 00:00:00 GMT", + Pretrial: 0.2892620354181453, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.2682258036595288, + "Transfer or Hold": 0.17026676129970397, + Unknown: 0.2722453996226219, + }, + { + start_date: "Wed, 01 Dec 2021 00:00:00 GMT", + end_date: "Sat, 01 Jan 2022 00:00:00 GMT", + Sentenced: 0.2510529511089188, + frequency: "MONTHLY", + dataVizMissingData: 0, + "Transfer or Hold": 0.11709100912557628, + Unknown: 0.43929879147773626, + Pretrial: 0.19255724828776868, + }, + { + start_date: "Sat, 01 Jan 2022 00:00:00 GMT", + end_date: "Tue, 01 Feb 2022 00:00:00 GMT", + Pretrial: 0.30869215669470323, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.24349792072217666, + "Transfer or Hold": 0.0721033966298967, + Unknown: 0.3757065259532234, + }, + { + start_date: "Tue, 01 Feb 2022 00:00:00 GMT", + end_date: "Tue, 01 Mar 2022 00:00:00 GMT", + Pretrial: 0.5178903530384193, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.1845093332909454, + "Transfer or Hold": 0.19714093454774734, + Unknown: 0.1004593791228879, + }, + { + start_date: "Tue, 01 Mar 2022 00:00:00 GMT", + end_date: "Fri, 01 Apr 2022 00:00:00 GMT", + Pretrial: 0.28755833475613224, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.32915030447897103, + "Transfer or Hold": 0.19984633771555405, + Unknown: 0.18344502304934265, + }, + { + start_date: "Fri, 01 Apr 2022 00:00:00 GMT", + end_date: "Sun, 01 May 2022 00:00:00 GMT", + Pretrial: 0.05333190569821091, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.23814413923335995, + "Transfer or Hold": 0.22714972647808904, + Unknown: 0.4813742285903401, + }, + { + start_date: "Sun, 01 May 2022 00:00:00 GMT", + end_date: "Wed, 01 Jun 2022 00:00:00 GMT", + Pretrial: 0.25663335387689173, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.10756365481988132, + "Transfer or Hold": 0.2919320209624858, + Unknown: 0.3438709703407411, + }, + { + start_date: "Wed, 01 Jun 2022 00:00:00 GMT", + end_date: "Fri, 01 Jul 2022 00:00:00 GMT", + Pretrial: 0.10378173803809689, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.27443061887367115, + "Transfer or Hold": 0.3150559501893921, + Unknown: 0.3067316928988399, + }, + { + start_date: "Fri, 01 Jul 2022 00:00:00 GMT", + end_date: "Mon, 01 Aug 2022 00:00:00 GMT", + Unknown: 0.437083957046799, + frequency: "MONTHLY", + dataVizMissingData: 0, + "Transfer or Hold": 0.08775949287883673, + Sentenced: 0.1850491670717796, + Pretrial: 0.2901073830025847, + }, + { + start_date: "Mon, 01 Aug 2022 00:00:00 GMT", + end_date: "Thu, 01 Sep 2022 00:00:00 GMT", + Pretrial: 0.4095628826459896, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.19961349289951608, + "Transfer or Hold": 0.09669013694073293, + Unknown: 0.2941334875137614, + }, + { + start_date: "Thu, 01 Sep 2022 00:00:00 GMT", + end_date: "Sat, 01 Oct 2022 00:00:00 GMT", + Pretrial: 0.3109958750736594, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.3366343968347504, + "Transfer or Hold": 0.18409293711591884, + Unknown: 0.16827679097567136, + }, + { + start_date: "Sat, 01 Oct 2022 00:00:00 GMT", + end_date: "Tue, 01 Nov 2022 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Tue, 01 Nov 2022 00:00:00 GMT", + end_date: "Thu, 01 Dec 2022 00:00:00 GMT", + Unknown: 0.9997500624843789, + frequency: "MONTHLY", + dataVizMissingData: 0, + Pretrial: null, + Sentenced: null, + "Transfer or Hold": 0.00024993751562109475, + }, + { + start_date: "Thu, 01 Dec 2022 00:00:00 GMT", + end_date: "Sun, 01 Jan 2023 00:00:00 GMT", + Pretrial: null, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: null, + "Transfer or Hold": 0, + Unknown: null, + }, +]; + +beforeAll(() => { + jest.useFakeTimers("modern"); + jest.setSystemTime(new Date(2022, 7, 23)); +}); + +afterAll(() => { + jest.useRealTimers(); +}); + +describe("incrementMonth", () => { + test("incrementMonth increments month correctly", () => { + const testDate = new Date("Tue, 01 Mar 2022 00:00:00 GMT"); + const testDate2 = new Date("Thu, 01 Dec 2022 00:00:00 GMT"); + expect(incrementMonth(testDate).toUTCString()).toBe( + "Fri, 01 Apr 2022 00:00:00 GMT" + ); + expect(incrementMonth(testDate2).toUTCString()).toBe( + "Sun, 01 Jan 2023 00:00:00 GMT" + ); + }); +}); + +describe("incrementYear", () => { + test("incrementYear increments year correctly", () => { + const testDate = new Date("Tue, 01 Mar 2022 00:00:00 GMT"); + const testDate2 = new Date("Thu, 01 Dec 2022 00:00:00 GMT"); + expect(incrementYear(testDate).toUTCString()).toBe( + "Wed, 01 Mar 2023 00:00:00 GMT" + ); + expect(incrementYear(testDate2).toUTCString()).toBe( + "Fri, 01 Dec 2023 00:00:00 GMT" + ); + }); +}); + +describe("filterByTimeRange", () => { + test("filterByTimeRange filters through different months", () => { + expect(filterByTimeRange(testDatapoints, 6).length).toBe(10); + expect(filterByTimeRange(testDatapoints, 12).length).toBe(16); + }); +}); + +describe("transformToRelativePerchanges", () => { + test("transformToRelativePerchanges transforms datapoints correctly", () => { + expect(transformToRelativePerchanges(testDatapoints2)).toStrictEqual( + testDatapoints2Percentages + ); + }); +}); + +describe("filterNullDatapoints", () => { + test("filterNullDatapoints filters datapoints with all null dimensions only", () => { + expect(filterNullDatapoints(testDatapoints3)).toStrictEqual( + testDatapoints3WithoutNullDatapoints + ); + }); +}); + +describe("fillTimeGapsBetweenDatapoints", () => { + test("fillTimeGapsBetweenDatapoints adds datapoints between data", () => { + expect(fillTimeGapsBetweenDatapoints(testDatapoints4, 0)).toStrictEqual( + testDatapoints4WithGapDatapoints + ); + }); + test("fillTimeGapsBetweenDatapoints adds datapoints between data plus additional earlier month padding", () => { + expect(fillTimeGapsBetweenDatapoints(testDatapoints4, 120)).toStrictEqual( + testDatapoints4WithGapDatapoints2 + ); + }); +}); + +describe("transformData", () => { + test("putting it all together", () => { + expect(transformData(testDatapoints5, 60, "Percentage")).toStrictEqual( + testDatapoints5Transformed + ); + }); +}); diff --git a/common/components/DataViz/utils.ts b/common/components/DataViz/utils.ts new file mode 100644 index 000000000..0211a7978 --- /dev/null +++ b/common/components/DataViz/utils.ts @@ -0,0 +1,340 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { mapValues, pickBy } from "lodash"; + +import { + Datapoint, + DatapointsViewSetting, + DataVizAggregateName, + DataVizTimeRange, +} from "../../types"; +import { formatNumberInput } from "../../utils"; + +export const thirtyOneDaysInSeconds = 2678400000; +export const threeHundredSixtySixDaysInSeconds = 31622400000; + +export const nextMonthMap = new Map([ + ["Jan", "Feb"], + ["Feb", "Mar"], + ["Mar", "Apr"], + ["Apr", "May"], + ["May", "Jun"], + ["Jun", "Jul"], + ["Jul", "Aug"], + ["Aug", "Sep"], + ["Sep", "Oct"], + ["Oct", "Nov"], + ["Nov", "Dec"], + ["Dec", "Jan"], +]); + +const abbreviatedMonths = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", +]; + +export const splitUtcString = (utcString: string) => { + // the utc string can be split like this: + // const [dayOfWeek, day, month, year, time, timezone] = splitUtcString(str); + return utcString.split(" "); +}; + +export const getDatapointDimensions = (datapoint: Datapoint) => + // gets the datapoint object minus the non-dimension keys "start_date", "end_date", "frequency", "dataVizMissingData" + pickBy( + datapoint, + (val, key) => + key !== "start_date" && + key !== "end_date" && + key !== "frequency" && + key !== "dataVizMissingData" + ); + +export const sortDatapointDimensions = (dimA: string, dimB: string) => { + // sort alphabetically, except put "Other" and "Unknown" at the end. + if (dimA === "Other" && dimB === "Unknown") { + return -1; + } + if (dimB === "Other" && dimA === "Unknown") { + return 1; + } + if (dimA === "Other" || dimA === "Unknown") { + return 1; + } + if (dimB === "Other" || dimB === "Unknown") { + return -1; + } + return dimA.localeCompare(dimB); +}; + +export const getSumOfDimensionValues = (datapoint: Datapoint) => { + let sumOfDimensions = 0; + const dimensions = getDatapointDimensions(datapoint); + Object.values(dimensions).forEach((value) => { + sumOfDimensions += value as number; + }); + return sumOfDimensions; +}; + +// write my own month incrementer since Date.setMonth doesn't keep the date the same... +export const incrementMonth = (date: Date) => { + const [, day, month, year, time, timezone] = splitUtcString( + date.toUTCString() + ); + return new Date( + `${day} ${nextMonthMap.get(month)} ${ + month === "Dec" ? Number(year) + 1 : year + } ${time} ${timezone}` + ); +}; + +export const incrementYear = (date: Date) => { + const clonedDate = new Date(date.getTime()); + clonedDate.setFullYear(clonedDate.getFullYear() + 1); + return clonedDate; +}; + +// returns a new Date set to the GMT time zone +// for comparing with Datapoint time strings which are also set to 00:00:00 GMT. +export const createGMTDate = ( + day: number, + monthIndex: number, + year: number +) => { + return new Date( + `${day} ${abbreviatedMonths[monthIndex]} ${year} 00:00:00 GMT` + ); +}; + +export const getHighestTotalValue = (data: Datapoint[]) => { + let highestValue = 0; + data.forEach((datapoint) => { + const sumOfDimensions = getSumOfDimensionValues(datapoint); + if (sumOfDimensions > highestValue) { + highestValue = sumOfDimensions; + } + }); + return highestValue; +}; + +// functions to transform and filter an array of Datapoints to display in a chart + +export const filterByTimeRange = ( + data: Datapoint[], + monthsAgo: DataVizTimeRange +) => { + if (monthsAgo === 0) { + return data; + } + const earliestDate = new Date(); + earliestDate.setMonth(earliestDate.getMonth() - monthsAgo); + earliestDate.setHours( + earliestDate.getHours() - earliestDate.getTimezoneOffset() / 60 + ); // account for timezone offset since datapoint dates are in UTC+0 time. + return data.filter((dp) => { + return new Date(dp.start_date) >= earliestDate; + }); +}; + +export const transformToRelativePerchanges = (data: Datapoint[]) => { + return data.map((datapoint) => { + const dimensions = getDatapointDimensions(datapoint); + const sumOfDimensions = getSumOfDimensionValues(datapoint); + const dimensionsPercentage = mapValues(dimensions, (val, key) => { + if (typeof val === "number" && val !== 0) { + return val / sumOfDimensions; + } + return val; + }); + return { + ...datapoint, + ...dimensionsPercentage, + }; + }); +}; + +export const filterNullDatapoints = (data: Datapoint[]) => { + return data.filter((datapoint) => { + const dimensions = getDatapointDimensions(datapoint); + let hasReportedValues = false; + Object.values(dimensions).every((dimValue) => { + if (dimValue !== null) { + hasReportedValues = true; + return false; + } + return true; + }); + return hasReportedValues; + }); +}; + +/** + * A gap datapoint represents a time range with no reported data + * and is formatted by setting all dimension values to 0 + * and setting the value of "dataVizMissingData" to ~1/3 the height of the bar on the chart. + * + * This method generates gap datapoints between datapoints up to a certain number of months ago. + */ +export const fillTimeGapsBetweenDatapoints = ( + data: Datapoint[], + monthsAgo: number +) => { + if (data.length === 0) { + return data; + } + + const isAnnual = data[0].frequency === "ANNUAL"; + const increment = isAnnual ? incrementYear : incrementMonth; + const defaultBarValue = getHighestTotalValue(data) / 3; + const dataWithGapDatapoints = [...data]; + // create the map of dimensions with zero values + const dimensionsMap = mapValues(getDatapointDimensions(data[0]), (_) => 0); + + // loop through all the datapoints + let totalOffset = 0; // whenever we insert a gap datapoint into `dataWithGapDatapoints`, increment the totalOffset + let lastDate = new Date(); + if (isAnnual) { + lastDate.setFullYear(lastDate.getFullYear() - monthsAgo / 12); + } else { + lastDate.setMonth(lastDate.getMonth() - monthsAgo); + } + lastDate = createGMTDate( + 1, + isAnnual ? 0 : lastDate.getMonth(), + lastDate.getFullYear() + ); + for (let i = 0; i < data.length; i += 1) { + const currentDate = new Date(data[i].start_date); + const timeInterval = + data[0].frequency === "MONTHLY" + ? thirtyOneDaysInSeconds + : threeHundredSixtySixDaysInSeconds; + // this while loop can insert multiple gap datapoints between datapoints + // so must increment this offset to maintain correct insert order + let offset = 0; + while (currentDate.getTime() - lastDate.getTime() > timeInterval) { + lastDate = increment(lastDate); + dataWithGapDatapoints.splice(i + offset + totalOffset, 0, { + start_date: lastDate.toUTCString(), + end_date: increment(lastDate).toUTCString(), + dataVizMissingData: defaultBarValue, + frequency: data[0].frequency, + ...dimensionsMap, + }); + offset += 1; + } + totalOffset += offset; + lastDate = currentDate; + } + + return dataWithGapDatapoints; +}; + +export const transformData = ( + d: Datapoint[], + monthsAgo: DataVizTimeRange, + datapointsViewSetting: DatapointsViewSetting +) => { + let transformedData = [...d]; + + if (transformedData.length === 0) { + return transformedData; + } + + // filter by time range + transformedData = filterByTimeRange(transformedData, monthsAgo); + + transformedData = filterNullDatapoints(transformedData); + + // format data into percentages for percentage view + if (datapointsViewSetting === "Percentage") { + transformedData = transformToRelativePerchanges(transformedData); + } + + return fillTimeGapsBetweenDatapoints(transformedData, monthsAgo); +}; + +// get insights from data + +export const getPercentChangeOverTime = (data: Datapoint[]) => { + if (data.length > 0) { + const start = data[0][DataVizAggregateName] as number | undefined; + const end = data[data.length - 1][DataVizAggregateName] as + | number + | undefined; + if (start !== undefined && end !== undefined) { + const formattedPercentChange = formatNumberInput( + Math.round(((end - start) / start) * 100).toString() + ); + if (formattedPercentChange) { + return `${formattedPercentChange}%`; + } + } + } + return "N/A"; +}; + +export const getAverageTotalValue = (data: Datapoint[], isAnnual: boolean) => { + if (data.length > 0) { + let totalValueFound = false; + const avgTotalValue = + data.reduce((res, dp) => { + if (dp[DataVizAggregateName] !== undefined) { + totalValueFound = true; + return res + (dp[DataVizAggregateName] as number); + } + return res; + }, 0) / data.length; + if (totalValueFound && avgTotalValue !== undefined) { + const formattedAvgTotalValue = formatNumberInput( + Math.round(avgTotalValue).toString() + ); + if (formattedAvgTotalValue !== undefined) { + return `${formattedAvgTotalValue}/${isAnnual ? "yr" : "mo"}`; + } + } + } + return "N/A"; +}; + +export const getLatestDateFormatted = ( + data: Datapoint[], + isAnnual: boolean +) => { + const mostRecentDate = data[data.length - 1]?.start_date; + if (mostRecentDate) { + const [, , month, year] = splitUtcString(mostRecentDate); + return `${!isAnnual ? `${month} ` : ""}${year}`; + } + return "N/A"; +}; + +export const formatDateShort = (dateStr: string) => { + const [, , month, year] = splitUtcString(dateStr); + return `${abbreviatedMonths.indexOf(month) + 1}/${year}`; +}; diff --git a/publisher/src/components/GlobalStyles/Palette.ts b/common/components/GlobalStyles/Palette.ts similarity index 100% rename from publisher/src/components/GlobalStyles/Palette.ts rename to common/components/GlobalStyles/Palette.ts diff --git a/publisher/src/components/GlobalStyles/Typography.ts b/common/components/GlobalStyles/Typography.ts similarity index 100% rename from publisher/src/components/GlobalStyles/Typography.ts rename to common/components/GlobalStyles/Typography.ts diff --git a/publisher/src/components/GlobalStyles/constants.ts b/common/components/GlobalStyles/constants.ts similarity index 100% rename from publisher/src/components/GlobalStyles/constants.ts rename to common/components/GlobalStyles/constants.ts diff --git a/publisher/src/components/GlobalStyles/index.ts b/common/components/GlobalStyles/index.ts similarity index 100% rename from publisher/src/components/GlobalStyles/index.ts rename to common/components/GlobalStyles/index.ts diff --git a/publisher/src/shared/types.ts b/common/types.ts similarity index 100% rename from publisher/src/shared/types.ts rename to common/types.ts diff --git a/common/utils/conversionUtils.ts b/common/utils/conversionUtils.ts new file mode 100644 index 000000000..73ec81d43 --- /dev/null +++ b/common/utils/conversionUtils.ts @@ -0,0 +1,29 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +/** + * Converts pixel to rem based on a root `font-size` of 16px. + * + * @param px - pixel value as "24px" + * @param root (optional) - change conversion from a default root `font-size` of 16px + * @returns rem value as string + */ + +export const rem = (px: string, root?: number) => { + const pxAsNumber = Number(px.replace("px", "")); + return `${pxAsNumber / (root || 16)}rem`; +}; diff --git a/common/utils/dateUtils.test.ts b/common/utils/dateUtils.test.ts new file mode 100644 index 000000000..f5b962be8 --- /dev/null +++ b/common/utils/dateUtils.test.ts @@ -0,0 +1,134 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { + printDateRangeFromMonthYear, + printElapsedDaysMonthsYearsSinceDate, +} from "./dateUtils"; + +describe("printDateRangeFromMonthYear", () => { + test("monthly", () => { + const result1 = printDateRangeFromMonthYear(1, 2022); + const result2 = printDateRangeFromMonthYear(2, 2022); + const result3 = printDateRangeFromMonthYear(3, 2022); + const result4 = printDateRangeFromMonthYear(4, 2022); + const result5 = printDateRangeFromMonthYear(5, 2022); + const result6 = printDateRangeFromMonthYear(6, 2022); + const result7 = printDateRangeFromMonthYear(7, 2022); + const result8 = printDateRangeFromMonthYear(8, 2022); + const result9 = printDateRangeFromMonthYear(9, 2022); + const result10 = printDateRangeFromMonthYear(10, 2022); + const result11 = printDateRangeFromMonthYear(11, 2022); + const result12 = printDateRangeFromMonthYear(12, 2022); + const result13 = printDateRangeFromMonthYear(1, 2020); + const result14 = printDateRangeFromMonthYear(12, 2020); + expect(result1).toEqual("January 1, 2022 - January 31, 2022"); + expect(result2).toEqual("February 1, 2022 - February 28, 2022"); + expect(result3).toEqual("March 1, 2022 - March 31, 2022"); + expect(result4).toEqual("April 1, 2022 - April 30, 2022"); + expect(result5).toEqual("May 1, 2022 - May 31, 2022"); + expect(result6).toEqual("June 1, 2022 - June 30, 2022"); + expect(result7).toEqual("July 1, 2022 - July 31, 2022"); + expect(result8).toEqual("August 1, 2022 - August 31, 2022"); + expect(result9).toEqual("September 1, 2022 - September 30, 2022"); + expect(result10).toEqual("October 1, 2022 - October 31, 2022"); + expect(result11).toEqual("November 1, 2022 - November 30, 2022"); + expect(result12).toEqual("December 1, 2022 - December 31, 2022"); + expect(result13).toEqual("January 1, 2020 - January 31, 2020"); + expect(result14).toEqual("December 1, 2020 - December 31, 2020"); + }); + + test("annual", () => { + const result1 = printDateRangeFromMonthYear(1, 2022, "ANNUAL"); + const result2 = printDateRangeFromMonthYear(7, 2022, "ANNUAL"); + expect(result1).toEqual("January 1, 2022 - December 31, 2022"); + expect(result2).toEqual("July 1, 2022 - June 30, 2023"); + }); +}); + +describe("printElapsedDaysMonthsYearsSinceDate", () => { + const dayAsMilliseconds = 86400000; + const zeroDaysLapsed = new Date( + Date.now() - dayAsMilliseconds * 0 + ).toString(); + const oneDayLapsed = new Date(Date.now() - dayAsMilliseconds * 1).toString(); + const twoDaysLapsed = new Date(Date.now() - dayAsMilliseconds * 2).toString(); + const fifteenDaysLapsed = new Date( + Date.now() - dayAsMilliseconds * 15 + ).toString(); + const fourtyDaysLapsed = new Date( + Date.now() - dayAsMilliseconds * 40 + ).toString(); + const sixtyDaysLapsed = new Date( + Date.now() - dayAsMilliseconds * 60 + ).toString(); + const hundredDaysLapsed = new Date( + Date.now() - dayAsMilliseconds * 100 + ).toString(); + const fiveHundredDaysLapsed = new Date( + Date.now() - dayAsMilliseconds * 500 + ).toString(); + const nineHundredDaysLapsed = new Date( + Date.now() - dayAsMilliseconds * 900 + ).toString(); + + test("0 days ago prints today", () => { + const zeroDaysLapsedText = + printElapsedDaysMonthsYearsSinceDate(zeroDaysLapsed); + const nonZeroDaysLapsedText = + printElapsedDaysMonthsYearsSinceDate(fifteenDaysLapsed); + expect(zeroDaysLapsedText).toEqual("today"); + expect(nonZeroDaysLapsedText).not.toEqual("today"); + }); + + test("1 day ago prints yesterday", () => { + const oneDayLapsedText = printElapsedDaysMonthsYearsSinceDate(oneDayLapsed); + expect(oneDayLapsedText).toEqual("yesterday"); + }); + + test("less than 31 days ago prints number of days lapsed", () => { + const twoDaysLapsedText = + printElapsedDaysMonthsYearsSinceDate(twoDaysLapsed); + const fifteenDaysLapsedText = + printElapsedDaysMonthsYearsSinceDate(fifteenDaysLapsed); + expect(twoDaysLapsedText).toEqual("2 days ago"); + expect(fifteenDaysLapsedText).toEqual("15 days ago"); + }); + + test("more than 30 days prints number of months lapsed", () => { + const fourtyDaysLapsedText = + printElapsedDaysMonthsYearsSinceDate(fourtyDaysLapsed); + const sixtyDaysLapsedText = + printElapsedDaysMonthsYearsSinceDate(sixtyDaysLapsed); + const hundredDaysLapsedText = + printElapsedDaysMonthsYearsSinceDate(hundredDaysLapsed); + expect(fourtyDaysLapsedText).toEqual("a month ago"); + expect(sixtyDaysLapsedText).toEqual("2 months ago"); + expect(hundredDaysLapsedText).toEqual("3 months ago"); + }); + + test("more than 365 days prints number of years lapsed", () => { + const fiveHundredDaysLapsedText = printElapsedDaysMonthsYearsSinceDate( + fiveHundredDaysLapsed + ); + const nineHundredDaysLapsedText = printElapsedDaysMonthsYearsSinceDate( + nineHundredDaysLapsed + ); + expect(fiveHundredDaysLapsedText).toEqual("a year ago"); + expect(nineHundredDaysLapsedText).toEqual("2 years ago"); + }); +}); diff --git a/common/utils/dateUtils.ts b/common/utils/dateUtils.ts new file mode 100644 index 000000000..546a9c2cf --- /dev/null +++ b/common/utils/dateUtils.ts @@ -0,0 +1,133 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { ReportFrequency } from "../types"; + +export const monthsByName = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +]; + +/** + * @returns the month and year as a string + * @example "March 2022" + */ +export const printDateAsMonthYear = (month: number, year: number): string => { + return new Intl.DateTimeFormat("en-US", { + month: "long", + year: "numeric", + }).format(Date.UTC(year, month, -15)); +}; + +/** + * @returns either "Annual Report [YEAR]" or "[MONTH] [YEAR]" as a string depending on frequency + * @example "Annual Report 2022" or "March 2022" + */ +export const printReportTitle = ( + month: number, + year: number, + frequency: ReportFrequency +): string => { + if (frequency === "ANNUAL") { + return `Annual Report ${year}`; + } + + return printDateAsMonthYear(month, year); +}; + +/** + * @returns elapsed number of days since a provided date as a string + * @example 'today', 'yesterday', '2 days ago', '3 months ago', '5 years ago' + */ +export const printElapsedDaysMonthsYearsSinceDate = (date: string): string => { + const now = +new Date(Date.now()); + const stringDateToNumber = +new Date(date); + const daysLapsed = Math.floor( + (now - stringDateToNumber) / (1000 * 60 * 60 * 24) + ); + + if (daysLapsed === 0) { + return `today`; + } + + if (daysLapsed === 1) { + return `yesterday`; + } + + if (daysLapsed < 31) { + return `${daysLapsed !== 1 ? daysLapsed : "a"} day${ + daysLapsed !== 1 ? "s" : "" + } ago`; + } + + if (daysLapsed > 30 && daysLapsed < 365) { + const monthsLapsed = Math.floor(daysLapsed / 30); + return `${monthsLapsed !== 1 ? monthsLapsed : "a"} month${ + monthsLapsed !== 1 ? "s" : "" + } ago`; + } + + if (daysLapsed >= 365) { + const yearsLapsed = Math.floor(daysLapsed / 365); + return `${yearsLapsed !== 1 ? yearsLapsed : "a"} year${ + yearsLapsed !== 1 ? "s" : "" + } ago`; + } + + return ""; +}; + +/** + * Prints a human-readable date range of the provided month based on month and year + * @returns date range of the month as a string + * @example printDateRangeFromMonthYear(12, 2022) returns 'December 1, 2022 - December 31, 2022' + */ +export const printDateRangeFromMonthYear = ( + month: number, + year: number, + frequency: ReportFrequency = "MONTHLY" +): string => { + /** + * Note: backend sends true month number, whereas JavaScript's Date API deals with zero-indexed month numbers + * The below method of calculating the last day (number) of a given month relies on getting the 0th day of the following month. + * Simply providing the true month number value (from `month` param) does the + 1 (following month) calculation for us. + */ + + if (frequency === "MONTHLY") { + const lastDayOfMonth = new Date(year, month, 0)?.getDate(); + const currentMonth = monthsByName[month - 1]; + return `${currentMonth} 1, ${year} - ${currentMonth} ${lastDayOfMonth}, ${year}`; + } + + const currentMonth = monthsByName[month - 1]; + const prevMonthNumber = month === 1 ? 12 : month - 1; + const prevMonth = monthsByName[prevMonthNumber - 1]; + const lastDayOfPrevMonth = new Date(year, prevMonthNumber, 0)?.getDate(); + return `${currentMonth} 1, ${year} - ${prevMonth} ${lastDayOfPrevMonth}, ${ + month === 1 ? year : year + 1 + }`; +}; diff --git a/common/utils/helperUtils.test.ts b/common/utils/helperUtils.test.ts new file mode 100644 index 000000000..c07454fa6 --- /dev/null +++ b/common/utils/helperUtils.test.ts @@ -0,0 +1,127 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { + formatNumberInput, + isPositiveNumber, + normalizeToString, + sanitizeInputValue, +} from "./helperUtils"; + +describe("sanitizeInputValue", () => { + test("return previous value if input value is undefined", () => { + const undefinedInput = sanitizeInputValue(undefined, 2); + const definedInput = sanitizeInputValue("1", 2); + + expect(undefinedInput).toBe(2); + expect(definedInput).toBe(1); + }); + + test("return null if empty string input", () => { + const emptyStringInput = sanitizeInputValue("", 2); + const nonEmptyStringInput = sanitizeInputValue("text", 2); + + expect(emptyStringInput).toBeNull(); + expect(nonEmptyStringInput).not.toBeNull(); + }); + + test("return the number zero for string 0 and 0.00 with decimals", () => { + const zeroString = sanitizeInputValue("0", null); + const zeroDecimal = sanitizeInputValue("0.00", null); + + expect(zeroString).toBe(0); + expect(zeroDecimal).toBe(0); + }); + + test("return value converted to number if convertible", () => { + const numberString = sanitizeInputValue("123", null); + const numberStringWithDecimals = sanitizeInputValue("123.2341", null); + const numberStringWithDecimalsAfterZero = sanitizeInputValue( + "0.12341", + null + ); + + expect(numberString).toBe(123); + expect(numberStringWithDecimals).toBe(123.2341); + expect(numberStringWithDecimalsAfterZero).toBe(0.12341); + }); + + test("return value as string if not convertible to number", () => { + const nonNumber = sanitizeInputValue("0.123abc", null); + expect(typeof nonNumber).toBe("string"); + }); +}); + +describe("normalizeToString", () => { + test("return string version of value", () => { + const undefinedInput = normalizeToString(undefined); + const nullInput = normalizeToString(null); + const booleanInput = normalizeToString(false); + const numberInput = normalizeToString(22); + const stringInput = normalizeToString("Hello"); + + expect(undefinedInput).toBe(""); + expect(nullInput).toBe(""); + expect(booleanInput).toBe("false"); + expect(numberInput).toBe("22"); + expect(stringInput).toBe("Hello"); + }); +}); + +describe("formatNumberInput", () => { + test("return formatted number with commas and decimals", () => { + const inputWithCommasSpaces = formatNumberInput( + " 1231223,23,23,3,3.123123123 11 " + ); + const inputWithSeriesOfNumbers = formatNumberInput("123122323233312"); + + expect(inputWithCommasSpaces).toBe("1,231,223,232,333.12312312311"); + expect(inputWithSeriesOfNumbers).toBe("123,122,323,233,312"); + }); + + test("return formatted number on first decimal instance", () => { + const inputWithDecimalAtEnd = formatNumberInput("2,32,3,23,2."); + expect(inputWithDecimalAtEnd).toBe("2,323,232."); + }); + + test("return input value if not valid", () => { + const invalidInput = formatNumberInput("12xyz!"); + expect(invalidInput).toBe("12xyz!"); + }); +}); + +describe("isPositiveNumber", () => { + test("valid positive numbers return true", () => { + expect(isPositiveNumber("1")).toBe(true); + expect(isPositiveNumber("12")).toBe(true); + expect(isPositiveNumber("13")).toBe(true); + expect(isPositiveNumber("3.4")).toBe(true); + expect(isPositiveNumber("0")).toBe(true); + }); + test("negative numbers return false", () => { + expect(isPositiveNumber("-1")).toBe(false); + expect(isPositiveNumber("-5")).toBe(false); + expect(isPositiveNumber("-5.6")).toBe(false); + }); + test("invalid numbers return false", () => { + expect(isPositiveNumber("-1 ")).toBe(false); + expect(isPositiveNumber("0.0.0")).toBe(false); + expect(isPositiveNumber("five")).toBe(false); + expect(isPositiveNumber(" ")).toBe(false); + expect(isPositiveNumber("")).toBe(false); + }); +}); diff --git a/common/utils/helperUtils.ts b/common/utils/helperUtils.ts new file mode 100644 index 000000000..65f373ec7 --- /dev/null +++ b/common/utils/helperUtils.ts @@ -0,0 +1,225 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { debounce, memoize } from "lodash"; + +import { MetricContext } from "../types"; + +export const isPositiveNumber = (value: string | number) => { + if (typeof value === "string") { + return (value.trim() !== "" && Number(value) === 0) || Number(value) > 0; + } + return value >= 0; +}; + +/** + * Separate multiple people on a list by comma - no comma for the last person on the list + * @example ['Editor 1', 'Editor 2', 'Editor 3'] would print: `Editor 1, Editor 2, Editor 3` + */ +export const printCommaSeparatedList = (list: string[]): string => { + const string = list.map((item, i) => + i < list.length - 1 ? `${item}, ` : `${item}` + ); + return string.join(" "); +}; + +/** + * Take a string, trim and remove all spacing, and lowercase it. + * @example normalizeString("All Reports ") will be "allreports" + */ +export const normalizeString = (string: string): string => { + return string.split(" ").join("").toLowerCase().trim(); +}; + +/** + * Take a string, replace _ with ' ' space. + * @example "NOT_STARTED" becomes "NOT STARTED" + */ +export const removeSnakeCase = (string: string): string => { + return string.split("_").join(" "); +}; + +/** + * Concatenate two string keys by an `_` underscore (default) or a specified separator string + * @returns a single concatenated string + * @examples + * combineTwoKeyNames("KEY1", "KEY2") will return "KEY1_KEY2" + * combineTwoKeyNames("KEY1", "KEY2", "-") will return "KEY1-KEY2" + */ +export const combineTwoKeyNames = ( + key1: string, + key2: string, + separator?: string +) => { + return `${key1}${separator || "_"}${key2}`; +}; + +/** + * Remove commas, spaces and trim string + * + * @returns a trimmed string free from spaces and commas + * @example " 1,000,00 0 " becomes "1000000" + */ + +export const removeCommaSpaceAndTrim = (string: string) => { + return string?.replaceAll(",", "").replaceAll(" ", "").trim(); +}; + +/** + * Formats string version of numbers into string format with thousands separator + * + * @returns a string representation of a number with commas + * @example " 1231223,23,23,3,3.123123123 11 " " becomes "1,231,223,232,333.12312312311" + */ + +export const formatNumberInput = ( + value: string | undefined +): string | undefined => { + if (value === undefined) { + return undefined; + } + + const maxNumber = 999_999_999_999_999; // 1 quadrillion + const cleanValue = removeCommaSpaceAndTrim(value); + const splitValues = cleanValue.split("."); + + if (Number(cleanValue) > maxNumber) { + return Number(cleanValue.slice(0, 15)).toLocaleString(); + } + + if (splitValues && splitValues.length === 2) { + if (cleanValue[cleanValue.length - 1] === ".") { + return Number(splitValues[0]) !== 0 && Number(splitValues[0]) + ? `${Number(splitValues[0]).toLocaleString()}.` + : value; + } + + if (cleanValue.includes(".")) { + const [wholeNumber, decimal] = cleanValue.split("."); + return Number(wholeNumber) + ? `${Number(wholeNumber).toLocaleString()}.${decimal}` + : value; + } + } + return Number(cleanValue) ? Number(cleanValue).toLocaleString() : value; +}; + +/** + * Sanitize by formatting and converting string input to appropriate value for backend. + * + * @param value input value + * @param previousValue previously saved value retrieved from the backend + * @returns + * * `previousValue` from the backend if `value` is undefined + * * `null` for empty string + * * number `0` for true zeros ("0", "0.000", etc.) + * * `value` converted to number + * * `value` itself (if it is not a number) or if the type is "TEXT" + */ + +export const sanitizeInputValue = ( + value: string | undefined, + previousValue: string | number | boolean | null | undefined, + type?: MetricContext["type"] +): string | number | boolean | null | undefined => { + if (value === undefined) { + return previousValue; + } + const cleanValue = removeCommaSpaceAndTrim(value); + if (cleanValue === "") { + return null; + } + if (type === "TEXT") { + return value; + } + if (Number(cleanValue) === 0) { + return 0; + } + return Number(cleanValue) || value; +}; + +/** + * Converts string | number | boolean | null | undefined into string equivalents that conforms to a text input + * + * @returns a string, "" empty string to represent null and undefined, stringified version of number and boolean + */ +export const normalizeToString = ( + value: string | number | boolean | null | undefined +): string => { + const stringValue = value?.toString(); + return !stringValue ? "" : stringValue; +}; + +/** + * Group a list of objects based on property value + * @param arr list of objects + * @param key name of the property on which to perform the grouping + * @returns dictionary of property value to list of objects with that value + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const groupBy = (arr: T[], key: (i: T) => K) => { + const result = {} as Record; + arr.forEach((item) => { + if (!result[key(item)]) { + result[key(item)] = []; + } + result[key(item)].push(item); + }); + return result; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export interface MemoizeDebouncedFunction any> { + (...args: Parameters): void; + flush: (...args: Parameters) => void; +} + +/** + * This method should be used instead of the standard `debounce` if we want to + * debounce *only* if the arguments to the function are the same. + * For instance, consider a function `click(param: str)`. With standard debounce, + * calling `click('foo')` and `click('bar')` in quick succession will only result + * in the execution of `click('bar')`. However, using memoized debounce, both + * functions will execute, because their parameters are different. + * Taken from https://github.com/lodash/lodash/issues/2403#issuecomment-816137402 + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function memoizeDebounce any>( + func: F, + wait = 0, + options: _.DebounceSettings = {}, + resolver?: (...args: Parameters) => unknown +): MemoizeDebouncedFunction { + const debounceMemo = memoize<(...args: Parameters) => _.DebouncedFunc>( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + (..._args: Parameters) => debounce(func, wait, options), + resolver + ); + + function wrappedFunction( + this: MemoizeDebouncedFunction, + ...args: Parameters + ): ReturnType | undefined { + return debounceMemo(...args)(...args); + } + + wrappedFunction.flush = (...args: Parameters): void => { + debounceMemo(...args).flush(); + }; + + return wrappedFunction as unknown as MemoizeDebouncedFunction; +} diff --git a/common/utils/index copy.ts b/common/utils/index copy.ts new file mode 100644 index 000000000..3d8962b73 --- /dev/null +++ b/common/utils/index copy.ts @@ -0,0 +1,20 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export * from "./conversionUtils"; +export * from "./dateUtils"; +export * from "./helperUtils"; diff --git a/common/utils/index.ts b/common/utils/index.ts new file mode 100644 index 000000000..3d8962b73 --- /dev/null +++ b/common/utils/index.ts @@ -0,0 +1,20 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export * from "./conversionUtils"; +export * from "./dateUtils"; +export * from "./helperUtils"; diff --git a/publisher/package.json b/publisher/package.json index 0aeac5d84..f6bf34732 100644 --- a/publisher/package.json +++ b/publisher/package.json @@ -31,7 +31,7 @@ "web-vitals": "^2.1.0" }, "scripts": { - "start": "react-app-rewired start", + "dev": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-scripts eject", diff --git a/publisher/src/analytics.ts b/publisher/src/analytics.ts index 765a17233..e3210a234 100644 --- a/publisher/src/analytics.ts +++ b/publisher/src/analytics.ts @@ -15,7 +15,7 @@ // along with this program. If not, see . // ============================================================================= -import { UpdatedMetricsValues, UserAgency } from "./shared/types"; +import { UpdatedMetricsValues, UserAgency } from "@justice-counts/common/types"; const TEST_SENDING_ANALYTICS = false; // used for testing sending analytics in development const LOG_ANALYTICS = false; // used for logging analytics being sent diff --git a/publisher/src/components/Auth/VerificationPage.tsx b/publisher/src/components/Auth/VerificationPage.tsx index 335b30136..04a67ca86 100644 --- a/publisher/src/components/Auth/VerificationPage.tsx +++ b/publisher/src/components/Auth/VerificationPage.tsx @@ -15,12 +15,15 @@ // along with this program. If not, see . // ============================================================================= +import { + palette, + typography, +} from "@justice-counts/common/components/GlobalStyles"; import React from "react"; import styled from "styled-components/macro"; import { useStore } from "../../stores/StoreProvider"; import logo from "../assets/jc-logo-green-vector.png"; -import { palette, typography } from "../GlobalStyles"; export const PageContainer = styled.div` display: flex; diff --git a/publisher/src/components/Badge/Badge.tsx b/publisher/src/components/Badge/Badge.tsx index 0ecc41b1c..3f05c4541 100644 --- a/publisher/src/components/Badge/Badge.tsx +++ b/publisher/src/components/Badge/Badge.tsx @@ -14,10 +14,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // ============================================================================= +import { palette } from "@justice-counts/common/components/GlobalStyles"; import React from "react"; import styled from "styled-components/macro"; -import { palette } from "../GlobalStyles"; import { MiniLoader } from "../Loading/MiniLoader"; export type BadgeColors = "RED" | "GREEN" | "ORANGE" | "GREY"; diff --git a/publisher/src/components/DataUpload/DataUpload.styles.tsx b/publisher/src/components/DataUpload/DataUpload.styles.tsx index 9a5b44aec..7560f1afd 100644 --- a/publisher/src/components/DataUpload/DataUpload.styles.tsx +++ b/publisher/src/components/DataUpload/DataUpload.styles.tsx @@ -15,11 +15,15 @@ // along with this program. If not, see . // ============================================================================= +import { + HEADER_BAR_HEIGHT, + palette, + typography, +} from "@justice-counts/common/components/GlobalStyles"; import styled from "styled-components/macro"; import { rem } from "../../utils"; import { OpacityGradient } from "../Forms"; -import { HEADER_BAR_HEIGHT, palette, typography } from "../GlobalStyles"; import { Cell, LabelCell, diff --git a/publisher/src/components/DataUpload/DataUpload.tsx b/publisher/src/components/DataUpload/DataUpload.tsx index 7af310f04..f33d25d8c 100644 --- a/publisher/src/components/DataUpload/DataUpload.tsx +++ b/publisher/src/components/DataUpload/DataUpload.tsx @@ -15,11 +15,11 @@ // along with this program. If not, see . // ============================================================================= +import { AgencySystems } from "@justice-counts/common/types"; import { observer } from "mobx-react-lite"; import React, { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; -import { AgencySystems } from "../../shared/types"; import { useStore } from "../../stores"; import logoImg from "../assets/jc-logo-vector.png"; import { Logo, LogoContainer } from "../Header"; diff --git a/publisher/src/components/DataUpload/SystemSelection.tsx b/publisher/src/components/DataUpload/SystemSelection.tsx index 8d50869e3..9431d8bdf 100644 --- a/publisher/src/components/DataUpload/SystemSelection.tsx +++ b/publisher/src/components/DataUpload/SystemSelection.tsx @@ -15,9 +15,9 @@ // along with this program. If not, see . // ============================================================================= +import { AgencySystems } from "@justice-counts/common/types"; import React from "react"; -import { AgencySystems } from "../../shared/types"; import { removeSnakeCase } from "../../utils"; import { ReactComponent as CheckIcon } from "../assets/check-icon.svg"; import { diff --git a/publisher/src/components/DataUpload/UploadFile.tsx b/publisher/src/components/DataUpload/UploadFile.tsx index ba2c67a54..d43d6d3ca 100644 --- a/publisher/src/components/DataUpload/UploadFile.tsx +++ b/publisher/src/components/DataUpload/UploadFile.tsx @@ -15,9 +15,9 @@ // along with this program. If not, see . // ============================================================================= +import { AgencySystems } from "@justice-counts/common/types"; import React, { Fragment, useEffect, useRef, useState } from "react"; -import { AgencySystems } from "../../shared/types"; import { removeSnakeCase } from "../../utils"; import { ReactComponent as FileIcon } from "../assets/file-icon.svg"; import { showToast } from "../Toast"; diff --git a/publisher/src/components/DataUpload/UploadedFiles.tsx b/publisher/src/components/DataUpload/UploadedFiles.tsx index bdec22ee6..131185c04 100644 --- a/publisher/src/components/DataUpload/UploadedFiles.tsx +++ b/publisher/src/components/DataUpload/UploadedFiles.tsx @@ -15,11 +15,11 @@ // along with this program. If not, see . // ============================================================================= +import { Permission } from "@justice-counts/common/types"; import { when } from "mobx"; import { observer } from "mobx-react-lite"; import React, { useEffect, useState } from "react"; -import { Permission } from "../../shared/types"; import { useStore } from "../../stores"; import { removeSnakeCase } from "../../utils"; import downloadIcon from "../assets/download-icon.png"; diff --git a/publisher/src/components/DataUpload/types.ts b/publisher/src/components/DataUpload/types.ts index f4b44b668..4eb532665 100644 --- a/publisher/src/components/DataUpload/types.ts +++ b/publisher/src/components/DataUpload/types.ts @@ -15,7 +15,7 @@ // along with this program. If not, see . // ============================================================================= -import { RawDatapoint } from "../../shared/types"; +import { RawDatapoint } from "@justice-counts/common/types"; export interface DataUploadResponseBody { metrics: UploadedMetric[]; diff --git a/publisher/src/components/DataViz/DatapointsView.styles.tsx b/publisher/src/components/DataViz/DatapointsView.styles.tsx index 8c3fd8d9c..8c601c259 100644 --- a/publisher/src/components/DataViz/DatapointsView.styles.tsx +++ b/publisher/src/components/DataViz/DatapointsView.styles.tsx @@ -15,6 +15,10 @@ // along with this program. If not, see . // ============================================================================= +import { + palette, + typography, +} from "@justice-counts/common/components/GlobalStyles"; import { Dropdown, DropdownMenu, @@ -23,7 +27,6 @@ import { import React from "react"; import styled from "styled-components/macro"; -import { palette, typography } from "../GlobalStyles"; import { ExtendedDropdownMenuItem } from "../Menu/Menu.styles"; export const DatapointsViewContainer = styled.div` diff --git a/publisher/src/components/DataViz/DatapointsView.tsx b/publisher/src/components/DataViz/DatapointsView.tsx index d878a7456..f8a7a55c9 100644 --- a/publisher/src/components/DataViz/DatapointsView.tsx +++ b/publisher/src/components/DataViz/DatapointsView.tsx @@ -15,17 +15,18 @@ // along with this program. If not, see . // ============================================================================= -import { observer } from "mobx-react-lite"; -import React, { useEffect } from "react"; - +import BarChart from "@justice-counts/common/components/DataViz/BarChart"; +import Legend from "@justice-counts/common/components/DataViz/Legend"; import { DatapointsGroupedByAggregateAndDisaggregations, DatapointsViewSetting, DataVizAggregateName, DataVizTimeRangesMap, -} from "../../shared/types"; +} from "@justice-counts/common/types"; +import { observer } from "mobx-react-lite"; +import React, { useEffect } from "react"; + import { useStore } from "../../stores"; -import BarChart from "./BarChart"; import { DatapointsViewContainer, DatapointsViewControlsContainer, @@ -33,7 +34,6 @@ import { MetricInsight, MetricInsightsRow, } from "./DatapointsView.styles"; -import Legend from "./Legend"; import { filterByTimeRange, filterNullDatapoints, diff --git a/publisher/src/components/DataViz/utils.test.ts b/publisher/src/components/DataViz/utils.test.ts index 38481aa17..ceb0f8758 100644 --- a/publisher/src/components/DataViz/utils.test.ts +++ b/publisher/src/components/DataViz/utils.test.ts @@ -15,7 +15,8 @@ // along with this program. If not, see . // ============================================================================= -import { Datapoint } from "../../shared/types"; +import { Datapoint } from "@justice-counts/common/types"; + import { fillTimeGapsBetweenDatapoints, filterByTimeRange, diff --git a/publisher/src/components/DataViz/utils.ts b/publisher/src/components/DataViz/utils.ts index 5999b1a1d..142c4a994 100644 --- a/publisher/src/components/DataViz/utils.ts +++ b/publisher/src/components/DataViz/utils.ts @@ -15,14 +15,14 @@ // along with this program. If not, see . // ============================================================================= -import { mapValues, pickBy } from "lodash"; - import { Datapoint, DatapointsViewSetting, DataVizAggregateName, DataVizTimeRange, -} from "../../shared/types"; +} from "@justice-counts/common/types"; +import { mapValues, pickBy } from "lodash"; + import { formatNumberInput } from "../../utils"; export const thirtyOneDaysInSeconds = 2678400000; diff --git a/publisher/src/components/Forms/BinaryRadioButton.tsx b/publisher/src/components/Forms/BinaryRadioButton.tsx index d61ad243b..2a6625446 100644 --- a/publisher/src/components/Forms/BinaryRadioButton.tsx +++ b/publisher/src/components/Forms/BinaryRadioButton.tsx @@ -15,11 +15,13 @@ // along with this program. If not, see . // ============================================================================= +import { + palette, + typography, +} from "@justice-counts/common/components/GlobalStyles"; import React, { InputHTMLAttributes } from "react"; import styled from "styled-components/macro"; -import { palette, typography } from "../GlobalStyles"; - export const BinaryRadioGroupContainer = styled.div` display: flex; flex-direction: column; diff --git a/publisher/src/components/Forms/Dropdown.tsx b/publisher/src/components/Forms/Dropdown.tsx index 5e9442e62..53ad54745 100644 --- a/publisher/src/components/Forms/Dropdown.tsx +++ b/publisher/src/components/Forms/Dropdown.tsx @@ -15,11 +15,13 @@ // along with this program. If not, see . // ============================================================================= +import { + palette, + typography, +} from "@justice-counts/common/components/GlobalStyles"; import React, { SelectHTMLAttributes } from "react"; import styled from "styled-components/macro"; -import { palette, typography } from "../GlobalStyles"; - const DropdownContainer = styled.div` position: relative; width: 100%; diff --git a/publisher/src/components/Forms/Form.styles.tsx b/publisher/src/components/Forms/Form.styles.tsx index 97c7c74e9..a50cd36ba 100644 --- a/publisher/src/components/Forms/Form.styles.tsx +++ b/publisher/src/components/Forms/Form.styles.tsx @@ -15,9 +15,13 @@ // along with this program. If not, see . // ============================================================================= +import { + HEADER_BAR_HEIGHT, + palette, + typography, +} from "@justice-counts/common/components/GlobalStyles"; import styled from "styled-components/macro"; -import { HEADER_BAR_HEIGHT, palette, typography } from "../GlobalStyles"; import { DATA_ENTRY_WIDTH, ONE_PANEL_MAX_WIDTH, diff --git a/publisher/src/components/Forms/NotReportedIcon.tsx b/publisher/src/components/Forms/NotReportedIcon.tsx index a16284ef2..fa357ed42 100644 --- a/publisher/src/components/Forms/NotReportedIcon.tsx +++ b/publisher/src/components/Forms/NotReportedIcon.tsx @@ -15,12 +15,15 @@ // along with this program. If not, see . // ============================================================================= +import { + palette, + typography, +} from "@justice-counts/common/components/GlobalStyles"; import React, { useState } from "react"; import { useNavigate } from "react-router-dom"; import styled from "styled-components/macro"; import notReportedIcon from "../assets/not-reported-icon.png"; -import { palette, typography } from "../GlobalStyles"; import { TWO_PANEL_MAX_WIDTH } from "../Reports/ReportDataEntry.styles"; export const NotReportedIconWrapper = styled.div<{ diff --git a/publisher/src/components/Forms/TabbedDisaggregations.tsx b/publisher/src/components/Forms/TabbedDisaggregations.tsx index 619941bee..e3253e015 100644 --- a/publisher/src/components/Forms/TabbedDisaggregations.tsx +++ b/publisher/src/components/Forms/TabbedDisaggregations.tsx @@ -15,9 +15,9 @@ // along with this program. If not, see . // ============================================================================= +import { Metric as MetricType } from "@justice-counts/common/types"; import React, { useEffect, useState } from "react"; -import { Metric as MetricType } from "../../shared/types"; import { useStore } from "../../stores"; import successIcon from "../assets/status-check-icon.png"; import errorIcon from "../assets/status-error-icon.png"; diff --git a/publisher/src/components/Forms/TextInput.tsx b/publisher/src/components/Forms/TextInput.tsx index c70dc4870..8fe7c6046 100644 --- a/publisher/src/components/Forms/TextInput.tsx +++ b/publisher/src/components/Forms/TextInput.tsx @@ -15,15 +15,18 @@ // along with this program. If not, see . // ============================================================================= +import { + palette, + typography, +} from "@justice-counts/common/components/GlobalStyles"; +import { FormError } from "@justice-counts/common/types"; import React, { InputHTMLAttributes, useState } from "react"; import styled from "styled-components/macro"; -import { FormError } from "../../shared/types"; import { rem } from "../../utils"; import infoRedIcon from "../assets/info-red-icon.png"; import statusCheckIcon from "../assets/status-check-icon.png"; import statusErrorIcon from "../assets/status-error-icon.png"; -import { palette, typography } from "../GlobalStyles"; import { NotReportedIcon } from "."; export const InputWrapper = styled.div` diff --git a/publisher/src/components/Header/Header.styles.tsx b/publisher/src/components/Header/Header.styles.tsx index 1cb71ad09..37cd776be 100644 --- a/publisher/src/components/Header/Header.styles.tsx +++ b/publisher/src/components/Header/Header.styles.tsx @@ -15,11 +15,12 @@ // along with this program. If not, see . // ============================================================================= +import { + HEADER_BAR_HEIGHT, + palette, +} from "@justice-counts/common/components/GlobalStyles"; import styled from "styled-components/macro"; -import { HEADER_BAR_HEIGHT } from "../GlobalStyles"; -import { palette } from "../GlobalStyles/Palette"; - export const HeaderBar = styled.header` width: 100%; height: ${HEADER_BAR_HEIGHT}px; diff --git a/publisher/src/components/Menu/Menu.styles.tsx b/publisher/src/components/Menu/Menu.styles.tsx index 7c5dd829a..c4bbd0503 100644 --- a/publisher/src/components/Menu/Menu.styles.tsx +++ b/publisher/src/components/Menu/Menu.styles.tsx @@ -14,6 +14,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // ============================================================================= +import { + HEADER_BAR_HEIGHT, + palette, + typography, +} from "@justice-counts/common/components/GlobalStyles"; import { DropdownMenu, DropdownMenuItem, @@ -21,7 +26,6 @@ import { } from "@recidiviz/design-system"; import styled from "styled-components/macro"; -import { HEADER_BAR_HEIGHT, palette, typography } from "../GlobalStyles"; import { ONE_PANEL_MAX_WIDTH } from "../Reports/ReportDataEntry.styles"; export const MenuContainer = styled.nav` diff --git a/publisher/src/components/Menu/Menu.tsx b/publisher/src/components/Menu/Menu.tsx index a4ea0ee74..b0f40a933 100644 --- a/publisher/src/components/Menu/Menu.tsx +++ b/publisher/src/components/Menu/Menu.tsx @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // ============================================================================= +import { Permission } from "@justice-counts/common/types"; import { Dropdown } from "@recidiviz/design-system"; import { observer } from "mobx-react-lite"; import React, { useEffect, useState } from "react"; import { useLocation, useNavigate } from "react-router-dom"; -import { Permission } from "../../shared/types"; import { useStore } from "../../stores"; import { Button } from "../DataUpload"; import { diff --git a/publisher/src/components/MetricsView/MetricsView.styles.tsx b/publisher/src/components/MetricsView/MetricsView.styles.tsx index dc4144466..da9286c6e 100644 --- a/publisher/src/components/MetricsView/MetricsView.styles.tsx +++ b/publisher/src/components/MetricsView/MetricsView.styles.tsx @@ -15,10 +15,13 @@ // along with this program. If not, see . // ============================================================================= +import { + palette, + typography, +} from "@justice-counts/common/components/GlobalStyles"; import styled from "styled-components/macro"; import { BinaryRadioGroupWrapper } from "../Forms"; -import { palette, typography } from "../GlobalStyles"; export const MetricsViewContainer = styled.div` width: 100%; diff --git a/publisher/src/components/MetricsView/MetricsView.tsx b/publisher/src/components/MetricsView/MetricsView.tsx index 0faad657a..a2419b3fd 100644 --- a/publisher/src/components/MetricsView/MetricsView.tsx +++ b/publisher/src/components/MetricsView/MetricsView.tsx @@ -15,12 +15,16 @@ // along with this program. If not, see . // ============================================================================= +import { + AgencySystems, + FormError, + ReportFrequency, +} from "@justice-counts/common/types"; import { debounce as _debounce } from "lodash"; import { reaction, when } from "mobx"; import { observer } from "mobx-react-lite"; import React, { useEffect, useRef, useState } from "react"; -import { AgencySystems, FormError, ReportFrequency } from "../../shared/types"; import { useStore } from "../../stores"; import { isPositiveNumber, diff --git a/publisher/src/components/Modal/Modal.tsx b/publisher/src/components/Modal/Modal.tsx index 6cbda3820..14f0bb817 100644 --- a/publisher/src/components/Modal/Modal.tsx +++ b/publisher/src/components/Modal/Modal.tsx @@ -15,11 +15,13 @@ // along with this program. If not, see . // ============================================================================= +import { + HEADER_BAR_HEIGHT, + palette, +} from "@justice-counts/common/components/GlobalStyles"; import React, { useEffect, useState } from "react"; import styled, { css, keyframes } from "styled-components/macro"; -import { HEADER_BAR_HEIGHT, palette } from "../GlobalStyles"; - const ModalContainer = styled.div` width: 100vw; height: 100vh; diff --git a/publisher/src/components/Onboarding/Onboarding.tsx b/publisher/src/components/Onboarding/Onboarding.tsx index 2fa141f18..48179b0bd 100644 --- a/publisher/src/components/Onboarding/Onboarding.tsx +++ b/publisher/src/components/Onboarding/Onboarding.tsx @@ -15,12 +15,15 @@ // along with this program. If not, see . // ============================================================================= +import { + palette, + typography, +} from "@justice-counts/common/components/GlobalStyles"; import React, { useEffect, useRef, useState } from "react"; import styled, { keyframes } from "styled-components/macro"; import { useStore } from "../../stores"; import logo from "../assets/jc-logo-vector-onboarding.png"; -import { palette, typography } from "../GlobalStyles"; import { DATA_ENTRY_WIDTH, ONE_PANEL_MAX_WIDTH, diff --git a/publisher/src/components/Onboarding/OnboardingDataEntrySummary.tsx b/publisher/src/components/Onboarding/OnboardingDataEntrySummary.tsx index 1523d44c6..d56a30fae 100644 --- a/publisher/src/components/Onboarding/OnboardingDataEntrySummary.tsx +++ b/publisher/src/components/Onboarding/OnboardingDataEntrySummary.tsx @@ -15,12 +15,15 @@ // along with this program. If not, see . // ============================================================================= +import { + palette, + typography, +} from "@justice-counts/common/components/GlobalStyles"; import React, { useEffect, useState } from "react"; import styled from "styled-components/macro"; import closeIcon from "../assets/dark-close-icon.png"; import { ReactComponent as Logo } from "../assets/jc-logo-vector.svg"; -import { palette, typography } from "../GlobalStyles"; import { ONE_PANEL_MAX_WIDTH } from "../Reports/ReportDataEntry.styles"; import { OnboardingBackdropContainer, OnboardingContainer } from "./Onboarding"; diff --git a/publisher/src/components/Reports/CreateReport.tsx b/publisher/src/components/Reports/CreateReport.tsx index e6c51d5fb..e5bb40b8f 100644 --- a/publisher/src/components/Reports/CreateReport.tsx +++ b/publisher/src/components/Reports/CreateReport.tsx @@ -15,12 +15,19 @@ // along with this program. If not, see . // ============================================================================= +import { + palette, + typography, +} from "@justice-counts/common/components/GlobalStyles"; +import { + CreateReportFormValuesType, + ReportOverview, +} from "@justice-counts/common/types"; import React, { useState } from "react"; import { useNavigate } from "react-router-dom"; import styled from "styled-components/macro"; import { trackReportCreated } from "../../analytics"; -import { CreateReportFormValuesType, ReportOverview } from "../../shared/types"; import { useStore } from "../../stores"; import { monthsByName, printDateRangeFromMonthYear } from "../../utils"; import { @@ -38,7 +45,6 @@ import { TitleWrapper, } from "../Forms"; import { Dropdown } from "../Forms/Dropdown"; -import { palette, typography } from "../GlobalStyles"; import { showToast } from "../Toast"; import { PublishButton, diff --git a/publisher/src/components/Reports/DataEntryForm.tsx b/publisher/src/components/Reports/DataEntryForm.tsx index cdca086a8..9343825b0 100644 --- a/publisher/src/components/Reports/DataEntryForm.tsx +++ b/publisher/src/components/Reports/DataEntryForm.tsx @@ -15,6 +15,11 @@ // along with this program. If not, see . // ============================================================================= +import { + HEADER_BAR_HEIGHT, + palette, + typography, +} from "@justice-counts/common/components/GlobalStyles"; import { reaction, runInAction } from "mobx"; import { observer } from "mobx-react-lite"; import React, { Fragment, useEffect, useRef, useState } from "react"; @@ -50,7 +55,6 @@ import { TabbedDisaggregations, Title, } from "../Forms"; -import { HEADER_BAR_HEIGHT, palette, typography } from "../GlobalStyles"; import { Onboarding, OnboardingDataEntrySummary } from "../Onboarding"; import { showToast } from "../Toast"; import { diff --git a/publisher/src/components/Reports/DataEntryFormComponents.tsx b/publisher/src/components/Reports/DataEntryFormComponents.tsx index c5fe4d754..566940606 100644 --- a/publisher/src/components/Reports/DataEntryFormComponents.tsx +++ b/publisher/src/components/Reports/DataEntryFormComponents.tsx @@ -15,15 +15,15 @@ // along with this program. If not, see . // ============================================================================= -import { observer } from "mobx-react-lite"; -import React from "react"; - import { Metric, MetricContext, MetricDisaggregationDimensions, MetricDisaggregations, -} from "../../shared/types"; +} from "@justice-counts/common/types"; +import { observer } from "mobx-react-lite"; +import React from "react"; + import { useStore } from "../../stores"; import { formatNumberInput } from "../../utils"; import { BinaryRadioButton, TextInput } from "../Forms"; diff --git a/publisher/src/components/Reports/HelperText.tsx b/publisher/src/components/Reports/HelperText.tsx index 8bb372b1e..aa4b11b87 100644 --- a/publisher/src/components/Reports/HelperText.tsx +++ b/publisher/src/components/Reports/HelperText.tsx @@ -15,11 +15,14 @@ // along with this program. If not, see . // ============================================================================= +import { + palette, + typography, +} from "@justice-counts/common/components/GlobalStyles"; import React from "react"; import styled from "styled-components/macro"; import { useStore } from "../../stores"; -import { palette, typography } from "../GlobalStyles"; import { BREAKPOINT_HEIGHT, TWO_PANEL_MAX_WIDTH, diff --git a/publisher/src/components/Reports/PublishConfirmation.tsx b/publisher/src/components/Reports/PublishConfirmation.tsx index de26b031b..c31a95327 100644 --- a/publisher/src/components/Reports/PublishConfirmation.tsx +++ b/publisher/src/components/Reports/PublishConfirmation.tsx @@ -15,23 +15,23 @@ // along with this program. If not, see . // ============================================================================= +import { palette } from "@justice-counts/common/components/GlobalStyles"; +import { + MetricContextWithErrors, + MetricDisaggregationDimensionsWithErrors, + MetricDisaggregationsWithErrors, + MetricWithErrors, +} from "@justice-counts/common/types"; import { observer } from "mobx-react-lite"; import React, { Fragment, useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import styled from "styled-components/macro"; import { trackReportPublished } from "../../analytics"; -import { - MetricContextWithErrors, - MetricDisaggregationDimensionsWithErrors, - MetricDisaggregationsWithErrors, - MetricWithErrors, -} from "../../shared/types"; import { useStore } from "../../stores"; import { printReportTitle, rem } from "../../utils"; import errorIcon from "../assets/status-error-icon.png"; import { Button } from "../Forms"; -import { palette } from "../GlobalStyles"; import { showToast } from "../Toast"; import { PublishButton } from "./ReportDataEntry.styles"; diff --git a/publisher/src/components/Reports/ReportDataEntry.styles.tsx b/publisher/src/components/Reports/ReportDataEntry.styles.tsx index b150e7734..d9440a8ad 100644 --- a/publisher/src/components/Reports/ReportDataEntry.styles.tsx +++ b/publisher/src/components/Reports/ReportDataEntry.styles.tsx @@ -15,11 +15,13 @@ // along with this program. If not, see . // ============================================================================= +import { + palette, + typography, +} from "@justice-counts/common/components/GlobalStyles"; import React from "react"; import styled from "styled-components/macro"; -import { palette, typography } from "../GlobalStyles"; - export const SIDE_PANEL_WIDTH = 360; export const DATA_ENTRY_WIDTH = 644; export const SIDE_PANEL_HORIZONTAL_PADDING = 24; diff --git a/publisher/src/components/Reports/ReportDataEntry.tsx b/publisher/src/components/Reports/ReportDataEntry.tsx index 207559d06..b01081395 100644 --- a/publisher/src/components/Reports/ReportDataEntry.tsx +++ b/publisher/src/components/Reports/ReportDataEntry.tsx @@ -15,13 +15,13 @@ // along with this program. If not, see . // ============================================================================= +import { Report } from "@justice-counts/common/types"; import { when } from "mobx"; import { observer } from "mobx-react-lite"; import React, { useEffect, useState } from "react"; import { useParams } from "react-router-dom"; import { trackReportUnpublished } from "../../analytics"; -import { Report } from "../../shared/types"; import { useStore } from "../../stores"; import { printReportTitle } from "../../utils"; import { PageWrapper } from "../Forms"; diff --git a/publisher/src/components/Reports/ReportSummaryPanel.tsx b/publisher/src/components/Reports/ReportSummaryPanel.tsx index 9c2b9066b..4b4d49498 100644 --- a/publisher/src/components/Reports/ReportSummaryPanel.tsx +++ b/publisher/src/components/Reports/ReportSummaryPanel.tsx @@ -15,12 +15,16 @@ // along with this program. If not, see . // ============================================================================= +import { + palette, + typography, +} from "@justice-counts/common/components/GlobalStyles"; +import { Metric } from "@justice-counts/common/types"; import { observer } from "mobx-react-lite"; import React from "react"; import { useNavigate } from "react-router-dom"; import styled from "styled-components/macro"; -import { Metric } from "../../shared/types"; import { useStore } from "../../stores"; import { printCommaSeparatedList, @@ -35,7 +39,6 @@ import { PreTitle, Title, } from "../Forms"; -import { palette, typography } from "../GlobalStyles"; import HelperText from "./HelperText"; import { BREAKPOINT_HEIGHT, diff --git a/publisher/src/components/Reports/Reports.styles.tsx b/publisher/src/components/Reports/Reports.styles.tsx index 73c1ff2d1..e2987c2ce 100644 --- a/publisher/src/components/Reports/Reports.styles.tsx +++ b/publisher/src/components/Reports/Reports.styles.tsx @@ -15,10 +15,13 @@ // along with this program. If not, see . // ============================================================================= +import { + HEADER_BAR_HEIGHT, + palette, + typography, +} from "@justice-counts/common/components/GlobalStyles"; import styled from "styled-components/macro"; -import { HEADER_BAR_HEIGHT, palette, typography } from "../GlobalStyles"; - const COLLAPSED_INNER_COLUMNS_WIDTH = 846; export const PageHeader = styled.div` diff --git a/publisher/src/components/ReviewMetrics/ReviewMetrics.styles.tsx b/publisher/src/components/ReviewMetrics/ReviewMetrics.styles.tsx index 99e5cda8e..08dcea26e 100644 --- a/publisher/src/components/ReviewMetrics/ReviewMetrics.styles.tsx +++ b/publisher/src/components/ReviewMetrics/ReviewMetrics.styles.tsx @@ -15,10 +15,14 @@ // along with this program. If not, see . // ============================================================================= +import { + HEADER_BAR_HEIGHT, + palette, + typography, +} from "@justice-counts/common/components/GlobalStyles"; import styled from "styled-components/macro"; import { DataUploadContainer } from "../DataUpload"; -import { HEADER_BAR_HEIGHT, palette, typography } from "../GlobalStyles"; export const MAIN_PANEL_MAX_WIDTH = 864; diff --git a/publisher/src/components/ReviewMetrics/ReviewMetrics.tsx b/publisher/src/components/ReviewMetrics/ReviewMetrics.tsx index 5208b1faa..ff007226e 100644 --- a/publisher/src/components/ReviewMetrics/ReviewMetrics.tsx +++ b/publisher/src/components/ReviewMetrics/ReviewMetrics.tsx @@ -15,11 +15,14 @@ // along with this program. If not, see . // ============================================================================= +import { + DataVizAggregateName, + RawDatapoint, +} from "@justice-counts/common/types"; import { observer } from "mobx-react-lite"; import React, { useEffect } from "react"; import { useLocation, useNavigate } from "react-router-dom"; -import { DataVizAggregateName, RawDatapoint } from "../../shared/types"; import logoImg from "../assets/jc-logo-vector.png"; import { Button, diff --git a/publisher/src/components/Toast/Toast.ts b/publisher/src/components/Toast/Toast.ts index 8143141fb..15fae52b5 100644 --- a/publisher/src/components/Toast/Toast.ts +++ b/publisher/src/components/Toast/Toast.ts @@ -15,8 +15,13 @@ // along with this program. If not, see . // ============================================================================= +import { + HEADER_BAR_HEIGHT, + palette, + typography, +} from "@justice-counts/common/components/GlobalStyles"; + import checkIconWhite from "../assets/status-check-white-icon.png"; -import { HEADER_BAR_HEIGHT, palette, typography } from "../GlobalStyles"; type ToastColor = "blue" | "red" | "grey"; diff --git a/publisher/src/index.tsx b/publisher/src/index.tsx index 55146997f..8b0d0c59f 100644 --- a/publisher/src/index.tsx +++ b/publisher/src/index.tsx @@ -15,6 +15,7 @@ // along with this program. If not, see . // ============================================================================= +import { palette } from "@justice-counts/common/components/GlobalStyles"; import React from "react"; import ReactDOM from "react-dom"; import { BrowserRouter } from "react-router-dom"; @@ -22,7 +23,6 @@ import { createGlobalStyle } from "styled-components/macro"; import App from "./App"; import AuthWall from "./components/Auth"; -import { palette } from "./components/GlobalStyles"; import { StoreProvider } from "./stores"; // load analytics diff --git a/publisher/src/mocks/PreviewDataObject.tsx b/publisher/src/mocks/PreviewDataObject.tsx index ab2aa1355..9bfde3f4a 100644 --- a/publisher/src/mocks/PreviewDataObject.tsx +++ b/publisher/src/mocks/PreviewDataObject.tsx @@ -15,11 +15,10 @@ // along with this program. If not, see . // ============================================================================= +import { palette } from "@justice-counts/common/components/GlobalStyles"; import React, { useState } from "react"; import styled from "styled-components/macro"; -import { palette } from "../components/GlobalStyles"; - const PreviewButton = styled.button<{ open?: boolean }>` height: 80px; width: 80px; diff --git a/publisher/src/pages/AccountSettings.tsx b/publisher/src/pages/AccountSettings.tsx index 202d58038..6525e5183 100644 --- a/publisher/src/pages/AccountSettings.tsx +++ b/publisher/src/pages/AccountSettings.tsx @@ -15,6 +15,7 @@ // along with this program. If not, see . // ============================================================================= +import { typography } from "@justice-counts/common/components/GlobalStyles"; import { debounce as _debounce } from "lodash"; import React, { useRef } from "react"; import styled from "styled-components/macro"; @@ -25,7 +26,6 @@ import { UploadedFilesWrapper, } from "../components/DataUpload"; import { TextInput, Title, TitleWrapper } from "../components/Forms"; -import { typography } from "../components/GlobalStyles"; import { useStore } from "../stores"; const SettingsContainer = styled.div` diff --git a/publisher/src/pages/Reports.tsx b/publisher/src/pages/Reports.tsx index 96f19d703..c31b640ff 100644 --- a/publisher/src/pages/Reports.tsx +++ b/publisher/src/pages/Reports.tsx @@ -15,6 +15,7 @@ // along with this program. If not, see . // ============================================================================= +import { Permission, ReportOverview } from "@justice-counts/common/types"; import { reaction, when } from "mobx"; import { observer } from "mobx-react-lite"; import React, { Fragment, useEffect, useState } from "react"; @@ -45,7 +46,6 @@ import { TabbedOptions, Table, } from "../components/Reports"; -import { Permission, ReportOverview } from "../shared/types"; import { useStore } from "../stores"; import { normalizeString, diff --git a/publisher/src/stores/DatapointsStore.ts b/publisher/src/stores/DatapointsStore.ts index ca526e06d..ea0b4dc9f 100644 --- a/publisher/src/stores/DatapointsStore.ts +++ b/publisher/src/stores/DatapointsStore.ts @@ -15,6 +15,12 @@ // along with this program. If not, see . // ============================================================================= +import { + DatapointsByMetric, + DataVizAggregateName, + DimensionNamesByMetricAndDisaggregation, + RawDatapoint, +} from "@justice-counts/common/types"; import { IReactionDisposer, makeAutoObservable, @@ -22,12 +28,6 @@ import { runInAction, } from "mobx"; -import { - DatapointsByMetric, - DataVizAggregateName, - DimensionNamesByMetricAndDisaggregation, - RawDatapoint, -} from "../shared/types"; import { isPositiveNumber } from "../utils"; import API from "./API"; import UserStore from "./UserStore"; diff --git a/publisher/src/stores/FormStore.ts b/publisher/src/stores/FormStore.ts index 8a4303c95..c6160e9f1 100644 --- a/publisher/src/stores/FormStore.ts +++ b/publisher/src/stores/FormStore.ts @@ -15,8 +15,6 @@ // along with this program. If not, see . // ============================================================================= -import { makeAutoObservable } from "mobx"; - import { FormError, FormStoreContextValues, @@ -24,7 +22,9 @@ import { FormStoreMetricValues, Metric, UpdatedMetricsValues, -} from "../shared/types"; +} from "@justice-counts/common/types"; +import { makeAutoObservable } from "mobx"; + import { isPositiveNumber, normalizeToString, diff --git a/publisher/src/stores/ReportStore.test.tsx b/publisher/src/stores/ReportStore.test.tsx index 07212a837..6ca061b2e 100644 --- a/publisher/src/stores/ReportStore.test.tsx +++ b/publisher/src/stores/ReportStore.test.tsx @@ -15,13 +15,13 @@ // along with this program. If not, see . // ============================================================================= +import { ReportOverview } from "@justice-counts/common/types"; import { render, screen } from "@testing-library/react"; import { runInAction } from "mobx"; import React from "react"; import mockJSON from "../mocks/reportOverviews.json"; import Reports from "../pages/Reports"; -import { ReportOverview } from "../shared/types"; import { rootStore, StoreProvider } from "."; const mockUnorderedReportsMap: { [reportID: string]: ReportOverview } = {}; diff --git a/publisher/src/stores/ReportStore.ts b/publisher/src/stores/ReportStore.ts index 2fcb4a429..b5f5a3d3c 100644 --- a/publisher/src/stores/ReportStore.ts +++ b/publisher/src/stores/ReportStore.ts @@ -15,6 +15,13 @@ // along with this program. If not, see . // ============================================================================= +import { + Metric, + Report, + ReportOverview, + ReportStatus, + UpdatedMetricsValues, +} from "@justice-counts/common/types"; import { IReactionDisposer, makeAutoObservable, @@ -24,13 +31,6 @@ import { import { UploadedFileStatus } from "../components/DataUpload"; import { MetricSettings } from "../components/MetricsView"; -import { - Metric, - Report, - ReportOverview, - ReportStatus, - UpdatedMetricsValues, -} from "../shared/types"; import { groupBy } from "../utils/helperUtils"; import API from "./API"; import UserStore from "./UserStore"; diff --git a/publisher/src/stores/UserStore.ts b/publisher/src/stores/UserStore.ts index 897553460..71219903e 100644 --- a/publisher/src/stores/UserStore.ts +++ b/publisher/src/stores/UserStore.ts @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // ============================================================================= +import { UserAgency } from "@justice-counts/common/types"; import { makeAutoObservable, runInAction, when } from "mobx"; import { makePersistable } from "mobx-persist-store"; import { APP_METADATA_CLAIM, AuthStore } from "../components/Auth"; import { showToast } from "../components/Toast"; -import { UserAgency } from "../shared/types"; import API from "./API"; type UserSettingsRequestBody = { diff --git a/publisher/src/utils/dateUtils.ts b/publisher/src/utils/dateUtils.ts index a3ec6e09b..1ae5a6cc1 100644 --- a/publisher/src/utils/dateUtils.ts +++ b/publisher/src/utils/dateUtils.ts @@ -15,7 +15,7 @@ // along with this program. If not, see . // ============================================================================= -import { ReportFrequency } from "../shared/types"; +import { ReportFrequency } from "@justice-counts/common/types"; export const monthsByName = [ "January", diff --git a/publisher/src/utils/helperUtils.ts b/publisher/src/utils/helperUtils.ts index 41bf9f106..e658828c9 100644 --- a/publisher/src/utils/helperUtils.ts +++ b/publisher/src/utils/helperUtils.ts @@ -15,10 +15,9 @@ // along with this program. If not, see . // ============================================================================= +import { MetricContext } from "@justice-counts/common/types"; import { debounce, memoize } from "lodash"; -import { MetricContext } from "../shared/types"; - export const isPositiveNumber = (value: string | number) => { if (typeof value === "string") { return (value.trim() !== "" && Number(value) === 0) || Number(value) > 0;