UI: Fix chart legend order (#8827) (#8856)

* fix chart legend order and make sync clients last

* reimplement using idx in chart legend

Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com>
Co-authored-by: claire bontempo <cbontempo@hashicorp.com>
This commit is contained in:
Vault Automation 2025-09-02 17:43:21 -06:00 committed by GitHub
parent 27fa51c7a8
commit ea46b31d25
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 66 additions and 41 deletions

View File

@ -35,10 +35,10 @@ Data visualizations render in in a flex row with a 1/3-width left element and a
{{#if @legend}}
<div class="legend-container" data-test-counts-card-legend>
{{#each @legend as |l|}}
{{#each @legend as |l idx|}}
<div class="legend-item">
<span class="dots legend-{{l.key}}"></span>
<Hds::Text::Body @tag="p" @size="100">{{capitalize l.label}}</Hds::Text::Body>
<span class="dots legend-dot-{{if (eq l.key 'new_clients') 'new_clients' idx}}"></span>
<Hds::Text::Body @tag="p" @size="100">{{l.label}}</Hds::Text::Body>
</div>
{{/each}}
</div>

View File

@ -36,12 +36,13 @@ export default class RunningTotal extends Component<Args> {
get chartLegend() {
if (this.showStacked) {
return [
{ key: 'entity_clients', label: 'entity clients' },
{ key: 'non_entity_clients', label: 'non-entity clients' },
...(this.flags.secretsSyncIsActivated ? [{ key: 'secret_syncs', label: 'secret sync clients' }] : []),
{ key: 'acme_clients', label: 'acme clients' },
{ key: 'entity_clients', label: 'Entity clients' },
{ key: 'non_entity_clients', label: 'Non-entity clients' },
{ key: 'acme_clients', label: 'ACME clients' },
// MUST BE LAST because conditionally renders and legend color mapping for stacked bars will be off otherwise
...(this.flags.secretsSyncIsActivated ? [{ key: 'secret_syncs', label: 'Secret sync clients' }] : []),
];
}
return [{ key: 'new_clients', label: 'new clients' }];
return [{ key: 'new_clients', label: 'New clients' }];
}
}

View File

@ -7,11 +7,12 @@
*/
// LEGEND STYLING (positioning is in chart-container.scss)
$blue-500: #1c345f;
$secret_syncs: #6cc5b0;
$acme_clients: #ff725c;
$entity_clients: #4269d0;
$non_entity_clients: #efb117;
$single: #1c345f;
// stacked bar chart color scheme
$first: #4269d0;
$second: #efb117;
$third: #ff725c;
$fourth: #6cc5b0;
.legend-container {
.dots {
@ -20,20 +21,22 @@ $non_entity_clients: #efb117;
border-radius: 50%;
display: inline-block;
}
.legend-new_clients {
background-color: $blue-500;
.legend-dot-new_clients {
background-color: $single;
}
.legend-entity_clients {
background-color: $entity_clients;
// numbers are indices because chart legend is iterated over to ensure
// legend colors match the correct stacked-bar-# class below
.legend-dot-0 {
background-color: $first;
}
.legend-non_entity_clients {
background-color: $non_entity_clients;
.legend-dot-1 {
background-color: $second;
}
.legend-secret_syncs {
background-color: $secret_syncs;
.legend-dot-2 {
background-color: $third;
}
.legend-acme_clients {
background-color: $acme_clients;
.legend-dot-3 {
background-color: $fourth;
}
}
@ -85,7 +88,7 @@ $non_entity_clients: #efb117;
}
.lineal-chart-bar {
fill: var(--token-color-palette-blue-500);
fill: var(--token-color-palette-single);
}
.lineal-axis {
@ -98,20 +101,21 @@ $non_entity_clients: #efb117;
}
}
// @colorScale arg for Lineal::VBars is "stacked-bar", indices are added by lineal
// @colorScale arg for Lineal::VBars is "stacked-bar", numbers are added by lineal (not 0-indexed)
.stacked-bar-1 {
color: $entity_clients;
fill: $entity_clients;
color: $first;
fill: $first;
}
.stacked-bar-2 {
color: $non_entity_clients;
fill: $non_entity_clients;
color: $second;
fill: $second;
}
.stacked-bar-3 {
color: $secret_syncs;
fill: $secret_syncs;
color: $third;
fill: $third;
}
// MUST BE LAST because conditionally renders and legend color mapping for stacked bars will be off otherwise
.stacked-bar-4 {
color: $acme_clients;
fill: $acme_clients;
color: $fourth;
fill: $fourth;
}

View File

@ -42,7 +42,7 @@ module('Acceptance | clients | overview', function (hooks) {
assert.dom(CLIENT_COUNT.statTextValue('Secret sync')).doesNotExist();
assert.dom(CLIENT_COUNT.statTextValue('Entity')).exists('other stats are still visible');
await click(GENERAL.inputByAttr('toggle view'));
assert.dom(CHARTS.legend).hasText('Entity clients Non-entity clients Acme clients');
assert.dom(CHARTS.legend).hasText('Entity clients Non-entity clients ACME clients');
});
// These tests use the clientsHandler which dynamically generates activity data, used for asserting date querying, etc
@ -277,7 +277,12 @@ module('Acceptance | clients | overview', function (hooks) {
await visit('/vault/clients/counts/overview');
assert.dom(CLIENT_COUNT.statTextValue('Secret sync')).exists('shows secret sync data on overview');
await click(GENERAL.inputByAttr('toggle view'));
assert.dom(CHARTS.legend).hasText('Entity clients Non-entity clients Secret sync clients Acme clients');
assert
.dom(CHARTS.legend)
.hasText(
'Entity clients Non-entity clients ACME clients Secret sync clients',
'it renders legend in order that matches the stacked bar data'
);
});
test('it should hide secrets sync stats when feature is NOT activated', async function (assert) {
@ -295,7 +300,12 @@ module('Acceptance | clients | overview', function (hooks) {
.doesNotExist('stat is hidden because feature is not activated');
assert.dom(CLIENT_COUNT.statTextValue('Entity')).exists('other stats are still visible');
await click(GENERAL.inputByAttr('toggle view'));
assert.dom(CHARTS.legend).hasText('Entity clients Non-entity clients Acme clients');
assert
.dom(CHARTS.legend)
.hasText(
'Entity clients Non-entity clients ACME clients',
'it renders legend in order that matches the stacked bar data and does not include secret sync'
);
});
test('it should show secrets sync stats for HVD managed clusters', async function (assert) {
@ -306,7 +316,12 @@ module('Acceptance | clients | overview', function (hooks) {
await visit('/vault/clients/counts/overview');
assert.dom(CLIENT_COUNT.statTextValue('Secret sync')).exists();
await click(GENERAL.inputByAttr('toggle view'));
assert.dom(CHARTS.legend).hasText('Entity clients Non-entity clients Secret sync clients Acme clients');
assert
.dom(CHARTS.legend)
.hasText(
'Entity clients Non-entity clients ACME clients Secret sync clients',
'it renders legend in order that matches the stacked bar data'
);
});
});
});

View File

@ -101,14 +101,19 @@ module('Integration | Component | clients/running-total', function (hooks) {
.dom(CLIENT_COUNT.card('Client usage trends for selected billing period'))
.exists('running total component renders');
assert.dom(CHARTS.chart('Client usage by month')).exists('bar chart renders');
assert.dom(CHARTS.legend).hasText('Entity clients Non-entity clients Secret sync clients Acme clients');
assert
.dom(CHARTS.legend)
.hasText(
'Entity clients Non-entity clients ACME clients Secret sync clients',
'it renders legend in order that matches the stacked bar data and secret sync clients is last'
);
// assert each legend item is correct
const expectedLegend = [
{ label: 'Entity clients', color: 'rgb(66, 105, 208)' },
{ label: 'Non-entity clients', color: 'rgb(239, 177, 23)' },
{ label: 'ACME clients', color: 'rgb(255, 114, 92)' },
{ label: 'Secret sync clients', color: 'rgb(108, 197, 176)' },
{ label: 'Acme clients', color: 'rgb(255, 114, 92)' },
];
findAll('.legend-item').forEach((e, i) => {
@ -172,13 +177,13 @@ module('Integration | Component | clients/running-total', function (hooks) {
await click(GENERAL.inputByAttr('toggle view'));
assert
.dom(CHARTS.legend)
.hasText('Entity clients Non-entity clients Acme clients', 'legend does not include sync clients');
.hasText('Entity clients Non-entity clients ACME clients', 'legend does not include sync clients');
// assert each legend item is correct
const expectedLegend = [
{ label: 'Entity clients', color: 'rgb(66, 105, 208)' },
{ label: 'Non-entity clients', color: 'rgb(239, 177, 23)' },
{ label: 'Acme clients', color: 'rgb(255, 114, 92)' },
{ label: 'ACME clients', color: 'rgb(255, 114, 92)' },
];
findAll('.legend-item').forEach((e, i) => {