UI/Client count page bug fixes (#12914)

* fix spacing between bars

* fix month displayed on client count page

* centers tooltip over cursor when page browser fills window

* wraps date.length check in conditional

* adds test to date-format helper

* formats client count numbers

* fixes tooltip miscalculating percent

* fixes config data for monthly mirage endpoint
This commit is contained in:
claire bontempo 2021-10-26 16:10:09 -07:00 committed by GitHub
parent 92e6972b86
commit dacbc154af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 256 additions and 38 deletions

View File

@ -9,6 +9,7 @@
.stat-label {
font-size: $size-5;
font-weight: $font-weight-semibold;
margin-bottom: $spacing-xxs;
line-height: inherit;
}
.stat-text {

View File

@ -138,13 +138,14 @@
@title="Top 10 Namespaces"
@description="Each namespace's client count includes clients in child namespaces."
@dataset={{this.barChartDataset}}
@tooltipData={{or @model.activity.clients @model.activity.total.clients}}
@onClick={{action this.selectNamespace}}
@mapLegend={{array
(hash key='non_entity_tokens' label='Non-entity tokens')
(hash key='distinct_entities' label='Unique entities')
}}
>
<DownloadCsv @label={{'Export all namespace data'}} @csvData={{this.getCsvData}} @fileName={{'client-count-by-namespaces.csv'}} />
<DownloadCsv @label={{'Export all namespace data'}} @csvData={{this.getCsvData}} @fileName={{'client-count-by-namespaces.csv'}} />
</BarChart>
</div>
<div class="column">
@ -186,4 +187,4 @@
{{/unless}}
{{/if}}
</div>
{{/if}}
{{/if}}

View File

@ -38,7 +38,7 @@
<div class="box is-fullwidth is-shadowless">
{{#if (and resultStart resultEnd)}}
<h2 class="title is-4" data-test-pricing-result-dates>
{{date-format resultStart "MMM dd, yyyy"}} through {{date-format resultEnd "MMM dd, yyyy"}}
{{date-format resultStart "MMM dd, yyyy" dateOnly=true}} through {{date-format resultEnd "MMM dd, yyyy" dateOnly=true}}
</h2>
{{/if}}
</div>

View File

@ -19,6 +19,7 @@
* @param {string} title - title of the chart
* @param {array} mapLegend - array of objects with key names 'key' and 'label' for the map legend
* @param {object} dataset - dataset for the chart
* @param {array} tooltipData - misc. information needed to display tooltip (i.e. total clients from query params)
* @param {string} [description] - description of the chart
* @param {string} [labelKey=label] - labelKey is the key name in the dataset passed in that corresponds to the value labeling the y-axis
* @param {function} [onClick] - takes function from parent and passes it to click event on data bars
@ -74,10 +75,10 @@ class BarChartComponent extends Component {
}
@action
renderBarChart(element, data) {
renderBarChart(element, args) {
let elementId = guidFor(element);
let [dataset] = data;
let totalCount = dataset.reduce((prevValue, currValue) => prevValue + currValue.total, 0);
let dataset = args[0];
let totalCount = args[1];
let handleClick = this.args.onClick;
let labelKey = this.labelKey;
let stackFunction = stack().keys(this.mapLegend.map(l => l.key));
@ -140,7 +141,7 @@ class BarChartComponent extends Component {
.append('rect')
.attr('class', 'data-bar')
.style('cursor', 'pointer')
.attr('width', chartData => `${xScale(chartData[1] - chartData[0] - 5)}%`)
.attr('width', chartData => `${xScale(chartData[1] - chartData[0]) - 0.25}%`)
.attr('height', yScale.bandwidth())
.attr('x', chartData => `${xScale(chartData[0])}%`)
.attr('y', ({ data }) => yScale(data[labelKey]))
@ -213,8 +214,8 @@ class BarChartComponent extends Component {
select('.chart-tooltip')
.style('opacity', 1)
.style('max-width', '200px')
.style('left', `${event.pageX - 400}px`)
.style('top', `${event.pageY - 150}px`)
.style('left', `${event.pageX - 325}px`)
.style('top', `${event.pageY - 140}px`)
.text(
`${Math.round((chartData.total * 100) / totalCount)}% of total client counts:
${chartData.non_entity_tokens} non-entity tokens, ${chartData.distinct_entities} unique entities.
@ -263,7 +264,7 @@ class BarChartComponent extends Component {
.on('mousemove', function(chartData) {
if (chartData.label.length >= CHAR_LIMIT) {
select('.chart-tooltip')
.style('left', `${event.pageX - 400}px`)
.style('left', `${event.pageX - 300}px`)
.style('top', `${event.pageY - 100}px`)
.text(`${chartData.label}`)
.style('max-width', 'fit-content');

View File

@ -1,11 +1,16 @@
import { helper } from '@ember/component/helper';
import { format, parseISO } from 'date-fns';
export function dateFormat([date, style], { isFormatted = false }) {
export function dateFormat([date, style], { isFormatted = false, dateOnly = false }) {
// see format breaking in upgrade to date-fns 2.x https://github.com/date-fns/date-fns/blob/master/CHANGELOG.md#changed-5
if (isFormatted) {
return format(new Date(date), style);
}
// when date is in '2021-09-01T00:00:00Z' format
// remove hours so date displays unaffected by timezone
if (dateOnly && typeof date === 'string') {
date = date.split('T')[0];
}
let number = typeof date === 'string' ? parseISO(date) : date;
if (!number) {
return;

View File

@ -1,6 +1,9 @@
import { helper } from '@ember/component/helper';
export function formatNumber([number]) {
if (typeof number !== 'number') {
return number;
}
// formats a number according to the locale
return new Intl.NumberFormat().format(number);
}

View File

@ -19,8 +19,8 @@
<div class="is-border"></div>
<div class="bar-chart-container">
<svg
{{did-insert this.renderBarChart @dataset}}
{{did-update this.renderBarChart @dataset}}
{{did-insert this.renderBarChart @dataset @tooltipData}}
{{did-update this.renderBarChart @dataset @tooltipData}}
class="bar-chart"></svg>
</div>
<div class="is-border"></div>

View File

@ -3,5 +3,5 @@
{{#if @subText}}
<div class="stat-text">{{@subText}}</div>
{{/if}}
<div class="stat-value">{{@value}}</div>
<div class="stat-value">{{format-number @value}}</div>
</div>

View File

@ -31,6 +31,218 @@ export default function() {
};
});
this.get('/sys/internal/counters/activity/monthly', function() {
return {
data: {
by_namespace: [
{
namespace_id: 'Z4Rzh',
namespace_path: 'namespace1/',
counts: {
distinct_entities: 867,
non_entity_tokens: 939,
clients: 1806,
},
},
{
namespace_id: 'DcgzU',
namespace_path: 'namespace17/',
counts: {
distinct_entities: 966,
non_entity_tokens: 550,
clients: 1516,
},
},
{
namespace_id: '5SWT8',
namespace_path: 'namespacelonglonglong4/',
counts: {
distinct_entities: 996,
non_entity_tokens: 417,
clients: 1413,
},
},
{
namespace_id: 'XGu7R',
namespace_path: 'namespace12/',
counts: {
distinct_entities: 829,
non_entity_tokens: 540,
clients: 1369,
},
},
{
namespace_id: 'yHcL9',
namespace_path: 'namespace11/',
counts: {
distinct_entities: 563,
non_entity_tokens: 705,
clients: 1268,
},
},
{
namespace_id: 'F0xGm',
namespace_path: 'namespace10/',
counts: {
distinct_entities: 925,
non_entity_tokens: 255,
clients: 1180,
},
},
{
namespace_id: 'aJuQG',
namespace_path: 'namespace9/',
counts: {
distinct_entities: 935,
non_entity_tokens: 239,
clients: 1174,
},
},
{
namespace_id: 'bw5UO',
namespace_path: 'namespace6/',
counts: {
distinct_entities: 810,
non_entity_tokens: 363,
clients: 1173,
},
},
{
namespace_id: 'IeyJp',
namespace_path: 'namespace14/',
counts: {
distinct_entities: 774,
non_entity_tokens: 392,
clients: 1166,
},
},
{
namespace_id: 'Uc0o8',
namespace_path: 'namespace16/',
counts: {
distinct_entities: 408,
non_entity_tokens: 743,
clients: 1151,
},
},
{
namespace_id: 'R6L40',
namespace_path: 'namespace2/',
counts: {
distinct_entities: 292,
non_entity_tokens: 736,
clients: 1028,
},
},
{
namespace_id: 'Rqa3W',
namespace_path: 'namespace13/',
counts: {
distinct_entities: 160,
non_entity_tokens: 803,
clients: 963,
},
},
{
namespace_id: 'MSgZE',
namespace_path: 'namespace7/',
counts: {
distinct_entities: 201,
non_entity_tokens: 657,
clients: 858,
},
},
{
namespace_id: 'kxU4t',
namespace_path: 'namespacelonglonglong3/',
counts: {
distinct_entities: 742,
non_entity_tokens: 26,
clients: 768,
},
},
{
namespace_id: '5xKya',
namespace_path: 'namespace15/',
counts: {
distinct_entities: 663,
non_entity_tokens: 19,
clients: 682,
},
},
{
namespace_id: '5KxXA',
namespace_path: 'namespace18anotherlong/',
counts: {
distinct_entities: 470,
non_entity_tokens: 196,
clients: 666,
},
},
{
namespace_id: 'AAidI',
namespace_path: 'namespace20/',
counts: {
distinct_entities: 429,
non_entity_tokens: 60,
clients: 489,
},
},
{
namespace_id: 'BCl56',
namespace_path: 'namespace8/',
counts: {
distinct_entities: 61,
non_entity_tokens: 201,
clients: 262,
},
},
{
namespace_id: 'yYNw2',
namespace_path: 'namespace19/',
counts: {
distinct_entities: 165,
non_entity_tokens: 85,
clients: 250,
},
},
{
namespace_id: 'root',
namespace_path: '',
counts: {
distinct_entities: 67,
non_entity_tokens: 9,
clients: 76,
},
},
],
distinct_entities: 11323,
non_entity_tokens: 7935,
clients: 19258,
},
};
});
this.get('/sys/health', function() {
return {
initialized: true,
sealed: false,
standby: false,
license: {
expiry: '2021-05-12T23:20:50.52Z',
state: 'stored',
},
performance_standby: false,
replication_performance_mode: 'disabled',
replication_dr_mode: 'disabled',
server_time_utc: 1622562585,
version: '1.9.0+ent',
cluster_name: 'vault-cluster-e779cd7c',
cluster_id: '5f20f5ab-acea-0481-787e-71ec2ff5a60b',
last_wal: 121,
};
});
this.get('/sys/license/status', function() {
return {
data: {
@ -60,25 +272,5 @@ export default function() {
};
});
this.get('/sys/health', function() {
return {
initialized: true,
sealed: false,
standby: false,
license: {
expiry: '2021-05-12T23:20:50.52Z',
state: 'stored',
},
performance_standby: false,
replication_performance_mode: 'disabled',
replication_dr_mode: 'disabled',
server_time_utc: 1622562585,
version: '1.9.0+ent',
cluster_name: 'vault-cluster-e779cd7c',
cluster_id: '5f20f5ab-acea-0481-787e-71ec2ff5a60b',
last_wal: 121,
};
});
this.passthrough();
}

View File

@ -1,6 +1,6 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { render, settled } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | StatText', function(hooks) {
@ -14,16 +14,21 @@ module('Integration | Component | StatText', function(hooks) {
test('it renders passed in attributes', async function(assert) {
this.set('label', 'A Label');
this.set('value', '9,999');
this.set('value', 'A value');
this.set('size', 'l');
this.set('subText', 'This is my description');
await render(
hbs`<StatText @label={{this.label}} @size={{this.size}} @value={{this.value}} @subText={{this.subText}}/>`
);
assert.dom('.stat-label').hasText(this.label, 'renders label');
assert.dom('.stat-text').hasText(this.subText, 'renders subtext');
assert.dom('.stat-value').hasText(this.value, 'renders value');
assert.dom('.stat-value').hasText(this.value, 'renders a non-integer value');
this.set('value', 604099);
await settled();
let formattedNumber = '604,099';
assert.dom('.stat-value').hasText(formattedNumber, 'renders correctly formatted integer value');
});
});

View File

@ -53,4 +53,14 @@ module('Integration | Helper | date-format', function(hooks) {
);
assert.dom('[data-test-date-format]').includesText(format(formattedDate, 'MMMM dd, yyyy hh:mm:ss a'));
});
test('displays correct date when timestamp is in ISO 8601 format', async function(assert) {
let timestampDate = '2021-09-01T00:00:00Z';
this.set('timestampDate', timestampDate);
await render(
hbs`<p data-test-date-format>Date: {{date-format timestampDate 'MMM dd, yyyy' dateOnly=true}}</p>`
);
assert.dom('[data-test-date-format]').includesText('Date: Sep 01, 2021');
});
});