From ebc283f694bd5ca41310c91b22059ea326067651 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Mon, 20 Jan 2025 14:42:39 +0100 Subject: [PATCH 1/8] feat(1-3262): begin impl of new month/range picker --- .../NetworkTrafficUsage.1.tsx | 0 .../NetworkTrafficUsage.tsx | 6 +- .../NetworkTrafficUsage/PeriodSelector.tsx | 167 +++++++++++++++++ .../useInstanceTrafficMetrics.ts | 172 +++++++++++++++++- frontend/src/hooks/useTrafficData.ts | 3 +- 5 files changed, 343 insertions(+), 5 deletions(-) create mode 100644 frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.1.tsx create mode 100644 frontend/src/component/admin/network/NetworkTrafficUsage/PeriodSelector.tsx diff --git a/frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.1.tsx b/frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.1.tsx new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.tsx b/frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.tsx index 77ac24177809..7bcc649026c2 100644 --- a/frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.tsx +++ b/frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.tsx @@ -1,4 +1,4 @@ -import { useMemo, type VFC, useState, useEffect } from 'react'; +import { useMemo, useState, useEffect, type FC } from 'react'; import useTheme from '@mui/material/styles/useTheme'; import styled from '@mui/material/styles/styled'; import { usePageTitle } from 'hooks/usePageTitle'; @@ -34,6 +34,7 @@ import { formatTickValue } from 'component/common/Chart/formatTickValue'; import { useTrafficLimit } from './hooks/useTrafficLimit'; import { BILLING_TRAFFIC_BUNDLE_PRICE } from 'component/admin/billing/BillingDashboard/BillingPlan/BillingPlan'; import { useLocationSettings } from 'hooks/useLocationSettings'; +import { PeriodSelector } from './PeriodSelector'; const StyledBox = styled(Box)(({ theme }) => ({ display: 'grid', @@ -139,7 +140,7 @@ const createBarChartOptions = ( }, }); -export const NetworkTrafficUsage: VFC = () => { +export const NetworkTrafficUsage: FC = () => { usePageTitle('Network - Data Usage'); const theme = useTheme(); @@ -278,6 +279,7 @@ export const NetworkTrafficUsage: VFC = () => { estimatedMonthlyCost={estimatedMonthlyCost} /> + + console.log( + 'clicked', + e, + e.target.value, + ) + } + /> + + + ))} + + + + ); +}; diff --git a/frontend/src/hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics.ts b/frontend/src/hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics.ts index b95d82d68c34..0036af0c0f0f 100644 --- a/frontend/src/hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics.ts +++ b/frontend/src/hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics.ts @@ -24,7 +24,7 @@ export const useInstanceTrafficMetrics = ( return useMemo( () => ({ - usage: data, + usage: useInstanceTrafficMetricsMonths2(4), loading: !error && !data, refetch: () => mutate(), error, @@ -38,3 +38,173 @@ const fetcher = (path: string) => { .then(handleErrorResponses('Instance Metrics')) .then((res) => res.json()); }; + +export const useInstanceTrafficMetricsMonths2 = (_monthsBack: number) => { + const mockMonthData = [ + { + apiPath: '/api/admin', + months: [ + { + month: '2025-01', + trafficTypes: [ + { + group: 'successful-requests', + count: 535, + }, + ], + }, + { + month: '2024-12', + trafficTypes: [ + { + group: 'successful-requests', + count: 240, + }, + ], + }, + { + month: '2024-11', + trafficTypes: [ + { + group: 'successful-requests', + count: 180, // Example count + }, + ], + }, + { + month: '2024-10', + trafficTypes: [ + { + group: 'successful-requests', + count: 200, // Example count + }, + ], + }, + ], + }, + { + apiPath: '/edge', + months: [ + { + month: '2025-01', + trafficTypes: [ + { + group: 'successful-requests', + count: 535, + }, + ], + }, + { + month: '2024-12', + trafficTypes: [ + { + group: 'successful-requests', + count: 240, + }, + ], + }, + { + month: '2024-11', + trafficTypes: [ + { + group: 'successful-requests', + count: 180, // Example count + }, + ], + }, + { + month: '2024-10', + trafficTypes: [ + { + group: 'successful-requests', + count: 200, // Example count + }, + ], + }, + ], + }, + { + apiPath: '/api/frontend', + months: [ + { + month: '2025-01', + trafficTypes: [ + { + group: 'successful-requests', + count: 535, + }, + ], + }, + { + month: '2024-12', + trafficTypes: [ + { + group: 'successful-requests', + count: 240, + }, + ], + }, + { + month: '2024-11', + trafficTypes: [ + { + group: 'successful-requests', + count: 180, // Example count + }, + ], + }, + { + month: '2024-10', + trafficTypes: [ + { + group: 'successful-requests', + count: 200, // Example count + }, + ], + }, + ], + }, + { + apiPath: '/api/client', + months: [ + { + month: '2025-01', + trafficTypes: [ + { + group: 'successful-requests', + count: 535, + }, + ], + }, + { + month: '2024-12', + trafficTypes: [ + { + group: 'successful-requests', + count: 240, + }, + ], + }, + { + month: '2024-11', + trafficTypes: [ + { + group: 'successful-requests', + count: 180, // Example count + }, + ], + }, + { + month: '2024-10', + trafficTypes: [ + { + group: 'successful-requests', + count: 200, // Example count + }, + ], + }, + ], + }, + ]; + return mockMonthData; +}; diff --git a/frontend/src/hooks/useTrafficData.ts b/frontend/src/hooks/useTrafficData.ts index 5e8ebd90a374..b2aefe0598df 100644 --- a/frontend/src/hooks/useTrafficData.ts +++ b/frontend/src/hooks/useTrafficData.ts @@ -48,8 +48,7 @@ const calculateTrafficDataCost = ( return unitCount * trafficUnitCost; }; -const padMonth = (month: number): string => - month < 10 ? `0${month}` : `${month}`; +const padMonth = (month: number): string => month.toString().padStart(2, '0'); export const toSelectablePeriod = ( date: Date, From 368c303cacd106002cb70bc2ff0d7ae65de3f75a Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Mon, 20 Jan 2025 15:19:20 +0100 Subject: [PATCH 2/8] feat(1-3262): rework to normal buttons instead --- .../NetworkTrafficUsage/PeriodSelector.tsx | 128 +++++++++++------- 1 file changed, 77 insertions(+), 51 deletions(-) diff --git a/frontend/src/component/admin/network/NetworkTrafficUsage/PeriodSelector.tsx b/frontend/src/component/admin/network/NetworkTrafficUsage/PeriodSelector.tsx index 70bc8f368b74..7ae68db37f08 100644 --- a/frontend/src/component/admin/network/NetworkTrafficUsage/PeriodSelector.tsx +++ b/frontend/src/component/admin/network/NetworkTrafficUsage/PeriodSelector.tsx @@ -1,5 +1,5 @@ import { styled } from '@mui/material'; -import { ScreenReaderOnly } from 'component/common/ScreenReaderOnly/ScreenReaderOnly'; +import { useState } from 'react'; export type Period = { key: string; @@ -62,35 +62,27 @@ const getSelectablePeriods = (): Period[] => { const Wrapper = styled('article')(({ theme }) => ({ marginTop: '2rem', // temporary borderRadius: theme.shape.borderRadiusLarge, - border: `1px solid ${theme.palette.divider}`, - paddingInline: theme.spacing(1), - paddingBlock: theme.spacing(2.5), + border: `2px solid ${theme.palette.divider}`, + padding: theme.spacing(3), })); -const Selector = styled('fieldset')(({ theme }) => ({ - '&:focus-within': { - bakgroundColor: 'blue', - }, - label: { +const MonthSelector = styled('article')(({ theme }) => ({ + button: { cursor: 'pointer', + border: 'none', + background: 'none', + fontSize: theme.typography.body1.fontSize, + paddingBlock: theme.spacing(0.5), + + '&.selected': { + backgroundColor: theme.palette.secondary.light, + }, }, - 'label:focus-within': { - backgroundColor: 'red', - }, - 'label:has(input:checked)': { - backgroundColor: theme.palette.secondary.light, - }, - 'label:has(input:disabled)': { - color: theme.palette.text.disabled, + 'button:disabled': { cursor: 'default', }, - input: { - display: 'none', - }, border: 'none', - // borderRadius: theme.shape.borderRadiusLarge, - // borderColor: theme.palette.divider, - legend: { + hgroup: { h3: { margin: 0, fontSize: theme.typography.h3.fontSize, @@ -99,23 +91,28 @@ const Selector = styled('fieldset')(({ theme }) => ({ color: theme.palette.text.secondary, fontSize: theme.typography.body2.fontSize, }, - // position: 'absolute', - // top: '-.6em', + + marginBottom: theme.spacing(2), }, })); const MonthGrid = styled('div')(({ theme }) => ({ + // should be a list under the hood? display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', - rowGap: theme.spacing(1), - columnGap: theme.spacing(2), - label: { + rowGap: theme.spacing(2), + columnGap: theme.spacing(3), + button: { textAlign: 'center', padding: theme.spacing(0.2), borderRadius: theme.shape.borderRadius, }, })); +const RangeSelector = styled('article')(({ theme }) => ({ + // border: 'none' // just something +})); + type Selection = | { type: 'month'; @@ -128,40 +125,69 @@ type Selection = export const PeriodSelector = () => { const selectablePeriods = getSelectablePeriods(); - console.log(selectablePeriods); + const [selection, setSelection] = useState< + { type: 'month'; value: string } | { type: 'range'; value: number } + >({ + type: 'month', + value: currentPeriod.key, + }); + + const rangeOptions = [3, 6, 12].map((monthsBack) => ({ + value: monthsBack, + label: `Last ${monthsBack} months`, + })); + + console.log(rangeOptions); + return ( - - + +

Select month

Last 12 months

- +
{selectablePeriods.map((period, index) => ( - + ))} -
+ + +

Range

+ +
    + {rangeOptions.map((option) => ( +
  • + +
  • + ))} +
+
); }; From 80eee2bcbe88f4ea5e85ce68c15f9dfae7b37998 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Tue, 21 Jan 2025 09:17:45 +0100 Subject: [PATCH 3/8] feat(1-3262): impl design --- .../NetworkTrafficUsage/PeriodSelector.tsx | 101 +++++++++++------- 1 file changed, 64 insertions(+), 37 deletions(-) diff --git a/frontend/src/component/admin/network/NetworkTrafficUsage/PeriodSelector.tsx b/frontend/src/component/admin/network/NetworkTrafficUsage/PeriodSelector.tsx index 7ae68db37f08..bb00d135db5d 100644 --- a/frontend/src/component/admin/network/NetworkTrafficUsage/PeriodSelector.tsx +++ b/frontend/src/component/admin/network/NetworkTrafficUsage/PeriodSelector.tsx @@ -64,15 +64,16 @@ const Wrapper = styled('article')(({ theme }) => ({ borderRadius: theme.shape.borderRadiusLarge, border: `2px solid ${theme.palette.divider}`, padding: theme.spacing(3), -})); - -const MonthSelector = styled('article')(({ theme }) => ({ + display: 'flex', + flexFlow: 'column', + gap: theme.spacing(2), button: { cursor: 'pointer', border: 'none', background: 'none', fontSize: theme.typography.body1.fontSize, - paddingBlock: theme.spacing(0.5), + padding: theme.spacing(0.5), + borderRadius: theme.shape.borderRadius, '&.selected': { backgroundColor: theme.palette.secondary.light, @@ -81,6 +82,9 @@ const MonthSelector = styled('article')(({ theme }) => ({ 'button:disabled': { cursor: 'default', }, +})); + +const MonthSelector = styled('article')(({ theme }) => ({ border: 'none', hgroup: { h3: { @@ -92,25 +96,40 @@ const MonthSelector = styled('article')(({ theme }) => ({ fontSize: theme.typography.body2.fontSize, }, - marginBottom: theme.spacing(2), + marginBottom: theme.spacing(1), }, })); -const MonthGrid = styled('div')(({ theme }) => ({ - // should be a list under the hood? +const MonthGrid = styled('ul')(({ theme }) => ({ + listStyle: 'none', + padding: 0, display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', - rowGap: theme.spacing(2), - columnGap: theme.spacing(3), - button: { - textAlign: 'center', - padding: theme.spacing(0.2), - borderRadius: theme.shape.borderRadius, - }, + rowGap: theme.spacing(1), + columnGap: theme.spacing(2), })); const RangeSelector = styled('article')(({ theme }) => ({ - // border: 'none' // just something + display: 'flex', + flexFlow: 'column', + gap: theme.spacing(0.5), + h4: { + fontSize: theme.typography.body2.fontSize, + margin: 0, + color: theme.palette.text.secondary, + }, +})); + +const RangeList = styled('ul')(({ theme }) => ({ + listStyle: 'none', + padding: 0, + 'li + li': { + marginTop: theme.spacing(1), + }, + + button: { + marginLeft: `-${theme.spacing(0.5)}`, + }, })); type Selection = @@ -125,9 +144,7 @@ type Selection = export const PeriodSelector = () => { const selectablePeriods = getSelectablePeriods(); - const [selection, setSelection] = useState< - { type: 'month'; value: string } | { type: 'range'; value: number } - >({ + const [selection, setSelection] = useState({ type: 'month', value: currentPeriod.key, }); @@ -148,37 +165,47 @@ export const PeriodSelector = () => { {selectablePeriods.map((period, index) => ( - +
  • + +
  • ))}

    Range

    -
      + {rangeOptions.map((option) => (
    • ))} -
    +
    ); From 15761caed72eda6e31d80bbcb7ab926212992a96 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Tue, 21 Jan 2025 09:35:29 +0100 Subject: [PATCH 4/8] feat(1-3262): add flag --- frontend/src/interfaces/uiConfig.ts | 1 + src/lib/types/experimental.ts | 7 ++++++- src/server-dev.ts | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts index 1410afbe8444..580ad39d5402 100644 --- a/frontend/src/interfaces/uiConfig.ts +++ b/frontend/src/interfaces/uiConfig.ts @@ -93,6 +93,7 @@ export type UiFlags = { sortProjectRoles?: boolean; lifecycleImprovements?: boolean; frontendHeaderRedesign?: boolean; + dataUsageMultiMonthView?: boolean; }; export interface IVersionInfo { diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts index ae4620749fd4..8fdca16f62a6 100644 --- a/src/lib/types/experimental.ts +++ b/src/lib/types/experimental.ts @@ -63,7 +63,8 @@ export type IFlagKey = | 'uniqueSdkTracking' | 'sortProjectRoles' | 'lifecycleImprovements' - | 'frontendHeaderRedesign'; + | 'frontendHeaderRedesign' + | 'dataUsageMultiMonthView'; export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>; @@ -300,6 +301,10 @@ const flags: IFlags = { process.env.UNLEASH_EXPERIMENTAL_FRONTEND_HEADER_REDESIGN, false, ), + dataUsageMultiMonthView: parseEnvVarBoolean( + process.env.UNLEASH_EXPERIMENTAL_DATA_USAGE_MULTI_MONTH_VIEW, + false, + ), }; export const defaultExperimentalOptions: IExperimentalOptions = { diff --git a/src/server-dev.ts b/src/server-dev.ts index 72d9c3e04f7f..948f47de757e 100644 --- a/src/server-dev.ts +++ b/src/server-dev.ts @@ -58,6 +58,7 @@ process.nextTick(async () => { uniqueSdkTracking: true, lifecycleImprovements: true, frontendHeaderRedesign: true, + dataUsageMultiMonthView: true, }, }, authentication: { From f020e271218955217d3c8f31cf3c98666aaf1078 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Tue, 21 Jan 2025 09:56:42 +0100 Subject: [PATCH 5/8] feat(1-3262): hook up new component to existing dates --- .../NetworkTrafficUsage.tsx | 61 +++++++++++++------ .../NetworkTrafficUsage/PeriodSelector.tsx | 40 +++++++----- 2 files changed, 70 insertions(+), 31 deletions(-) diff --git a/frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.tsx b/frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.tsx index 7bcc649026c2..113ccd592616 100644 --- a/frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.tsx +++ b/frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.tsx @@ -35,6 +35,7 @@ import { useTrafficLimit } from './hooks/useTrafficLimit'; import { BILLING_TRAFFIC_BUNDLE_PRICE } from 'component/admin/billing/BillingDashboard/BillingPlan/BillingPlan'; import { useLocationSettings } from 'hooks/useLocationSettings'; import { PeriodSelector } from './PeriodSelector'; +import { useUiFlag } from 'hooks/useUiFlag'; const StyledBox = styled(Box)(({ theme }) => ({ display: 'grid', @@ -140,9 +141,17 @@ const createBarChartOptions = ( }, }); +// this is primarily for dev purposes. The existing grid is very inflexible, so we might want to change it, but for demoing the design, this is enough. +const NewHeader = styled('div')(() => ({ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'flex-start', +})); + export const NetworkTrafficUsage: FC = () => { usePageTitle('Network - Data Usage'); const theme = useTheme(); + const showMultiMonthSelector = useUiFlag('dataUsageMultiMonthView'); const { isOss } = useUiConfig(); @@ -270,31 +279,49 @@ export const NetworkTrafficUsage: FC = () => { } /> - - + {showMultiMonthSelector ? ( + - - - - + setPeriod(e.target.value) + } + style={{ + minWidth: '100%', + marginBottom: theme.spacing(2), + }} + formControlStyles={{ width: '100%' }} + /> + -
    + )} { - // const { locationSettings } = useLocationSettings(); const year = date.getFullYear(); const month = date.getMonth(); const period = `${year}-${(month + 1).toString().padStart(2, '0')}`; @@ -60,7 +59,6 @@ const getSelectablePeriods = (): Period[] => { }; const Wrapper = styled('article')(({ theme }) => ({ - marginTop: '2rem', // temporary borderRadius: theme.shape.borderRadiusLarge, border: `2px solid ${theme.palette.divider}`, padding: theme.spacing(3), @@ -142,12 +140,25 @@ type Selection = monthsBack: number; }; -export const PeriodSelector = () => { +type Props = { + selectedPeriod: string; + setPeriod: (period: string) => void; +}; + +export const PeriodSelector: FC = ({ selectedPeriod, setPeriod }) => { const selectablePeriods = getSelectablePeriods(); - const [selection, setSelection] = useState({ - type: 'month', - value: currentPeriod.key, - }); + + // this is for dev purposes; only to show how the design will work when you select a range. + const [tempOverride, setTempOverride] = useState(); + + const select = (value: Selection) => { + if (value.type === 'month') { + setTempOverride(null); + setPeriod(value.value); + } else { + setTempOverride(value); + } + }; const rangeOptions = [3, 6, 12].map((monthsBack) => ({ value: monthsBack, @@ -168,15 +179,15 @@ export const PeriodSelector = () => {