From d4ec473d8a50bea346c1f94c38aed4d6707d880f Mon Sep 17 00:00:00 2001 From: Ammar Alakkad Date: Mon, 1 Sep 2025 11:16:53 +0300 Subject: [PATCH 1/2] Add usage_trends_chart component --- .../components/usage_trends_chart.vue | 132 ++++++++++++++++++ .../components/usage_trends_chart_spec.js | 86 ++++++++++++ locale/gitlab.pot | 27 ++++ 3 files changed, 245 insertions(+) create mode 100644 ee/app/assets/javascripts/usage_quotas/usage_billing/components/usage_trends_chart.vue create mode 100644 ee/spec/frontend/usage_quotas/usage_billing/components/usage_trends_chart_spec.js diff --git a/ee/app/assets/javascripts/usage_quotas/usage_billing/components/usage_trends_chart.vue b/ee/app/assets/javascripts/usage_quotas/usage_billing/components/usage_trends_chart.vue new file mode 100644 index 00000000000000..9ad53076e6a6ff --- /dev/null +++ b/ee/app/assets/javascripts/usage_quotas/usage_billing/components/usage_trends_chart.vue @@ -0,0 +1,132 @@ + + diff --git a/ee/spec/frontend/usage_quotas/usage_billing/components/usage_trends_chart_spec.js b/ee/spec/frontend/usage_quotas/usage_billing/components/usage_trends_chart_spec.js new file mode 100644 index 00000000000000..a449415b6f9e39 --- /dev/null +++ b/ee/spec/frontend/usage_quotas/usage_billing/components/usage_trends_chart_spec.js @@ -0,0 +1,86 @@ +import { GlAreaChart } from '@gitlab/ui/src/charts'; +import { GlBadge } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import UsageTrendsChart from 'ee/usage_quotas/usage_billing/components/usage_trends_chart.vue'; + +describe('UsageTrendsChart', () => { + let wrapper; + + const defaultProps = { + usageData: [ + ['2025-07-01', '1000'], + ['2025-07-02', '200'], + ], + monthStartDate: '2025-07-01', + monthEndDate: '2025-07-31', + }; + + const createComponent = (props = {}) => { + wrapper = shallowMountExtended(UsageTrendsChart, { + propsData: { ...defaultProps, ...props }, + }); + }; + + describe('rendering elements', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders formatted month date range', () => { + expect(wrapper.findByTestId('chart-heading').text()).toBe('Jul 1 – 31, 2025'); + }); + + it('passes the correct `option` prop to the gl-area-chart', () => { + expect(wrapper.findComponent(GlAreaChart).props('option')).toMatchObject({ + xAxis: { name: 'Date', type: 'category' }, + yAxis: { name: 'Tokens' }, + }); + }); + + it('passes the correct chartData to the gl-area-chart', () => { + expect(wrapper.findComponent(GlAreaChart).props('data')).toMatchObject([ + { + name: 'Daily usage', + data: [ + ['2025-07-01', '1000'], + ['2025-07-02', '200'], + ], + }, + ]); + }); + }); + + describe('trend changes', () => { + it.each` + trend | badgeVariant | badgeIcon + ${0.9} | ${'success'} | ${'trend-up'} + ${0} | ${'danger'} | ${'trend-down'} + ${0.2} | ${'neutral'} | ${'trend-static'} + `( + 'pass the correct variant and icon to the badge when trend = $trend', + ({ trend, badgeVariant, badgeIcon }) => { + createComponent({ trend }); + + expect(wrapper.findComponent(GlBadge).props()).toMatchObject({ + variant: badgeVariant, + icon: badgeIcon, + }); + }, + ); + + it.each` + trend | className + ${0.9} | ${'gl-text-green-500'} + ${0} | ${'gl-text-red-500'} + ${0.2} | ${''} + `( + 'pass the correct class to the usage trend title when trend = $trend', + ({ trend, className }) => { + createComponent({ trend }); + + const title = wrapper.findByTestId('usage-trend-title'); + expect(title.attributes('class')).toContain(className); + }, + ); + }); +}); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index aad31452156b17..ee2eda13d245c6 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -69085,21 +69085,48 @@ msgstr "" msgid "UsageBilling|An error occurred while fetching data" msgstr "" +msgid "UsageBilling|Current month" +msgstr "" + msgid "UsageBilling|Current month usage" msgstr "" +msgid "UsageBilling|Custom dates" +msgstr "" + +msgid "UsageBilling|Daily average use" +msgstr "" + +msgid "UsageBilling|Daily usage" +msgstr "" + msgid "UsageBilling|Increase monthly commitment" msgstr "" +msgid "UsageBilling|Last 3 months" +msgstr "" + +msgid "UsageBilling|Last month" +msgstr "" + +msgid "UsageBilling|Peak daily use" +msgstr "" + msgid "UsageBilling|Purchase a monthly commitment" msgstr "" +msgid "UsageBilling|Tokens" +msgstr "" + msgid "UsageBilling|Usage Billing" msgstr "" msgid "UsageBilling|Usage by user" msgstr "" +msgid "UsageBilling|Usage trend" +msgstr "" + msgid "UsageBilling|Usage trends" msgstr "" -- GitLab From c667d0de0c0f9250a4517915e5f5e4f88efa11a9 Mon Sep 17 00:00:00 2001 From: Ammar Alakkad Date: Mon, 1 Sep 2025 11:17:08 +0300 Subject: [PATCH 2/2] Usage usage_trends_chart in usage app --- .../usage_billing/components/app.vue | 37 ++++++++++++++++++- .../usage_billing/components/app_spec.js | 15 ++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/ee/app/assets/javascripts/usage_quotas/usage_billing/components/app.vue b/ee/app/assets/javascripts/usage_quotas/usage_billing/components/app.vue index a21423da1d1909..e1e3e5707d5bb5 100644 --- a/ee/app/assets/javascripts/usage_quotas/usage_billing/components/app.vue +++ b/ee/app/assets/javascripts/usage_quotas/usage_billing/components/app.vue @@ -6,6 +6,7 @@ import axios from '~/lib/utils/axios_utils'; import { captureException } from '~/sentry/sentry_browser_wrapper'; import PageHeading from '~/vue_shared/components/page_heading.vue'; import PurchaseCommitmentCard from './purchase_commitment_card.vue'; +import UsageTrendsChart from './usage_trends_chart.vue'; export default { name: 'UsageBillingApp', @@ -16,6 +17,7 @@ export default { GlTab, PageHeading, PurchaseCommitmentCard, + UsageTrendsChart, }, data() { return { @@ -24,6 +26,32 @@ export default { subscriptionData: null, }; }, + computed: { + gitlabUnitsUsage() { + return this.subscriptionData.gitlabUnitsUsage; + }, + trend() { + return ( + this.gitlabUnitsUsage.poolUsage?.usageTrend || this.gitlabUnitsUsage.seatUsage?.usageTrend + ); + }, + dailyUsage() { + return ( + this.gitlabUnitsUsage.poolUsage?.dailyUsage || this.gitlabUnitsUsage.seatUsage?.dailyUsage + ); + }, + dailyPeak() { + return ( + this.gitlabUnitsUsage.poolUsage?.peakUsage || this.gitlabUnitsUsage.seatUsage?.peakUsage + ); + }, + dailyAverage() { + return ( + this.gitlabUnitsUsage.poolUsage?.dailyAverage || + this.gitlabUnitsUsage.seatUsage?.dailyAverage + ); + }, + }, async mounted() { await this.fetchUsageData(); }, @@ -99,7 +127,14 @@ export default { - {{ s__('UsageBilling|Usage trends') }} + {{ s__('UsageBilling|Usage by user') }} diff --git a/ee/spec/frontend/usage_quotas/usage_billing/components/app_spec.js b/ee/spec/frontend/usage_quotas/usage_billing/components/app_spec.js index 6e59402a5cf7bd..6587ebcae3ae0e 100644 --- a/ee/spec/frontend/usage_quotas/usage_billing/components/app_spec.js +++ b/ee/spec/frontend/usage_quotas/usage_billing/components/app_spec.js @@ -6,6 +6,8 @@ import waitForPromises from 'helpers/wait_for_promises'; import { logError } from '~/lib/logger'; import axios from '~/lib/utils/axios_utils'; import { captureException } from '~/sentry/sentry_browser_wrapper'; +import PurchaseCommitmentCard from 'ee/usage_quotas/usage_billing/components/purchase_commitment_card.vue'; +import UsageTrendsChart from 'ee/usage_quotas/usage_billing/components/usage_trends_chart.vue'; import { mockUsageDataWithPool } from '../mock_data'; jest.mock('~/lib/logger'); @@ -62,12 +64,25 @@ describe('UsageBillingApp', () => { ); }); + it('renders purchase-commitment-card', () => { + expect(wrapper.findComponent(PurchaseCommitmentCard).exists()).toBe(true); + }); + it('renders the correct tabs', () => { const tabs = findTabs(); expect(tabs.at(0).attributes('title')).toBe('Usage trends'); expect(tabs.at(1).attributes('title')).toBe('Usage by user'); }); + + it('renders usage trends chart with correct props', () => { + expect(wrapper.findComponent(UsageTrendsChart).props()).toMatchObject({ + monthStartDate: '2024-01-01', + monthEndDate: '2024-01-31', + trend: 0.12, + }); + expect(wrapper.findComponent(UsageTrendsChart).props('usageData')).toHaveLength(30); + }); }); describe('error state', () => { -- GitLab