[UI] Add total credential units + GCP KMS in data protection calls (#14312) (#14368) (#14413)

* Add tests!

* Add gcp kms value

* Update to use enum

* Update metric helper test

Co-authored-by: Kianna <30884335+kiannaquach@users.noreply.github.com>
This commit is contained in:
Vault Automation 2026-05-04 08:27:39 -06:00 committed by GitHub
parent 5c64806ef0
commit f0efc6dccb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 86 additions and 8 deletions

View File

@ -18,8 +18,11 @@
<Hds::Text::Body @tag="p">{{or this.total "0"}}</Hds::Text::Body>
</Hds::Layout::Flex::Item>
{{#each-in @metrics as |metricKey metricValue|}}
{{#if (eq metricKey this.normalizedBillableMetrics.ID_TOKEN_UNITS_OIDC)}}
<Hds::Layout::Flex::Item @basis="20%" @grow={{true}} @shrink={{true}} data-test-metric-detail={{metricKey}} />
{{/if}}
{{#let (this.metricDetails metricKey) as |display|}}
<Hds::Layout::Flex::Item @basis="15%" @grow={{true}} @shrink={{true}} data-test-metric-detail={{metricKey}}>
<Hds::Layout::Flex::Item @basis="20%" @grow={{true}} @shrink={{true}} data-test-metric-detail={{metricKey}}>
{{#if display.tooltipText}}
<Hds::Text::Display @tag="h3" @color="foreground-primary">
{{display.label}}

View File

@ -14,6 +14,8 @@ interface Args {
}
export default class MetricCard extends Component<Args> {
normalizedBillableMetrics = NormalizedBillingMetrics;
get total() {
const sums = Object.values(this.args.metrics).filter((metric) => metric !== undefined);
return calculateSum(sums);
@ -55,6 +57,14 @@ export default class MetricCard extends Component<Args> {
tooltipText:
'Total number of SSH one-time passwords issued, normalized by their duration. Each OTP is 0.0014 units.',
},
[NormalizedBillingMetrics.ID_TOKEN_UNITS_OIDC]: {
label: 'OIDC token units',
tooltipText: 'Total number of ID tokens issued, normalized by their duration.',
},
[NormalizedBillingMetrics.ID_TOKEN_UNITS_SPIFFE]: {
label: 'SPIFFE JWT units',
tooltipText: 'Total number of SPIFFE JWT tokens issued, normalized by their duration.',
},
[NormalizedBillingMetrics.SSH_UNITS_CERTIFICATE_UNITS]: {
label: 'SSH certificate units',
tooltipText: 'Total number of SSH certificates issued, normalized by their duration.',
@ -65,6 +75,9 @@ export default class MetricCard extends Component<Args> {
[NormalizedBillingMetrics.DATA_PROTECTION_CALLS_TRANSFORM]: {
label: 'Transform',
},
[NormalizedBillingMetrics.DATA_PROTECTION_CALLS_GCPKMS]: {
label: 'GCP KMS',
},
[NormalizedBillingMetrics.MANAGED_KEYS_TOTP]: {
label: 'TOTP',
},

View File

@ -12,9 +12,6 @@
Data reflects usage across this Vault cluster. Billing metrics determine license utilization.
</Hds::Text::Body>
</:description>
<:actions>
<Hds::Link::Standalone @icon="docs-link" @text="Documentation" @href={{doc-link "/vault/gui/billing-metrics"}} />
</:actions>
</Page::Header>
<Billing::DateRange
@ -42,7 +39,21 @@
</Hds::Layout::Flex::Item>
<Hds::Layout::Flex::Item @basis="60%" @grow={{true}} @shrink={{true}}>
<Hds::Text::Display @tag="h2" @weight="medium" @size="400" class="has-bottom-margin-s">Details by metric</Hds::Text::Display>
<Hds::Layout::Flex
@direction="row"
@gap="16"
@wrap={{true}}
@align="center"
@justify="space-between"
class="has-bottom-margin-s"
>
<Hds::Text::Display @tag="h2" @weight="medium" @size="400">Details by metric</Hds::Text::Display>
<Hds::Link::Standalone
@icon="docs-link"
@text="Learn more about metrics"
@href={{doc-link "/vault/gui/billing-metrics"}}
/>
</Hds::Layout::Flex>
<Hds::Layout::Flex @direction="column" @gap="16">
{{#each-in this.detailsByMetric as |cardTitle cardKey|}}
<Billing::MetricCard @title={{cardTitle}} @metrics={{this.metricsForCard cardKey}} />

View File

@ -41,10 +41,13 @@ export default class BillingPageOverview extends Component {
NormalizedBillingMetrics.PKI_UNITS_TOTAL,
NormalizedBillingMetrics.SSH_UNITS_OTP_UNITS,
NormalizedBillingMetrics.SSH_UNITS_CERTIFICATE_UNITS,
NormalizedBillingMetrics.ID_TOKEN_UNITS_OIDC,
NormalizedBillingMetrics.ID_TOKEN_UNITS_SPIFFE,
],
'Data protection calls': [
NormalizedBillingMetrics.DATA_PROTECTION_CALLS_TRANSFORM,
NormalizedBillingMetrics.DATA_PROTECTION_CALLS_TRANSIT,
NormalizedBillingMetrics.DATA_PROTECTION_CALLS_GCPKMS,
],
'Managed keys': [NormalizedBillingMetrics.MANAGED_KEYS_TOTP, NormalizedBillingMetrics.MANAGED_KEYS_KMSE],
};
@ -135,6 +138,7 @@ export default class BillingPageOverview extends Component {
@action
onDateChange(dropdownOption: Month | null | undefined) {
this.selectedDateOption = dropdownOption;
this.normalizedMetricData = normalizeMetricData(dropdownOption);
}

View File

@ -24,7 +24,7 @@ interface Args {
export default class SummaryCard extends Component<Args> {
summaryMetricKeys = [
NormalizedBillingMetrics.STATIC_SECRETS_TOTAL,
NormalizedBillingMetrics.PKI_UNITS_TOTAL,
NormalizedBillingMetrics.CREDENTIAL_UNITS_TOTAL,
NormalizedBillingMetrics.DATA_PROTECTION_CALLS_TOTAL,
NormalizedBillingMetrics.MANAGED_KEYS_TOTAL,
NormalizedBillingMetrics.KMIP_USED_IN_MONTH,
@ -35,8 +35,8 @@ export default class SummaryCard extends Component<Args> {
[NormalizedBillingMetrics.STATIC_SECRETS_TOTAL]: {
label: 'Secrets',
},
[NormalizedBillingMetrics.PKI_UNITS_TOTAL]: {
label: 'PKI units',
[NormalizedBillingMetrics.CREDENTIAL_UNITS_TOTAL]: {
label: 'Credential units',
},
[NormalizedBillingMetrics.DATA_PROTECTION_CALLS_TOTAL]: {
label: 'Data protection calls',

View File

@ -7,11 +7,16 @@ import type { Month, NormalizedMetricsData } from 'vault/vault/billing/overview'
export enum NormalizedBillingMetrics {
AUTO_ROTATED_ROLES_TOTAL = 'auto_rotated_roles_total',
CREDENTIAL_UNITS_TOTAL = 'credential_units_total',
DATA_PROTECTION_CALLS_TOTAL = 'data_protection_calls_total',
DATA_PROTECTION_CALLS_TRANSFORM = 'data_protection_calls_transform',
DATA_PROTECTION_CALLS_TRANSIT = 'data_protection_calls_transit',
DATA_PROTECTION_CALLS_GCPKMS = 'data_protection_calls_gcpkms',
DYNAMIC_ROLES_TOTAL = 'dynamic_roles_total',
EXTERNAL_PLUGINS_TOTAL = 'external_plugins_total',
ID_TOKEN_UNITS_TOTAL = 'id_token_units_total',
ID_TOKEN_UNITS_OIDC = 'id_token_units_oidc',
ID_TOKEN_UNITS_SPIFFE = 'id_token_units_spiffe',
KMIP_USED_IN_MONTH = 'kmip_used_in_month',
MANAGED_KEYS = 'managed_keys',
MANAGED_KEYS_KMSE = 'managed_keys_kmse',
@ -68,6 +73,23 @@ export function normalizeMetricData(metric: Month | null | undefined) {
normalized[detailName] = detail.count;
}
}
// Calculate credential_units_total as the sum of ssh_units, pki_units, and id_token_units
const sshUnitsTotal =
typeof normalized[NormalizedBillingMetrics.SSH_UNITS_TOTAL] === 'number'
? normalized[NormalizedBillingMetrics.SSH_UNITS_TOTAL]
: 0;
const pkiUnitsTotal =
typeof normalized[NormalizedBillingMetrics.PKI_UNITS_TOTAL] === 'number'
? normalized[NormalizedBillingMetrics.PKI_UNITS_TOTAL]
: 0;
const idTokenUnitsTotal =
typeof normalized[NormalizedBillingMetrics.ID_TOKEN_UNITS_TOTAL] === 'number'
? normalized[NormalizedBillingMetrics.ID_TOKEN_UNITS_TOTAL]
: 0;
normalized[NormalizedBillingMetrics.CREDENTIAL_UNITS_TOTAL] =
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.
for (const metricsKey of Object.values(NormalizedBillingMetrics)) {

View File

@ -81,6 +81,12 @@ module('Acceptance | billing/overview', function (hooks) {
assert
.dom(SELECTORS.metricDetailValue(NormalizedBillingMetrics.SSH_UNITS_CERTIFICATE_UNITS))
.hasText('50.1234');
assert.dom(SELECTORS.metricDetail(NormalizedBillingMetrics.ID_TOKEN_UNITS_OIDC)).exists();
assert.dom(SELECTORS.metricDetailValue(NormalizedBillingMetrics.ID_TOKEN_UNITS_OIDC)).hasText('52.1234');
assert.dom(SELECTORS.metricDetail(NormalizedBillingMetrics.ID_TOKEN_UNITS_SPIFFE)).exists();
assert
.dom(SELECTORS.metricDetailValue(NormalizedBillingMetrics.ID_TOKEN_UNITS_SPIFFE))
.hasText('51.1234');
assert.dom(GENERAL.cardContainer('Data protection calls')).exists();
assert.dom(SELECTORS.metricDetail(NormalizedBillingMetrics.DATA_PROTECTION_CALLS_TRANSFORM)).exists();
@ -91,6 +97,9 @@ module('Acceptance | billing/overview', function (hooks) {
assert
.dom(SELECTORS.metricDetailValue(NormalizedBillingMetrics.DATA_PROTECTION_CALLS_TRANSIT))
.hasText('200');
assert
.dom(SELECTORS.metricDetailValue(NormalizedBillingMetrics.DATA_PROTECTION_CALLS_GCPKMS))
.hasText('220');
assert.dom(GENERAL.cardContainer('Managed keys')).exists();
assert.dom(SELECTORS.metricDetail(NormalizedBillingMetrics.MANAGED_KEYS_TOTP)).exists();

View File

@ -88,6 +88,7 @@ export const METRICS_DATA_RESPONSE = {
metric_details: [
{ type: 'transit', count: 200 },
{ type: 'transform', count: 220 },
{ type: 'gcpkms', count: 220 }, // Added for GCP KMS data protection calls
],
},
},
@ -101,6 +102,16 @@ export const METRICS_DATA_RESPONSE = {
],
},
},
{
metric_name: 'id_token_units', // Added for ID token units (OIDC and SPIFFE)
metric_data: {
total: 103.2468,
metric_details: [
{ type: 'oidc', count: 52.1234 },
{ type: 'spiffe', count: 51.1234 },
],
},
},
],
},
{

View File

@ -74,11 +74,16 @@ module('Unit | Utility | metric utils', function () {
};
const expected = {
auto_rotated_roles_total: 0,
credential_units_total: 0,
data_protection_calls_gcpkms: 0,
data_protection_calls_total: 0,
data_protection_calls_transform: 0,
data_protection_calls_transit: 0,
dynamic_roles_total: 0,
external_plugins_total: 0,
id_token_units_oidc: 0,
id_token_units_spiffe: 0,
id_token_units_total: 0,
kmip_used_in_month: false,
managed_keys: 0,
managed_keys_kmse: 0,