mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-05 04:16:31 +02:00
Merge remote-tracking branch 'remotes/from/ce/main'
This commit is contained in:
commit
2c4d9993bb
@ -30,7 +30,10 @@
|
||||
{{#if @isSelectedDateInvalid}}
|
||||
No data available.
|
||||
{{else}}
|
||||
Values update every 10 minutes. Last updated:
|
||||
{{#if @isCurrentMonth}}
|
||||
Values update every 10 minutes.
|
||||
{{/if}}
|
||||
Last updated:
|
||||
{{date-format @selectedDateOption.updated_at "MMMM d, yyyy, hh:mm:ss aaa" withTimeZone=true}}
|
||||
{{/if}}
|
||||
</Hds::Text::Body>
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
@onDateChange={{this.onDateChange}}
|
||||
@selectedDateOption={{this.selectedDate}}
|
||||
@isSelectedDateInvalid={{this.isSelectedDateInvalid}}
|
||||
@isCurrentMonth={{this.isCurrentMonth}}
|
||||
/>
|
||||
|
||||
<Hds::Layout::Flex @direction="row" @gap="16" @wrap={{true}} class="has-top-margin-s">
|
||||
|
||||
@ -54,7 +54,18 @@ export default class BillingPageOverview extends Component {
|
||||
|
||||
constructor(owner: unknown, args: object) {
|
||||
super(owner, args);
|
||||
this.startPoll();
|
||||
this.initializeBillingMetrics();
|
||||
}
|
||||
|
||||
get isCurrentMonth() {
|
||||
if (!this.selectedDateOption?.month) return false;
|
||||
const selectedDate = new Date(`${this.selectedDateOption.month}-01T00:00:00Z`);
|
||||
const currentDate = new Date();
|
||||
|
||||
return (
|
||||
selectedDate.getUTCFullYear() === currentDate.getUTCFullYear() &&
|
||||
selectedDate.getUTCMonth() === currentDate.getUTCMonth()
|
||||
);
|
||||
}
|
||||
|
||||
get selectedDate() {
|
||||
@ -78,7 +89,7 @@ export default class BillingPageOverview extends Component {
|
||||
fetchBillingMetrics = async () => {
|
||||
const response: SystemReadBillingOverviewResponse | null | undefined =
|
||||
await this.api.sys.systemReadBillingOverview();
|
||||
this.months = (response?.months as Month[]) || [];
|
||||
this.months = (response?.months?.slice(0, 2) as Month[]) || [];
|
||||
const updatedMonthFromSelectedMonth = this.months.find(
|
||||
(month: Month) => month.month === this.selectedDateOption?.month
|
||||
);
|
||||
@ -88,13 +99,26 @@ export default class BillingPageOverview extends Component {
|
||||
this._interval = this.calculatePollingInterval(updatedMonth.updated_at);
|
||||
}
|
||||
|
||||
this.onDateChange(updatedMonth ?? null);
|
||||
this.selectedDateOption = updatedMonth ?? null;
|
||||
this.normalizedMetricData = normalizeMetricData(updatedMonth);
|
||||
return this.months;
|
||||
};
|
||||
|
||||
async initializeBillingMetrics() {
|
||||
await this.fetchBillingMetrics();
|
||||
this.updatePollingState();
|
||||
}
|
||||
|
||||
updatePollingState() {
|
||||
if (this.isCurrentMonth) {
|
||||
this.startPoll();
|
||||
} else {
|
||||
this.stopPoll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the polling loop, invoking fetchBillingMetrics immediately and then
|
||||
* repeatedly on each interval. No-ops if polling is already active.
|
||||
* Starts the polling loop and repeatedly invokes fetchBillingMetrics on each interval.
|
||||
*/
|
||||
startPoll() {
|
||||
if (this._timer) return;
|
||||
@ -111,7 +135,7 @@ export default class BillingPageOverview extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
poll();
|
||||
this._timer = setTimeout(poll, this._interval);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -136,10 +160,16 @@ export default class BillingPageOverview extends Component {
|
||||
};
|
||||
|
||||
@action
|
||||
onDateChange(dropdownOption: Month | null | undefined) {
|
||||
async onDateChange(dropdownOption: Month | null | undefined) {
|
||||
this.selectedDateOption = dropdownOption;
|
||||
|
||||
this.normalizedMetricData = normalizeMetricData(dropdownOption);
|
||||
|
||||
if (this.isCurrentMonth) {
|
||||
this.stopPoll();
|
||||
await this.fetchBillingMetrics();
|
||||
}
|
||||
|
||||
this.updatePollingState();
|
||||
}
|
||||
|
||||
willDestroy() {
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { calculateSum } from 'vault/utils/chart-helpers';
|
||||
|
||||
import type { Month, NormalizedMetricsData } from 'vault/vault/billing/overview';
|
||||
|
||||
export enum NormalizedBillingMetrics {
|
||||
@ -87,11 +89,13 @@ export function normalizeMetricData(metric: Month | null | undefined) {
|
||||
typeof normalized[NormalizedBillingMetrics.ID_TOKEN_UNITS_TOTAL] === 'number'
|
||||
? normalized[NormalizedBillingMetrics.ID_TOKEN_UNITS_TOTAL]
|
||||
: 0;
|
||||
normalized[NormalizedBillingMetrics.CREDENTIAL_UNITS_TOTAL] =
|
||||
sshUnitsTotal + pkiUnitsTotal + idTokenUnitsTotal;
|
||||
normalized[NormalizedBillingMetrics.CREDENTIAL_UNITS_TOTAL] = calculateSum([
|
||||
sshUnitsTotal,
|
||||
pkiUnitsTotal,
|
||||
idTokenUnitsTotal,
|
||||
]);
|
||||
|
||||
// The API omits metrics that have zero usage rather than returning them with a count of 0.
|
||||
// To avoid blank values in the UI, we explicitly set any missing metric keys to 0.
|
||||
// Explicitly set any missing metric keys to 0.
|
||||
for (const metricsKey of Object.values(NormalizedBillingMetrics)) {
|
||||
if (!(metricsKey in normalized)) {
|
||||
normalized[metricsKey] = 0;
|
||||
|
||||
@ -27,6 +27,10 @@ module('Acceptance | billing/overview', function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
this.version = this.owner.lookup('service:version');
|
||||
this.mockMetrics = METRICS_DATA_RESPONSE.data;
|
||||
this.todayDate = new Date();
|
||||
this.currentMonth = this.todayDate.toISOString();
|
||||
this.mockMetrics.months[0].month = dateFormat([this.todayDate, 'yyyy-MM'], {});
|
||||
this.mockMetrics.months[0].updated_at = this.currentMonth;
|
||||
this.server.get('/sys/billing/overview', () => this.mockMetrics);
|
||||
|
||||
// Stub the API service
|
||||
@ -52,14 +56,10 @@ module('Acceptance | billing/overview', function (hooks) {
|
||||
.hasText(
|
||||
'Data reflects usage across this Vault cluster. Billing metrics determine license utilization.'
|
||||
);
|
||||
assert.dom(GENERAL.textBody('Last updated date time')).hasText(
|
||||
`Values update every 10 minutes. Last updated: ${dateFormat(
|
||||
[this.mockMetrics.months[0].updated_at, 'MMMM d, yyyy, hh:mm:ss aaa'],
|
||||
{
|
||||
withTimeZone: true,
|
||||
}
|
||||
)}`
|
||||
);
|
||||
// Vault update every 10 minute only shows if the current month is selected.
|
||||
assert
|
||||
.dom(GENERAL.textBody('Last updated date time'))
|
||||
.hasTextContaining('Values update every 10 minutes.');
|
||||
|
||||
assert.dom(GENERAL.cardContainer('Summary')).exists();
|
||||
|
||||
@ -109,6 +109,17 @@ module('Acceptance | billing/overview', function (hooks) {
|
||||
await logout();
|
||||
});
|
||||
|
||||
test('should not display updated at text if current month is not selected', async function (assert) {
|
||||
this.server.get('/sys/license/features', () => ({ features: ['Consumption Billing'] }));
|
||||
await login();
|
||||
assert.dom(GENERAL.navLink('Billing metrics')).hasText('Billing metrics');
|
||||
await click(GENERAL.navLink('Billing metrics'));
|
||||
assert.strictEqual(currentURL(), '/vault/billing/overview');
|
||||
await click(GENERAL.dropdownToggle('Date range'));
|
||||
await click(GENERAL.menuItem('2025-12'));
|
||||
assert.dom(GENERAL.textBody('Last updated date time')).hasTextContaining('Last updated: January 14');
|
||||
});
|
||||
|
||||
test('display no data available when updated_at is invalid', async function (assert) {
|
||||
this.server.get('/sys/license/features', () => ({ features: ['Consumption Billing'] }));
|
||||
const mockMetricsInvalidDate = { ...this.mockMetrics };
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user