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:
parent
92e6972b86
commit
dacbc154af
|
@ -9,6 +9,7 @@
|
|||
.stat-label {
|
||||
font-size: $size-5;
|
||||
font-weight: $font-weight-semibold;
|
||||
margin-bottom: $spacing-xxs;
|
||||
line-height: inherit;
|
||||
}
|
||||
.stat-text {
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue