From ceffc5e8fb57c8c6a4bed06f4638331f2c5e2306 Mon Sep 17 00:00:00 2001 From: Sarah Roberts Date: Thu, 19 Dec 2024 17:53:29 -0700 Subject: [PATCH 1/6] CORE-2016: added initial support for addon rates --- public/static/locales/en/subscriptions.json | 4 +- .../addons/edit/AddonRatesEditor.js | 93 +++++++++++++++++++ .../subscriptions/addons/edit/EditAddon.js | 27 +++++- .../subscriptions/addons/edit/formatters.js | 16 ++++ src/components/subscriptions/ids.js | 5 + .../subscriptions/SubscriptionAddonsMocks.js | 14 +++ 6 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 src/components/subscriptions/addons/edit/AddonRatesEditor.js diff --git a/public/static/locales/en/subscriptions.json b/public/static/locales/en/subscriptions.json index 335540a8d..d90699337 100644 --- a/public/static/locales/en/subscriptions.json +++ b/public/static/locales/en/subscriptions.json @@ -4,7 +4,7 @@ "addonCreated": "Add-on created", "addonDelete": "Add-on(s) deleted", "addonName": "Add-on name", - "addons": "Add-ons", + "addonRates": "Add-on Rates", "addonUpdated": "Add-on updated", "addSubscription": "Add subscription", "addTooltip": "Add subscription for existing user.", @@ -26,6 +26,7 @@ "editAddons": "Edit Add-ons", "editQuotas": "Edit Quotas", "editSubscription": "Edit Subscription", + "effectiveDate": "Effective Date", "endDate": "End Date", "false": "False", "gib": "GiB", @@ -43,6 +44,7 @@ "quota": "Quota", "quotas": "Quotas", "quotaUpdated": "Quota updated.", + "rate": "Rate", "removeAddonAriaLabel": "Remove add-on from subscription.", "removeAddonError": "Failed to remove add-on. Please try again.", "resourceType": "Resource Type", diff --git a/src/components/subscriptions/addons/edit/AddonRatesEditor.js b/src/components/subscriptions/addons/edit/AddonRatesEditor.js new file mode 100644 index 000000000..c8a1ce62b --- /dev/null +++ b/src/components/subscriptions/addons/edit/AddonRatesEditor.js @@ -0,0 +1,93 @@ +/** + * Form fields for adding, editing, and deleting app addon rates. + * + * @author sarahr + */ + +import React from "react"; +import { useTranslation } from "i18n"; +import { + TableContainer, + Table, + TableHead, + TableRow, + TableCell, + TableBody, + Typography, +} from "@mui/material"; +import { + FormNumberField, + FormTimestampField, +} from "components/forms/FormField"; +import buildID from "components/utils/DebugIDUtil"; +import { minValue, nonEmptyField } from "components/utils/validations"; +import { Field } from "formik"; + +import ids from "../../ids"; + +function AddonRateEditorRow(props) { + const { baseId, fieldName } = props; + const { t } = useTranslation("util"); + + return ( + + + minValue(value, t)} + /> + + + nonEmptyField(value, t)} + /> + + + ); +} + +function AddonRatesEditor(props) { + const { addonRates, baseId, fieldName } = props; + + const { t } = useTranslation("subscriptions"); + + return ( + <> + + + + {t("addonRates")} + + + + {t("rate")} + {t("effectiveDate")} + + + + {addonRates?.map((addonRate, index) => ( + + ))} + +
+
+ + ); +} + +export default AddonRatesEditor; diff --git a/src/components/subscriptions/addons/edit/EditAddon.js b/src/components/subscriptions/addons/edit/EditAddon.js index 56c13ac20..150779a88 100644 --- a/src/components/subscriptions/addons/edit/EditAddon.js +++ b/src/components/subscriptions/addons/edit/EditAddon.js @@ -6,10 +6,12 @@ import { useTranslation } from "i18n"; import buildID from "components/utils/DebugIDUtil"; import { Button, MenuItem } from "@mui/material"; import DEDialog from "components/utils/DEDialog"; -import { FastField, Field, Form, Formik } from "formik"; -import FormCheckbox from "components/forms/FormCheckbox"; -import FormNumberField from "components/forms/FormNumberField"; -import FormTextField from "components/forms/FormTextField"; +import { FastField, Field, FieldArray, Form, Formik } from "formik"; +import { + FormCheckbox, + FormNumberField, + FormTextField, +} from "components/forms/FormField"; import { nonEmptyField, nonZeroValue } from "components/utils/validations"; import ErrorTypographyWithDialog from "components/error/ErrorTypographyWithDialog"; @@ -24,6 +26,7 @@ import { RESOURCE_TYPES_QUERY_KEY, } from "serviceFacades/subscriptions"; import { announce } from "components/announcer/CyVerseAnnouncer"; +import AddonRatesEditor from "./AddonRatesEditor"; function EditAddonDialog(props) { const { addon, open, onClose, parentId } = props; @@ -183,7 +186,7 @@ function EditAddonDialog(props) { } function EditAddonForm(props) { - const { parentId, resourceTypes, t } = props; + const { parentId, resourceTypes, t, addon } = props; const { t: i18nUtil } = useTranslation("util"); return ( <> @@ -239,6 +242,20 @@ function EditAddonForm(props) { label={t("defaultPaid")} name="defaultPaid" /> + { + return ( + + ); + }} + /> ); } diff --git a/src/components/subscriptions/addons/edit/formatters.js b/src/components/subscriptions/addons/edit/formatters.js index 972a3e1cc..c1a4e7e97 100644 --- a/src/components/subscriptions/addons/edit/formatters.js +++ b/src/components/subscriptions/addons/edit/formatters.js @@ -7,6 +7,7 @@ function mapPropsToValues(addon) { defaultAmount: 0, defaultPaid: true, resourceType: "", + addonRates: [], }; if (addon) { @@ -17,6 +18,7 @@ function mapPropsToValues(addon) { default_amount, default_paid, resource_type, + addon_rates, } = addon; values = { @@ -30,6 +32,13 @@ function mapPropsToValues(addon) { : default_amount, defaultPaid: default_paid, resourceType: resource_type.unit, + addonRates: addon_rates.map((addonRate, index) => { + return { + effectiveDate: addonRate.effective_date, + key: `addonRates.${index}`, + rate: addonRate.rate, + }; + }), }; } @@ -44,6 +53,7 @@ function formatAddonSubmission(values, resourceTypes, update = false) { defaultAmount, defaultPaid, resourceType, + addonRates, } = values; const resourceObj = resourceTypes.find( @@ -60,6 +70,12 @@ function formatAddonSubmission(values, resourceTypes, update = false) { resource_type: { uuid: id, }, + addon_rates: addonRates.map((addonRate) => { + return { + effective_date: addonRate.effectiveDate, + rate: addonRate.rate, + }; + }), }; // Include the submission's UUID if an update is requested diff --git a/src/components/subscriptions/ids.js b/src/components/subscriptions/ids.js index 54e6c8e6a..b7e59ce00 100644 --- a/src/components/subscriptions/ids.js +++ b/src/components/subscriptions/ids.js @@ -27,6 +27,11 @@ export default { NAME: "name", RESOURCE_TYPE: "resourceType", RESOURCE_UNIT: "resourceUnit", + ADDON_RATES: "addonRates", + ADDON_RATE: { + RATE: "rate", + EFFECTIVE_DATE: "effectiveDate", + }, }, ADDONS_MENU_ITEM: "addonsMenuItem", CANCEL_BUTTON: "cancelButton", diff --git a/stories/subscriptions/SubscriptionAddonsMocks.js b/stories/subscriptions/SubscriptionAddonsMocks.js index b443fdfa9..9d42c400c 100644 --- a/stories/subscriptions/SubscriptionAddonsMocks.js +++ b/stories/subscriptions/SubscriptionAddonsMocks.js @@ -11,6 +11,13 @@ export const availableAddons = { name: "cpu.hours", unit: "cpu hours", }, + addon_rates: [ + { + uuid: "932ded25-fcad-4680-8e91-c44002b6e91b", + rate: 125.0, + effective_date: "2022-01-01T07:00:00Z", + }, + ], }, { uuid: "c21dd61f-aa41-40ad-8005-859679ceed9c", @@ -23,6 +30,13 @@ export const availableAddons = { name: "data.size", unit: "bytes", }, + addon_rates: [ + { + uuid: "932ded25-fcad-4680-8e91-c44002b6e91b", + rate: 125.0, + effective_date: "2022-01-01T07:00:00Z", + }, + ], }, ], }; From f8e4f1685c2d57f6435c33d7fc882a8cdbe3d7b9 Mon Sep 17 00:00:00 2001 From: Sarah Roberts Date: Fri, 20 Dec 2024 14:32:07 -0700 Subject: [PATCH 2/6] CORE-2016: fixed a bug with initial value population in addon rates --- src/components/subscriptions/addons/edit/EditAddon.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/subscriptions/addons/edit/EditAddon.js b/src/components/subscriptions/addons/edit/EditAddon.js index 150779a88..7d5cb5f8c 100644 --- a/src/components/subscriptions/addons/edit/EditAddon.js +++ b/src/components/subscriptions/addons/edit/EditAddon.js @@ -173,6 +173,7 @@ function EditAddonDialog(props) { /> )} ); }} From 63ca8f56a04eac87dcad47cae7779a8df6b2873c Mon Sep 17 00:00:00 2001 From: Sarah Roberts Date: Fri, 20 Dec 2024 15:59:15 -0700 Subject: [PATCH 3/6] CORE-2016: added support for adding new addon rates --- .../addons/edit/AddonRatesEditor.js | 49 +++++++++++++++---- .../subscriptions/addons/edit/EditAddon.js | 15 ++++-- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/components/subscriptions/addons/edit/AddonRatesEditor.js b/src/components/subscriptions/addons/edit/AddonRatesEditor.js index c8a1ce62b..5712ff8e5 100644 --- a/src/components/subscriptions/addons/edit/AddonRatesEditor.js +++ b/src/components/subscriptions/addons/edit/AddonRatesEditor.js @@ -7,6 +7,7 @@ import React from "react"; import { useTranslation } from "i18n"; import { + Button, TableContainer, Table, TableHead, @@ -22,12 +23,14 @@ import { import buildID from "components/utils/DebugIDUtil"; import { minValue, nonEmptyField } from "components/utils/validations"; import { Field } from "formik"; +import { Add, Delete } from "@mui/icons-material"; import ids from "../../ids"; function AddonRateEditorRow(props) { - const { baseId, fieldName } = props; - const { t } = useTranslation("util"); + const { baseId, fieldName, onDelete } = props; + const { t: i18nUtil } = useTranslation("util"); + const { t } = useTranslation(["common"]); return ( @@ -37,7 +40,7 @@ function AddonRateEditorRow(props) { id={buildID(baseId, ids.ADDONS_DLG.ADDON_RATE.RATE)} name={`${fieldName}.rate`} required - validate={(value) => minValue(value, t)} + validate={(value) => minValue(value, i18nUtil)} /> @@ -49,29 +52,57 @@ function AddonRateEditorRow(props) { )} name={`${fieldName}.effectiveDate`} required - validate={(value) => nonEmptyField(value, t)} + validate={(value) => nonEmptyField(value, i18nUtil)} /> + + + ); } function AddonRatesEditor(props) { - const { addonRates, baseId, fieldName } = props; + const { addonRates, baseId, fieldName, onAdd } = props; - const { t } = useTranslation("subscriptions"); + const { t } = useTranslation(["subscriptions", "common"]); return ( <> - {t("addonRates")} + {t("subscriptions:addonRates")} - {t("rate")} - {t("effectiveDate")} + + + {t("subscriptions:rate")} + + + + + {t("subscriptions:effectiveDate")} + + + + + diff --git a/src/components/subscriptions/addons/edit/EditAddon.js b/src/components/subscriptions/addons/edit/EditAddon.js index 7d5cb5f8c..b96837f8d 100644 --- a/src/components/subscriptions/addons/edit/EditAddon.js +++ b/src/components/subscriptions/addons/edit/EditAddon.js @@ -122,7 +122,7 @@ function EditAddonDialog(props) { }} enableReinitialize={true} > - {({ handleSubmit, resetForm }) => { + {({ handleSubmit, resetForm, values }) => { return (
)} @@ -246,14 +246,21 @@ function EditAddonForm(props) { { + const onAdd = () => { + arrayHelpers.unshift({ + rate: 0, + effectiveDate: Date.now().toString(), + }); + }; return ( ); }} From 325d5de913ee2a5f677716852bf33eff6d6ec9b5 Mon Sep 17 00:00:00 2001 From: Sarah Roberts Date: Fri, 20 Dec 2024 16:23:56 -0700 Subject: [PATCH 4/6] CORE-2016: added support for removing rates from an addon --- .../subscriptions/addons/edit/AddonRatesEditor.js | 9 ++++++--- src/components/subscriptions/addons/edit/EditAddon.js | 8 +++++++- src/components/subscriptions/addons/edit/formatters.js | 2 ++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/components/subscriptions/addons/edit/AddonRatesEditor.js b/src/components/subscriptions/addons/edit/AddonRatesEditor.js index 5712ff8e5..90f22658a 100644 --- a/src/components/subscriptions/addons/edit/AddonRatesEditor.js +++ b/src/components/subscriptions/addons/edit/AddonRatesEditor.js @@ -28,7 +28,7 @@ import { Add, Delete } from "@mui/icons-material"; import ids from "../../ids"; function AddonRateEditorRow(props) { - const { baseId, fieldName, onDelete } = props; + const { baseId, fieldName, key, onDelete } = props; const { t: i18nUtil } = useTranslation("util"); const { t } = useTranslation(["common"]); @@ -59,7 +59,9 @@ function AddonRateEditorRow(props) { @@ -69,7 +71,7 @@ function AddonRateEditorRow(props) { } function AddonRatesEditor(props) { - const { addonRates, baseId, fieldName, onAdd } = props; + const { addonRates, baseId, fieldName, onAdd, onDelete } = props; const { t } = useTranslation(["subscriptions", "common"]); @@ -112,6 +114,7 @@ function AddonRatesEditor(props) { baseId={buildID(baseId, index)} fieldName={`${fieldName}.${index}`} key={index} + onDelete={onDelete} /> ))}
diff --git a/src/components/subscriptions/addons/edit/EditAddon.js b/src/components/subscriptions/addons/edit/EditAddon.js index b96837f8d..718c41ac5 100644 --- a/src/components/subscriptions/addons/edit/EditAddon.js +++ b/src/components/subscriptions/addons/edit/EditAddon.js @@ -247,11 +247,16 @@ function EditAddonForm(props) { name="addonRates" render={(arrayHelpers) => { const onAdd = () => { - arrayHelpers.unshift({ + arrayHelpers.push({ rate: 0, effectiveDate: Date.now().toString(), }); }; + + const onDelete = (index) => { + arrayHelpers.remove(index); + }; + return ( ); }} diff --git a/src/components/subscriptions/addons/edit/formatters.js b/src/components/subscriptions/addons/edit/formatters.js index c1a4e7e97..bce9d249f 100644 --- a/src/components/subscriptions/addons/edit/formatters.js +++ b/src/components/subscriptions/addons/edit/formatters.js @@ -34,6 +34,7 @@ function mapPropsToValues(addon) { resourceType: resource_type.unit, addonRates: addon_rates.map((addonRate, index) => { return { + uuid, effectiveDate: addonRate.effective_date, key: `addonRates.${index}`, rate: addonRate.rate, @@ -72,6 +73,7 @@ function formatAddonSubmission(values, resourceTypes, update = false) { }, addon_rates: addonRates.map((addonRate) => { return { + uuid, effective_date: addonRate.effectiveDate, rate: addonRate.rate, }; From f8fdbe26b8f78ca7c1e56477a90924df54cf9fd6 Mon Sep 17 00:00:00 2001 From: Sarah Roberts Date: Mon, 6 Jan 2025 19:34:16 -0700 Subject: [PATCH 5/6] CORE-2016: fixed the problem with the consumable flag not being set in addon updates --- src/components/subscriptions/addons/edit/formatters.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/subscriptions/addons/edit/formatters.js b/src/components/subscriptions/addons/edit/formatters.js index bce9d249f..0eaa4da14 100644 --- a/src/components/subscriptions/addons/edit/formatters.js +++ b/src/components/subscriptions/addons/edit/formatters.js @@ -61,7 +61,7 @@ function formatAddonSubmission(values, resourceTypes, update = false) { (resource) => resourceType === resource.unit ); - const { id, unit, name } = resourceObj; + const { id, unit, name, consumable } = resourceObj; const submission = { name: addonName, @@ -88,6 +88,7 @@ function formatAddonSubmission(values, resourceTypes, update = false) { ...submission.resource_type, unit, name, + consumable, }; } From 6c21780a2e045bd19016dbaf38134437b85aca9f Mon Sep 17 00:00:00 2001 From: Sarah Roberts Date: Tue, 7 Jan 2025 16:10:26 -0700 Subject: [PATCH 6/6] CORE-2016: fixed the date format in addon update request bodies --- src/components/subscriptions/addons/edit/formatters.js | 8 +++++++- src/components/utils/DateFormatter.js | 4 ++-- src/components/utils/dateConstants.js | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/subscriptions/addons/edit/formatters.js b/src/components/subscriptions/addons/edit/formatters.js index 0eaa4da14..cc713cfcc 100644 --- a/src/components/subscriptions/addons/edit/formatters.js +++ b/src/components/subscriptions/addons/edit/formatters.js @@ -1,5 +1,11 @@ +import dateConstants from "components/utils/dateConstants"; +import { formatDateObject } from "components/utils/DateFormatter"; import { bytesInGiB, bytesToGiB } from "../../utils"; +function formatEffectiveDate(effectiveDate) { + return formatDateObject(new Date(effectiveDate), dateConstants.ISO_8601); +} + function mapPropsToValues(addon) { let values = { addonName: "", @@ -74,7 +80,7 @@ function formatAddonSubmission(values, resourceTypes, update = false) { addon_rates: addonRates.map((addonRate) => { return { uuid, - effective_date: addonRate.effectiveDate, + effective_date: formatEffectiveDate(addonRate.effectiveDate), rate: addonRate.rate, }; }), diff --git a/src/components/utils/DateFormatter.js b/src/components/utils/DateFormatter.js index 063ced765..412a00a8b 100644 --- a/src/components/utils/DateFormatter.js +++ b/src/components/utils/DateFormatter.js @@ -2,7 +2,7 @@ @author sriram */ -import lightFormat from "date-fns/lightFormat"; +import format from "date-fns/format"; import toDate from "date-fns/toDate"; import dateConstants from "./dateConstants"; import { formatDistance, fromUnixTime } from "date-fns"; @@ -16,7 +16,7 @@ import { formatDistance, fromUnixTime } from "date-fns"; function formatDate(longDate, dateFormat = dateConstants.LONG_DATE_FORMAT) { const longDateInt = parseInt(longDate, 10); return longDateInt - ? lightFormat(toDate(new Date(longDateInt)), dateFormat) + ? format(toDate(new Date(longDateInt)), dateFormat) : dateConstants.EMPTY_DATE; } diff --git a/src/components/utils/dateConstants.js b/src/components/utils/dateConstants.js index b33c8c4b6..f2bb8e158 100644 --- a/src/components/utils/dateConstants.js +++ b/src/components/utils/dateConstants.js @@ -2,5 +2,6 @@ export default { DATE_FORMAT: "yyyy-MM-dd", TIME_FORMAT: "HH:mm:ss", LONG_DATE_FORMAT: "yyyy-MM-dd HH:mm:ss", + ISO_8601: "yyyy-MM-dd'T'HH:mm:ssXXX", EMPTY_DATE: "-", };