From aeb3081624588ff0d4c3500cd223c58327225439 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 20 Mar 2025 13:54:19 +0100 Subject: [PATCH 1/5] chore: Don't use fallback functions for dragging (#9585) Makes it so that strategies project env strategies that aren't draggable don't get the drag icon. The reason it didn't work as expected was that we used fallback functions instead of keeping them undefined. I discovered that we applied two dragging boxes, so I removed the outer layer one (specific to project envs) in favor of relying on the inner one. Most of the lines changed are just indentation as a result of this nesting going away. Here's the diff. The top set of strategies aren't draggable; the lower ones are. ![image](https://github.com/user-attachments/assets/0a7b6371-9f34-4596-a85f-9881da821448) --- ...rojectEnvironmentStrategyDraggableItem.tsx | 118 ++++++++---------- .../StrategyDraggableItem.tsx | 12 +- 2 files changed, 59 insertions(+), 71 deletions(-) diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/ProjectEnvironmentStrategyDraggableItem.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/ProjectEnvironmentStrategyDraggableItem.tsx index 19de8335cf..cd0a413dee 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/ProjectEnvironmentStrategyDraggableItem.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/ProjectEnvironmentStrategyDraggableItem.tsx @@ -1,5 +1,5 @@ import { type DragEventHandler, type RefObject, useRef } from 'react'; -import { Box, useMediaQuery, useTheme } from '@mui/material'; +import { useMediaQuery, useTheme } from '@mui/material'; import type { IFeatureEnvironment } from 'interfaces/featureToggle'; import type { IFeatureStrategy } from 'interfaces/strategy'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; @@ -34,8 +34,6 @@ type ProjectEnvironmentStrategyDraggableItemProps = { onDragEnd?: () => void; }; -const onDragNoOp = () => () => {}; - export const ProjectEnvironmentStrategyDraggableItem = ({ className, strategy, @@ -43,9 +41,9 @@ export const ProjectEnvironmentStrategyDraggableItem = ({ environmentName, otherEnvironments, isDragging, - onDragStartRef = onDragNoOp, - onDragOver = onDragNoOp, - onDragEnd = onDragNoOp, + onDragStartRef, + onDragOver, + onDragEnd, }: ProjectEnvironmentStrategyDraggableItemProps) => { const projectId = useRequiredPathParam('projectId'); const featureId = useRequiredPathParam('featureId'); @@ -75,67 +73,59 @@ export const ProjectEnvironmentStrategyDraggableItem = ({ const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); return ( - - - {draftChange && !isSmallScreen ? ( - - ) : null} + + {draftChange && !isSmallScreen ? ( + + ) : null} - {scheduledChanges && - scheduledChanges.length > 0 && - !isSmallScreen ? ( - scheduledChange.id)} - /> - ) : null} - {otherEnvironments && otherEnvironments?.length > 0 ? ( - - ) : null} - - - - 0 && + !isSmallScreen ? ( + scheduledChange.id)} + /> + ) : null} + {otherEnvironments && otherEnvironments?.length > 0 ? ( + - - } - /> - + ) : null} + + + + + + } + /> ); }; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyDraggableItem.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyDraggableItem.tsx index 0ca7ca554a..6e2d857c6f 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyDraggableItem.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyDraggableItem.tsx @@ -8,8 +8,6 @@ import { Box } from '@mui/material'; import type { IFeatureStrategy } from 'interfaces/strategy'; import { StrategyItem } from './StrategyItem/StrategyItem'; -const onDragNoOp = () => () => {}; - type StrategyDraggableItemProps = { headerItemsRight: ReactNode; strategy: IFeatureStrategy; @@ -30,9 +28,9 @@ export const StrategyDraggableItem = ({ strategy, index, isDragging, - onDragStartRef = onDragNoOp, - onDragOver = onDragNoOp, - onDragEnd = onDragNoOp, + onDragStartRef, + onDragOver, + onDragEnd, headerItemsRight, }: StrategyDraggableItemProps) => { const ref = useRef(null); @@ -41,13 +39,13 @@ export const StrategyDraggableItem = ({ From 92a13c4c5514cfec08945b77eb41dda1a9d2a05a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Fournier?= Date: Thu, 20 Mar 2025 13:59:37 +0100 Subject: [PATCH 2/5] fix: all users have a root role and warning if not (#9584) ## About the changes SCIM provisioned users ended up without a root role. Unleash was assigning them the Viewer role by code but some queries using the db to resolve the role did not have the same logic leading to weird behaviors. This amends the situation by assigning the Viewer role to those users following the least privilege principle. Also adds a warning when assuming the Viewer role. That should never happen but we want to be confident before removing it. Depends on https://github.com/bricks-software/unleash-enterprise/pull/164 --- src/lib/services/access-service.ts | 5 +++-- ...250320121200-all-users-have-a-root-role.js | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 src/migrations/20250320121200-all-users-have-a-root-role.js diff --git a/src/lib/services/access-service.ts b/src/lib/services/access-service.ts index 4cd15f3b11..388fc35cfb 100644 --- a/src/lib/services/access-service.ts +++ b/src/lib/services/access-service.ts @@ -457,8 +457,9 @@ export class AccessService { async getRootRoleForUser(userId: number): Promise { const rootRole = await this.store.getRootRoleForUser(userId); if (!rootRole) { - const defaultRole = await this.getPredefinedRole(RoleName.VIEWER); - return defaultRole; + // this should never happen, but before breaking we want to know if it does. + this.logger.warn(`Could not find root role for user=${userId}.`); + return this.getPredefinedRole(RoleName.VIEWER); } return rootRole; } diff --git a/src/migrations/20250320121200-all-users-have-a-root-role.js b/src/migrations/20250320121200-all-users-have-a-root-role.js new file mode 100644 index 0000000000..8f1ab0ad27 --- /dev/null +++ b/src/migrations/20250320121200-all-users-have-a-root-role.js @@ -0,0 +1,20 @@ +exports.up = function (db, cb) { + // add root role Viewer (id 3) to all users who don't have a root role + db.runSql( + `INSERT INTO role_user(role_id, user_id, project) SELECT 3, u.id, 'default' +FROM users u +WHERE u.id > 0 AND u.deleted_at IS NULL AND NOT EXISTS ( + SELECT 1 + FROM role_user ru + JOIN roles r ON ru.role_id = r.id + WHERE ru.user_id = u.id + AND r.type IN ('root', 'root-custom') +);`, + cb, + ); +}; + +exports.down = function (db, callback) { + // No rollback + callback(); +}; \ No newline at end of file From 8c7d62e305e672b25f0935270dd8f3d6c54a9a3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Mar 2025 14:40:40 +0100 Subject: [PATCH 3/5] chore(deps): bump @babel/helpers from 7.26.0 to 7.26.10 in /website (#9521) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [@babel/helpers](https://github.com/babel/babel/tree/HEAD/packages/babel-helpers) from 7.26.0 to 7.26.10.
Release notes

Sourced from @​babel/helpers's releases.

v7.26.10 (2025-03-11)

Thanks @​jordan-choi and @​mmmsssttt404 for your first PRs!

This release includes a fix for https://github.com/babel/babel/security/advisories/GHSA-968p-4wvh-cqc8, a security vulnerability which affects the .replace method of transpiled regular expressions that use named capturing groups.

:eyeglasses: Spec Compliance

:bug: Bug Fix

  • babel-parser, babel-template
  • babel-core
  • babel-parser, babel-plugin-transform-typescript
  • babel-traverse
  • babel-generator
  • babel-parser
  • babel-helpers, babel-runtime, babel-runtime-corejs2, babel-runtime-corejs3

:nail_care: Polish

  • babel-standalone

:house: Internal

Committers: 6

v7.26.9 (2025-02-14)

:bug: Bug Fix

... (truncated)

Changelog

Sourced from @​babel/helpers's changelog.

v7.26.10 (2025-03-11)

:eyeglasses: Spec Compliance

:bug: Bug Fix

  • babel-parser, babel-template
  • babel-core
  • babel-parser, babel-plugin-transform-typescript
  • babel-traverse
  • babel-generator
  • babel-parser
  • babel-helpers, babel-runtime, babel-runtime-corejs2, babel-runtime-corejs3

:nail_care: Polish

  • babel-standalone

:house: Internal

v7.26.9 (2025-02-14)

:bug: Bug Fix

:house: Internal

v7.26.7 (2025-01-24)

:bug: Bug Fix

  • babel-helpers, babel-preset-env, babel-runtime-corejs3
  • babel-plugin-transform-typeof-symbol

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@babel/helpers&package-manager=npm_and_yarn&previous-version=7.26.0&new-version=7.26.10)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/Unleash/unleash/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- website/yarn.lock | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/website/yarn.lock b/website/yarn.lock index 36a5ec6a41..5c9b6237c8 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -235,7 +235,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.25.9, @babel/code-frame@npm:^7.26.0, @babel/code-frame@npm:^7.8.3": +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.25.9, @babel/code-frame@npm:^7.26.0, @babel/code-frame@npm:^7.26.2, @babel/code-frame@npm:^7.8.3": version: 7.26.2 resolution: "@babel/code-frame@npm:7.26.2" dependencies: @@ -494,12 +494,12 @@ __metadata: linkType: hard "@babel/helpers@npm:^7.26.0": - version: 7.26.0 - resolution: "@babel/helpers@npm:7.26.0" + version: 7.26.10 + resolution: "@babel/helpers@npm:7.26.10" dependencies: - "@babel/template": "npm:^7.25.9" - "@babel/types": "npm:^7.26.0" - checksum: 10c0/343333cced6946fe46617690a1d0789346960910225ce359021a88a60a65bc0d791f0c5d240c0ed46cf8cc63b5fd7df52734ff14e43b9c32feae2b61b1647097 + "@babel/template": "npm:^7.26.9" + "@babel/types": "npm:^7.26.10" + checksum: 10c0/f99e1836bcffce96db43158518bb4a24cf266820021f6461092a776cba2dc01d9fc8b1b90979d7643c5c2ab7facc438149064463a52dd528b21c6ab32509784f languageName: node linkType: hard @@ -514,6 +514,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.26.9": + version: 7.26.10 + resolution: "@babel/parser@npm:7.26.10" + dependencies: + "@babel/types": "npm:^7.26.10" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/c47f5c0f63cd12a663e9dc94a635f9efbb5059d98086a92286d7764357c66bceba18ccbe79333e01e9be3bfb8caba34b3aaebfd8e62c3d5921c8cf907267be75 + languageName: node + linkType: hard + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.25.9": version: 7.25.9 resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.25.9" @@ -1482,6 +1493,17 @@ __metadata: languageName: node linkType: hard +"@babel/template@npm:^7.26.9": + version: 7.26.9 + resolution: "@babel/template@npm:7.26.9" + dependencies: + "@babel/code-frame": "npm:^7.26.2" + "@babel/parser": "npm:^7.26.9" + "@babel/types": "npm:^7.26.9" + checksum: 10c0/019b1c4129cc01ad63e17529089c2c559c74709d225f595eee017af227fee11ae8a97a6ab19ae6768b8aa22d8d75dcb60a00b28f52e9fa78140672d928bc1ae9 + languageName: node + linkType: hard + "@babel/traverse@npm:^7.25.9": version: 7.25.9 resolution: "@babel/traverse@npm:7.25.9" @@ -1507,6 +1529,16 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.26.10, @babel/types@npm:^7.26.9": + version: 7.26.10 + resolution: "@babel/types@npm:7.26.10" + dependencies: + "@babel/helper-string-parser": "npm:^7.25.9" + "@babel/helper-validator-identifier": "npm:^7.25.9" + checksum: 10c0/7a7f83f568bfc3dfabfaf9ae3a97ab5c061726c0afa7dcd94226d4f84a81559da368ed79671e3a8039d16f12476cf110381a377ebdea07587925f69628200dac + languageName: node + linkType: hard + "@braintree/sanitize-url@npm:^7.0.1": version: 7.1.1 resolution: "@braintree/sanitize-url@npm:7.1.1" From 70444c2003be991535f129dde046fd043b5034f6 Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Thu, 20 Mar 2025 14:44:33 +0100 Subject: [PATCH 4/5] refactor: variant colors (#9586) Toned-down colors for dark theme --- .../RolloutVariants/RolloutVariants.tsx | 24 ++++-------- frontend/src/themes/colors.ts | 37 +++++++++++++------ frontend/src/themes/dark-theme.ts | 2 +- frontend/src/themes/theme.ts | 2 +- 4 files changed, 35 insertions(+), 30 deletions(-) diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/RolloutParameter/RolloutVariants/RolloutVariants.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/RolloutParameter/RolloutVariants/RolloutVariants.tsx index 2f57913745..b4389b48de 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/RolloutParameter/RolloutVariants/RolloutVariants.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/RolloutParameter/RolloutVariants/RolloutVariants.tsx @@ -6,22 +6,14 @@ import type { StrategyVariantSchema } from 'openapi'; import type { FC } from 'react'; const StyledVariantChip = styled(StrategyEvaluationChip)<{ order: number }>( - ({ theme, order }) => { - const variantColor = - theme.palette.variants[order % theme.palette.variants.length]; - - return { - borderRadius: theme.shape.borderRadiusExtraLarge, - border: 'none', - color: theme.palette.text.primary, - background: - // TODO: adjust theme.palette.variants - theme.mode === 'dark' - ? `hsl(from ${variantColor} h calc(s - 30) calc(l - 45))` - : `hsl(from ${variantColor} h s calc(l + 5))`, - fontWeight: theme.typography.fontWeightRegular, - }; - }, + ({ theme, order }) => ({ + borderRadius: theme.shape.borderRadiusExtraLarge, + border: 'none', + color: theme.palette.text.primary, + background: + theme.palette.variants[order % theme.palette.variants.length], + fontWeight: theme.typography.fontWeightRegular, + }), ); const StyledPayloadHeader = styled('div')(({ theme }) => ({ diff --git a/frontend/src/themes/colors.ts b/frontend/src/themes/colors.ts index 2b3b5302d5..898e8bb42c 100644 --- a/frontend/src/themes/colors.ts +++ b/frontend/src/themes/colors.ts @@ -95,18 +95,31 @@ export const colors = { 600: '#1f3751', 500: '#0e2840', }, - variants: [ - '#BEBBF3', - '#FFC46F', - '#B0D182', - '#96D2FA', - '#F7E3AE', - '#7FBAA9', - '#D3B9DB', - '#FBC5A0', - '#DDE7B5', - '#9EC4E3', - '#F8B6CC', + lightVariants: [ + '#d2d1f5', + '#f8d18f', + '#c1d699', + '#b7dbf9', + '#f7ecc8', + '#9ac1b5', + '#dacbe2', + '#f6d6bb', + '#e7edcb', + '#b7cfe7', + '#f3d0dc', + ] as string[], + darkVariants: [ + '#3f3d89', + '#6f4f1d', + '#3a3f2f', + '#325e87', + '#68620e', + '#8e3a53', + '#585659', + '#8a552a', + '#686e4b', + '#3e4f60', + '#343444', ] as string[], chartSeries: [ '#816DD3', diff --git a/frontend/src/themes/dark-theme.ts b/frontend/src/themes/dark-theme.ts index 4e2a1e2c20..2620388975 100644 --- a/frontend/src/themes/dark-theme.ts +++ b/frontend/src/themes/dark-theme.ts @@ -222,7 +222,7 @@ const theme = { // A400: '#A6000E', // A700: '#A6000E', }, - variants: colors.variants, + variants: colors.darkVariants, /** * Dashboard and charts diff --git a/frontend/src/themes/theme.ts b/frontend/src/themes/theme.ts index 4e22eac0b5..1e31aa06cd 100644 --- a/frontend/src/themes/theme.ts +++ b/frontend/src/themes/theme.ts @@ -281,7 +281,7 @@ const theme = { // A400: '#A6000E', // A700: '#A6000E', }, - variants: colors.variants, + variants: colors.lightVariants, /** * Dashboard and charts From 3fa54f44657c22c116935883a22983c03998212b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20G=C3=B3is?= Date: Fri, 21 Mar 2025 09:15:49 +0000 Subject: [PATCH 5/5] chore: instance status prices (#9588) https://linear.app/unleash/issue/2-3429/use-the-correct-prices-for-each-instance-in-unleashs-ui Uses the instance prices exposed through instance status to display the correct price amounts in Unleash's UI. --- .../BillingPlan/BillingDetailsPAYG.tsx | 17 +++++++++------ .../BillingPlan/BillingDetailsPro.tsx | 21 ++++++++++++------- .../BillingPlan/BillingPlan.tsx | 19 ++++++++--------- .../BillingPlan/useOverageCost.ts | 17 ++++++++------- .../NetworkTrafficUsage/hooks/useStats.ts | 14 ++++++++++--- .../SeatCostWarning/SeatCostWarning.tsx | 11 ++++++---- .../DemoDialogPlans/DemoDialogPlans.tsx | 16 +++++++++----- frontend/src/interfaces/instance.ts | 16 ++++++++++++++ 8 files changed, 88 insertions(+), 43 deletions(-) diff --git a/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/BillingDetailsPAYG.tsx b/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/BillingDetailsPAYG.tsx index 69c854ffb2..15687e8bc5 100644 --- a/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/BillingDetailsPAYG.tsx +++ b/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/BillingDetailsPAYG.tsx @@ -8,8 +8,8 @@ import { useUsers } from 'hooks/api/getters/useUsers/useUsers'; import { BILLING_INCLUDED_REQUESTS, BILLING_PAYG_DEFAULT_MINIMUM_SEATS, - BILLING_PAYG_USER_PRICE, - BILLING_TRAFFIC_BUNDLE_PRICE, + BILLING_PAYG_SEAT_PRICE, + BILLING_TRAFFIC_PRICE, } from './BillingPlan'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useOverageCost } from './useOverageCost'; @@ -34,11 +34,16 @@ export const BillingDetailsPAYG = ({ const eligibleUsers = users.filter((user) => user.email); + const seatPrice = + instanceStatus.prices?.payg?.seat ?? BILLING_PAYG_SEAT_PRICE; + const trafficPrice = + instanceStatus.prices?.payg?.traffic ?? BILLING_TRAFFIC_PRICE; + const minSeats = instanceStatus.minSeats ?? BILLING_PAYG_DEFAULT_MINIMUM_SEATS; const billableUsers = Math.max(eligibleUsers.length, minSeats); - const usersCost = BILLING_PAYG_USER_PRICE * billableUsers; + const usersCost = seatPrice * billableUsers; const includedTraffic = BILLING_INCLUDED_REQUESTS; const overageCost = useOverageCost(includedTraffic); @@ -66,7 +71,7 @@ export const BillingDetailsPAYG = ({ - ${BILLING_PAYG_USER_PRICE}/month per paid member + ${seatPrice}/month per paid member @@ -93,8 +98,8 @@ export const BillingDetailsPAYG = ({ - ${BILLING_TRAFFIC_BUNDLE_PRICE} per 1 - million started above included data + ${trafficPrice} per 1 million started above + included data diff --git a/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/BillingDetailsPro.tsx b/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/BillingDetailsPro.tsx index 84ac700dcd..9e5280d87f 100644 --- a/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/BillingDetailsPro.tsx +++ b/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/BillingDetailsPro.tsx @@ -9,10 +9,10 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit import { useUsers } from 'hooks/api/getters/useUsers/useUsers'; import { BILLING_INCLUDED_REQUESTS, - BILLING_PLAN_PRICES, BILLING_PRO_DEFAULT_INCLUDED_SEATS, - BILLING_PRO_USER_PRICE, - BILLING_TRAFFIC_BUNDLE_PRICE, + BILLING_PRO_BASE_PRICE, + BILLING_PRO_SEAT_PRICE, + BILLING_TRAFFIC_PRICE, } from './BillingPlan'; import { useOverageCost } from './useOverageCost'; @@ -41,12 +41,17 @@ export const BillingDetailsPro = ({ const eligibleUsers = users.filter((user) => user.email); - const planPrice = BILLING_PLAN_PRICES[instanceStatus.plan]; + const planPrice = + instanceStatus.prices?.pro?.base ?? BILLING_PRO_BASE_PRICE; + const seatPrice = + instanceStatus.prices?.pro?.seat ?? BILLING_PRO_SEAT_PRICE; + const trafficPrice = + instanceStatus.prices?.pro?.traffic ?? BILLING_TRAFFIC_PRICE; const seats = BILLING_PRO_DEFAULT_INCLUDED_SEATS; const freeAssigned = Math.min(eligibleUsers.length, seats); const paidAssigned = eligibleUsers.length - freeAssigned; - const paidAssignedPrice = BILLING_PRO_USER_PRICE * paidAssigned; + const paidAssignedPrice = seatPrice * paidAssigned; const includedTraffic = BILLING_INCLUDED_REQUESTS; const overageCost = useOverageCost(includedTraffic); @@ -96,7 +101,7 @@ export const BillingDetailsPro = ({ - ${BILLING_PRO_USER_PRICE}/month per paid member + ${seatPrice}/month per paid member @@ -123,8 +128,8 @@ export const BillingDetailsPro = ({ - ${BILLING_TRAFFIC_BUNDLE_PRICE} per 1 - million started above included data + ${trafficPrice} per 1 million started above + included data diff --git a/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/BillingPlan.tsx b/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/BillingPlan.tsx index 24e5796a3f..0c7690bbbb 100644 --- a/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/BillingPlan.tsx +++ b/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/BillingPlan.tsx @@ -1,6 +1,6 @@ import { Alert, Grid, styled } from '@mui/material'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { InstanceState, InstancePlan } from 'interfaces/instance'; +import { InstanceState } from 'interfaces/instance'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import { trialHasExpired, isTrialInstance } from 'utils/instanceTrial'; import { GridRow } from 'component/common/GridRow/GridRow'; @@ -9,16 +9,14 @@ import { Badge } from 'component/common/Badge/Badge'; import { BillingDetails } from './BillingDetails'; import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus'; -export const BILLING_PLAN_PRICES: Record = { - [InstancePlan.PRO]: 80, -}; +export const BILLING_PRO_BASE_PRICE = 80; +export const BILLING_PRO_SEAT_PRICE = 15; +export const BILLING_PAYG_SEAT_PRICE = 75; +export const BILLING_TRAFFIC_PRICE = 5; -export const BILLING_PAYG_USER_PRICE = 75; export const BILLING_PAYG_DEFAULT_MINIMUM_SEATS = 5; -export const BILLING_PRO_USER_PRICE = 15; export const BILLING_PRO_DEFAULT_INCLUDED_SEATS = 5; export const BILLING_INCLUDED_REQUESTS = 53_000_000; -export const BILLING_TRAFFIC_BUNDLE_PRICE = 5; const StyledPlanBox = styled('aside')(({ theme }) => ({ padding: theme.spacing(2.5), @@ -77,7 +75,8 @@ export const BillingPlan = () => { ); const expired = trialHasExpired(instanceStatus); - const planPrice = BILLING_PLAN_PRICES[instanceStatus.plan] ?? 0; + const baseProPrice = + instanceStatus.prices?.pro?.base ?? BILLING_PRO_BASE_PRICE; const plan = `${instanceStatus.plan}${isPAYG ? ' Pay-as-You-Go' : ''}`; const inactive = instanceStatus.state !== InstanceState.ACTIVE; @@ -131,10 +130,10 @@ export const BillingPlan = () => { 0} + condition={!isPAYG} show={ - ${planPrice.toFixed(2)} + ${baseProPrice.toFixed(2)} } /> diff --git a/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/useOverageCost.ts b/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/useOverageCost.ts index 80ae41455c..1f29f95158 100644 --- a/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/useOverageCost.ts +++ b/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/useOverageCost.ts @@ -5,7 +5,8 @@ import { calculateOverageCost, calculateTotalUsage, } from 'utils/traffic-calculations'; -import { BILLING_TRAFFIC_BUNDLE_PRICE } from './BillingPlan'; +import { BILLING_TRAFFIC_PRICE } from './BillingPlan'; +import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus'; export const useOverageCost = (includedTraffic: number) => { if (!includedTraffic) { @@ -17,6 +18,12 @@ export const useOverageCost = (includedTraffic: number) => { const from = formatDate(startOfMonth(now)); const to = formatDate(endOfMonth(now)); + const { instanceStatus } = useInstanceStatus(); + const trafficPrice = + instanceStatus?.prices?.[ + instanceStatus?.billing === 'pay-as-you-go' ? 'payg' : 'pro' + ]?.traffic ?? BILLING_TRAFFIC_PRICE; + const { result } = useTrafficSearch('daily', { from, to }); const overageCost = useMemo(() => { if (result.state !== 'success') { @@ -24,12 +31,8 @@ export const useOverageCost = (includedTraffic: number) => { } const totalUsage = calculateTotalUsage(result.data); - return calculateOverageCost( - totalUsage, - includedTraffic, - BILLING_TRAFFIC_BUNDLE_PRICE, - ); - }, [includedTraffic, JSON.stringify(result)]); + return calculateOverageCost(totalUsage, includedTraffic, trafficPrice); + }, [includedTraffic, JSON.stringify(result), trafficPrice]); return overageCost; }; diff --git a/frontend/src/component/admin/network/NetworkTrafficUsage/hooks/useStats.ts b/frontend/src/component/admin/network/NetworkTrafficUsage/hooks/useStats.ts index c34f4ab910..eec6922988 100644 --- a/frontend/src/component/admin/network/NetworkTrafficUsage/hooks/useStats.ts +++ b/frontend/src/component/admin/network/NetworkTrafficUsage/hooks/useStats.ts @@ -12,10 +12,11 @@ import { calculateOverageCost, calculateTotalUsage, } from 'utils/traffic-calculations'; -import { BILLING_TRAFFIC_BUNDLE_PRICE } from '../../../billing/BillingDashboard/BillingPlan/BillingPlan'; +import { BILLING_TRAFFIC_PRICE } from '../../../billing/BillingDashboard/BillingPlan/BillingPlan'; import { averageTrafficPreviousMonths } from '../average-traffic-previous-months'; import { useConnectionsConsumption } from 'hooks/api/getters/useConnectionsConsumption/useConnectionsConsumption'; import { useRequestsConsumption } from 'hooks/api/getters/useRequestsConsumption/useRequestsConsumption'; +import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus'; export const useTrafficStats = ( includedTraffic: number, @@ -26,6 +27,12 @@ export const useTrafficStats = ( chartDataSelection.grouping, toDateRange(chartDataSelection, currentDate), ); + const { instanceStatus } = useInstanceStatus(); + const trafficPrice = + instanceStatus?.prices?.[ + instanceStatus?.billing === 'pay-as-you-go' ? 'payg' : 'pro' + ]?.traffic ?? BILLING_TRAFFIC_PRICE; + const results = useMemo(() => { if (result.state !== 'success') { return { @@ -43,14 +50,14 @@ export const useTrafficStats = ( const overageCost = calculateOverageCost( usageTotal, includedTraffic, - BILLING_TRAFFIC_BUNDLE_PRICE, + trafficPrice, ); const estimatedMonthlyCost = calculateEstimatedMonthlyCost( traffic.apiData, includedTraffic, currentDate, - BILLING_TRAFFIC_BUNDLE_PRICE, + trafficPrice, ); const requestSummaryUsage = @@ -69,6 +76,7 @@ export const useTrafficStats = ( JSON.stringify(result), includedTraffic, JSON.stringify(chartDataSelection), + trafficPrice, ]); return results; diff --git a/frontend/src/component/admin/users/CreateUser/SeatCostWarning/SeatCostWarning.tsx b/frontend/src/component/admin/users/CreateUser/SeatCostWarning/SeatCostWarning.tsx index 0ef6df601a..62d20034c8 100644 --- a/frontend/src/component/admin/users/CreateUser/SeatCostWarning/SeatCostWarning.tsx +++ b/frontend/src/component/admin/users/CreateUser/SeatCostWarning/SeatCostWarning.tsx @@ -2,11 +2,15 @@ import type { VFC } from 'react'; import { Alert } from '@mui/material'; import { useUsersPlan } from 'hooks/useUsersPlan'; import { useUsers } from 'hooks/api/getters/useUsers/useUsers'; -import { BILLING_PRO_USER_PRICE } from 'component/admin/billing/BillingDashboard/BillingPlan/BillingPlan'; +import { BILLING_PRO_SEAT_PRICE } from 'component/admin/billing/BillingDashboard/BillingPlan/BillingPlan'; +import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus'; export const SeatCostWarning: VFC = () => { const { users } = useUsers(); const { isBillingUsers, seats, planUsers } = useUsersPlan(users); + const { instanceStatus } = useInstanceStatus(); + const seatPrice = + instanceStatus?.prices?.pro?.seat ?? BILLING_PRO_SEAT_PRICE; if (!isBillingUsers || planUsers.length < seats) { return null; @@ -20,9 +24,8 @@ export const SeatCostWarning: VFC = () => {

Heads up! You are exceeding your allocated free members included in your plan ({planUsers.length} of {seats}). - Creating this user will add{' '} - ${BILLING_PRO_USER_PRICE}/month to your - invoice, starting with your next payment. + Creating this user will add ${seatPrice}/month{' '} + to your invoice, starting with your next payment.

); diff --git a/frontend/src/component/demo/DemoDialog/DemoDialogPlans/DemoDialogPlans.tsx b/frontend/src/component/demo/DemoDialog/DemoDialogPlans/DemoDialogPlans.tsx index 6728914ea5..c038ef3e27 100644 --- a/frontend/src/component/demo/DemoDialog/DemoDialogPlans/DemoDialogPlans.tsx +++ b/frontend/src/component/demo/DemoDialog/DemoDialogPlans/DemoDialogPlans.tsx @@ -6,11 +6,11 @@ import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; import { useUiFlag } from 'hooks/useUiFlag'; import { BILLING_PAYG_DEFAULT_MINIMUM_SEATS, - BILLING_PAYG_USER_PRICE, - BILLING_PLAN_PRICES, + BILLING_PAYG_SEAT_PRICE, + BILLING_PRO_BASE_PRICE, BILLING_PRO_DEFAULT_INCLUDED_SEATS, } from 'component/admin/billing/BillingDashboard/BillingPlan/BillingPlan'; -import { InstancePlan } from 'interfaces/instance'; +import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus'; const StyledDemoDialog = styled(DemoDialog)(({ theme }) => ({ '& .MuiDialog-paper': { @@ -91,6 +91,12 @@ interface IDemoDialogPlansProps { export const DemoDialogPlans = ({ open, onClose }: IDemoDialogPlansProps) => { const { trackEvent } = usePlausibleTracker(); const isEnterprisePaygEnabled = useUiFlag('enterprise-payg'); + const { instanceStatus } = useInstanceStatus(); + + const paygSeatPrice = + instanceStatus?.prices?.payg?.seat ?? BILLING_PAYG_SEAT_PRICE; + const proBasePrice = + instanceStatus?.prices?.pro?.base ?? BILLING_PRO_BASE_PRICE; return ( @@ -139,7 +145,7 @@ export const DemoDialogPlans = ({ open, onClose }: IDemoDialogPlansProps) => {
- ${BILLING_PAYG_USER_PRICE} per user/month + ${paygSeatPrice} per user/month {BILLING_PAYG_DEFAULT_MINIMUM_SEATS} users @@ -174,7 +180,7 @@ export const DemoDialogPlans = ({ open, onClose }: IDemoDialogPlansProps) => {
- ${BILLING_PLAN_PRICES[InstancePlan.PRO]}/month + ${proBasePrice}/month includes {BILLING_PRO_DEFAULT_INCLUDED_SEATS}{' '} diff --git a/frontend/src/interfaces/instance.ts b/frontend/src/interfaces/instance.ts index 5a03e1e91b..c06eef0e63 100644 --- a/frontend/src/interfaces/instance.ts +++ b/frontend/src/interfaces/instance.ts @@ -1,3 +1,17 @@ +type InstancePrices = { + pro?: { + base?: number; + seat?: number; + traffic?: number; + }; + payg?: { + seat?: number; + traffic?: number; + }; +}; + +type InstanceBilling = 'pay-as-you-go' | 'subscription'; + export interface IInstanceStatus { plan: InstancePlan; trialExpiry?: string; @@ -8,6 +22,8 @@ export interface IInstanceStatus { seats?: number; minSeats?: number; isCustomBilling?: boolean; + prices?: InstancePrices; + billing?: InstanceBilling; } export enum InstanceState {