UI/Current month view (#13788)
* add timestamp to attribution * create usage stat component * updates stat text boxes * remove flex-header css * remove comment * add empty state if no data * update monthly serializer * remove empty state - unnecessary
This commit is contained in:
parent
d0efb80c23
commit
34ad4ca9f1
|
@ -16,6 +16,7 @@ import { tracked } from '@glimmer/tracking';
|
|||
* @startTimeDisplay={{this.startTimeDisplay}}
|
||||
* @endTimeDisplay={{this.endTimeDisplay}}
|
||||
* @isDateRange={{this.isDateRange}}
|
||||
* @timestamp={{this.responseTimestamp}}
|
||||
* />
|
||||
* ```
|
||||
* @param {array} chartLegend - (passed to child) array of objects with key names 'key' and 'label' so data can be stacked
|
||||
|
@ -25,6 +26,7 @@ import { tracked } from '@glimmer/tracking';
|
|||
* @param {string} startTimeDisplay - start date for CSV modal
|
||||
* @param {string} endTimeDisplay - end date for CSV modal
|
||||
* @param {boolean} isDateRange - getter calculated in parent to relay if dataset is a date range or single month
|
||||
* @param {string} timestamp - timestamp response was received from API
|
||||
*/
|
||||
|
||||
export default class Attribution extends Component {
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import Component from '@glimmer/component';
|
||||
|
||||
export default class Current extends Component {
|
||||
chartLegend = [
|
||||
{ key: 'entity_clients', label: 'entity clients' },
|
||||
{ key: 'non_entity_clients', label: 'non-entity clients' },
|
||||
];
|
||||
|
||||
// data for horizontal bar chart in attribution component
|
||||
get topTenNamespaces() {
|
||||
return this.args.model.monthly?.byNamespace;
|
||||
}
|
||||
|
||||
// top level TOTAL client counts from response for given month
|
||||
get runningTotals() {
|
||||
return this.args.model.monthly?.total;
|
||||
}
|
||||
|
||||
get responseTimestamp() {
|
||||
return this.args.model.monthly?.responseTimestamp;
|
||||
}
|
||||
}
|
|
@ -21,8 +21,8 @@ export default class Dashboard extends Component {
|
|||
];
|
||||
maxNamespaces = 10;
|
||||
chartLegend = [
|
||||
{ key: 'entity_clients', label: 'unique entities' },
|
||||
{ key: 'non_entity_clients', label: 'non-entity tokens' },
|
||||
{ key: 'entity_clients', label: 'entity clients' },
|
||||
{ key: 'non_entity_clients', label: 'non-entity clients' },
|
||||
];
|
||||
// TODO remove this adapter variable? or set to /clients/activity ?
|
||||
adapter = this.store.adapterFor('clients/new-init-activity');
|
||||
|
@ -85,6 +85,13 @@ export default class Dashboard extends Component {
|
|||
return this.args.model.activity.byNamespace;
|
||||
}
|
||||
|
||||
get responseTimestamp() {
|
||||
if (!this.args.model.activity || !this.args.model.activity.responseTimestamp) {
|
||||
return null;
|
||||
}
|
||||
return this.args.model.activity.responseTimestamp;
|
||||
}
|
||||
|
||||
@action
|
||||
async handleClientActivityQuery(month, year, dateType) {
|
||||
if (dateType === 'cancel') {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Model, { attr } from '@ember-data/model';
|
||||
export default class Activity extends Model {
|
||||
@attr('string') responseTimestamp;
|
||||
@attr('array') byNamespace;
|
||||
@attr('string') endTime;
|
||||
@attr('string') startTime;
|
||||
|
|
|
@ -1,15 +1,7 @@
|
|||
import Model, { attr } from '@ember-data/model';
|
||||
// ARG TODO copied from before, modify for what you need
|
||||
export default class Monthly extends Model {
|
||||
@attr('string') responseTimestamp;
|
||||
@attr('array') byNamespace;
|
||||
@attr('object') total;
|
||||
@attr('string') timestamp;
|
||||
// TODO CMB remove 'clients' and use 'total' object?
|
||||
@attr('number') clients;
|
||||
// new names
|
||||
@attr('number') entityClients;
|
||||
@attr('number') nonEntityClients;
|
||||
// old names
|
||||
@attr('number') distinctEntities;
|
||||
@attr('number') nonEntityTokens;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import ApplicationSerializer from '../application';
|
||||
import { formatISO } from 'date-fns';
|
||||
|
||||
/*
|
||||
SAMPLE PAYLOAD BEFORE/AFTER:
|
||||
|
@ -109,9 +110,10 @@ export default ApplicationSerializer.extend({
|
|||
},
|
||||
|
||||
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
|
||||
let response_timestamp = formatISO(new Date());
|
||||
let transformedPayload = {
|
||||
...payload,
|
||||
// TODO CMB should these be nested under "data" to go to model correctly?)
|
||||
response_timestamp,
|
||||
by_namespace: this.flattenDataset(payload.data.by_namespace),
|
||||
};
|
||||
delete payload.data.by_namespace;
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { formatISO } from 'date-fns';
|
||||
import ApplicationSerializer from '../application';
|
||||
import { formatISO } from 'date-fns';
|
||||
|
||||
export default ApplicationSerializer.extend({
|
||||
flattenDataset(payload) {
|
||||
let topTen = payload.slice(0, 10);
|
||||
let topTen = payload ? payload.slice(0, 10) : [];
|
||||
|
||||
return topTen.map((ns) => {
|
||||
// 'namespace_path' is an empty string for root
|
||||
if (ns['namespace_id'] === 'root') ns['namespace_path'] = 'root';
|
||||
let label = ns['namespace_path'] || ns['namespace_id']; // TODO CMB will namespace_path ever be empty?
|
||||
let label = ns['namespace_path'];
|
||||
let flattenedNs = {};
|
||||
// we don't want client counts nested within the 'counts' object for stacked charts
|
||||
Object.keys(ns['counts']).forEach((key) => (flattenedNs[key] = ns['counts'][key]));
|
||||
|
@ -39,16 +39,16 @@ export default ApplicationSerializer.extend({
|
|||
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
|
||||
let { data } = payload;
|
||||
let { clients, distinct_entities, non_entity_tokens } = data;
|
||||
let timestamp = formatISO(new Date());
|
||||
let response_timestamp = formatISO(new Date());
|
||||
let transformedPayload = {
|
||||
...payload,
|
||||
// TODO CMB should these be nested under "data" to go to model correctly?)
|
||||
timestamp,
|
||||
response_timestamp,
|
||||
by_namespace: this.flattenDataset(data.by_namespace),
|
||||
// nest within 'total' object to mimic /activity response shape
|
||||
total: {
|
||||
clients,
|
||||
entity_clients: distinct_entities,
|
||||
non_entity_clients: non_entity_tokens,
|
||||
entityClients: distinct_entities,
|
||||
nonEntityClients: non_entity_tokens,
|
||||
},
|
||||
};
|
||||
delete payload.data.by_namespace;
|
||||
|
|
|
@ -103,15 +103,3 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.has-gap {
|
||||
gap: $spacing-xxl;
|
||||
}
|
||||
|
||||
.stat-text-flex-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 70px;
|
||||
row-gap: 20px;
|
||||
}
|
||||
|
|
|
@ -38,14 +38,23 @@
|
|||
}
|
||||
}
|
||||
|
||||
.has-export {
|
||||
.has-header-link {
|
||||
display: grid;
|
||||
grid-template-columns: 4fr 1fr;
|
||||
|
||||
.header-right {
|
||||
text-align: right;
|
||||
> button {
|
||||
font-size: $size-8;
|
||||
&:hover,
|
||||
&.is-hovered,
|
||||
&:focus,
|
||||
&.is-focused {
|
||||
background-color: transparent;
|
||||
background-color: darken($ui-gray-050, 5%);
|
||||
border-color: darken($ui-gray-300, 5%);
|
||||
}
|
||||
}
|
||||
> a {
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
@ -102,6 +111,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.chart-empty-state {
|
||||
grid-column-start: 1;
|
||||
grid-column-end: -1;
|
||||
}
|
||||
|
||||
.chart-subTitle {
|
||||
grid-column-start: 1;
|
||||
grid-column-end: 3;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="chart-wrapper single-chart-grid">
|
||||
<div class="chart-header has-export has-bottom-margin-m">
|
||||
<div class="chart-header has-header-link has-bottom-margin-m">
|
||||
<div class="header-left">
|
||||
<h2 class="chart-title">Attribution</h2>
|
||||
<p class="chart-description">{{this.chartText.description}}</p>
|
||||
|
@ -13,34 +13,31 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{#if this.totalClientsData}}
|
||||
<div class="chart-container-wide">
|
||||
<Clients::HorizontalBarChart
|
||||
@dataset={{this.totalClientsData}}
|
||||
@chartLegend={{@chartLegend}}
|
||||
@clientTotals={{@runningTotals}}
|
||||
/>
|
||||
</div>
|
||||
<div class="chart-container-wide">
|
||||
<Clients::HorizontalBarChart
|
||||
@dataset={{this.totalClientsData}}
|
||||
@chartLegend={{@chartLegend}}
|
||||
@clientTotals={{@runningTotals}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="chart-subTitle">
|
||||
<p class="chart-subtext">{{this.chartText.totalCopy}}</p>
|
||||
</div>
|
||||
<div class="chart-subTitle">
|
||||
<p class="chart-subtext">{{this.chartText.totalCopy}}</p>
|
||||
</div>
|
||||
|
||||
<div class="data-details-top">
|
||||
<h3 class="data-details">Top {{lowercase this.attributionBreakdown}}</h3>
|
||||
<p class="data-details">{{this.topClientCounts.label}}</p>
|
||||
</div>
|
||||
<div class="data-details-top">
|
||||
<h3 class="data-details">Top {{lowercase this.attributionBreakdown}}</h3>
|
||||
<p class="data-details">{{this.topClientCounts.label}}</p>
|
||||
</div>
|
||||
|
||||
<div class="data-details-bottom">
|
||||
<h3 class="data-details">Clients in {{lowercase this.attributionBreakdown}}</h3>
|
||||
<p class="data-details">{{this.topClientCounts.clients}}</p>
|
||||
</div>
|
||||
{{else}}
|
||||
<EmptyState />
|
||||
{{/if}}
|
||||
<div class="data-details-bottom">
|
||||
<h3 class="data-details">Clients in {{lowercase this.attributionBreakdown}}</h3>
|
||||
<p class="data-details">{{this.topClientCounts.clients}}</p>
|
||||
</div>
|
||||
|
||||
<div class="timestamp">
|
||||
Updated Nov 15 2021, 4:07:32 pm
|
||||
Updated
|
||||
{{date-format @timestamp "MMM dd yyyy, h:mm:ss aaa"}}
|
||||
</div>
|
||||
|
||||
<div class="legend-center">
|
||||
|
|
|
@ -18,64 +18,16 @@
|
|||
{{#if @isLoading}}
|
||||
<LayoutLoading />
|
||||
{{else}}
|
||||
<div class="card has-bottom-margin-m">
|
||||
<div class="card-content">
|
||||
<div class="is-flex is-flex-center">
|
||||
<div class="is-flex-1">
|
||||
<h2 class="title is-5 is-marginless">
|
||||
Total usage
|
||||
</h2>
|
||||
<p class="sub-text">
|
||||
These totals are within this namespace and all its children.
|
||||
</p>
|
||||
</div>
|
||||
<LearnLink @path="/tutorials/vault/usage-metrics">
|
||||
Learn more
|
||||
</LearnLink>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="columns">
|
||||
<div class="column" data-test-client-count-stats>
|
||||
<StatText
|
||||
@label="Total active clients"
|
||||
{{! ARG TODO naming is changing }}
|
||||
@value={{or @model.monthly.clients "0"}}
|
||||
@size="l"
|
||||
@subText="The sum of unique entities and non-entity tokens; Vault's primary billing metric."
|
||||
/>
|
||||
</div>
|
||||
<div class="column">
|
||||
<StatText
|
||||
class="column"
|
||||
@label="Unique entities"
|
||||
{{! ARG TODO naming is changing }}
|
||||
@value={{or @model.monthly.distinctEntities "0"}}
|
||||
@size="l"
|
||||
@subText="Representation of a particular user, client or application that created a token via login."
|
||||
/>
|
||||
</div>
|
||||
<div class="column">
|
||||
<StatText
|
||||
class="column"
|
||||
@label="Non-entity tokens"
|
||||
@value={{or @model.monthly.nonEntityTokens "0"}}
|
||||
@size="l"
|
||||
@subText="Tokens created via a method that is not associated with an entity."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Clients::UsageStats @title={{date-format this.responseTimestamp "MMMM"}} @runningTotals={{this.runningTotals}} />
|
||||
|
||||
<Clients::Attribution
|
||||
@chartLegend={{array
|
||||
(hash key="entity_clients" label="Unique entities")
|
||||
(hash key="non_entity_clients" label="Non-entity tokens")
|
||||
}}
|
||||
@topTenNamespaces={{@model.monthly.byNamespace}}
|
||||
@runningTotals={{@model.monthly.total}}
|
||||
@selectedNamespace={{@model.monthly.selectedNamespace}}
|
||||
@startTimeDisplay={{date-format @model.monthly.timestamp "MMMM yyyy"}}
|
||||
@chartLegend={{this.chartLegend}}
|
||||
@topTenNamespaces={{this.topTenNamespaces}}
|
||||
@runningTotals={{this.runningTotals}}
|
||||
@selectedNamespace={{this.selectedNamespace}}
|
||||
@startTimeDisplay={{date-format this.responseTimestamp "MMMM yyyy"}}
|
||||
@isDateRange={{false}}
|
||||
@timestamp={{this.responseTimestamp}}
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
|
@ -74,6 +74,7 @@
|
|||
@startTimeDisplay={{this.startTimeDisplay}}
|
||||
@endTimeDisplay={{this.endTimeDisplay}}
|
||||
@isDateRange={{this.isDateRange}}
|
||||
@timestamp={{this.responseTimestamp}}
|
||||
/>
|
||||
{{else}}
|
||||
{{! ARG TODO remove once we have this dialed }}
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
<div class="chart-wrapper">
|
||||
<div class="chart-header has-bottom-margin-l">
|
||||
<h2 class="chart-title">{{@month}}</h2>
|
||||
</div>
|
||||
|
||||
<div class="stat-text-flex-wrapper">
|
||||
<div class="column-left">
|
||||
<StatText
|
||||
@label="New clients"
|
||||
@value={{2376}}
|
||||
@size="l"
|
||||
@subText="This is the number of clients which were created in Vault for the first time in the selected month."
|
||||
/>
|
||||
<div class="has-top-margin-l is-flex-start has-gap">
|
||||
<StatText @label={{capitalize @chartLegend.0.label}} @value={{521}} @size="m" />
|
||||
<StatText @label={{capitalize @chartLegend.1.label}} @value={{1855}} @size="m" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column-right">
|
||||
<StatText
|
||||
@label="Total monthly clients"
|
||||
@value={{3013}}
|
||||
@size="l"
|
||||
@subText="This is the number of total clients which used Vault for the given month, both new and previous."
|
||||
/>
|
||||
<div class="has-top-margin-l is-flex-start has-gap">
|
||||
<StatText @label={{capitalize @chartLegend.0.label}} @value={{521}} @size="m" />
|
||||
<StatText @label={{capitalize @chartLegend.1.label}} @value={{1855}} @size="m" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,42 @@
|
|||
<div class="chart-wrapper">
|
||||
<div class="chart-header has-header-link has-bottom-margin-m">
|
||||
<div class="header-left">
|
||||
<h2 class="chart-title">{{@title}}</h2>
|
||||
<p class="chart-description"> These totals are within this namespace and all its children. </p>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<LearnLink @path="/tutorials/vault/usage-metrics">
|
||||
Learn more
|
||||
</LearnLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="columns">
|
||||
<div class="column" data-test-client-count-stats>
|
||||
<StatText
|
||||
@label="Total clients"
|
||||
@value={{or @runningTotals.clients "0"}}
|
||||
@size="l"
|
||||
@subText="The sum of entity clientIDs and non-entity clientIDs; this is Vault’s primary billing metric."
|
||||
/>
|
||||
</div>
|
||||
<div class="column">
|
||||
<StatText
|
||||
class="column"
|
||||
@label="Entity clients"
|
||||
@value={{or @runningTotals.entityClients "0"}}
|
||||
@size="l"
|
||||
@subText="Representations of a particular user, client, or application that created a token via login."
|
||||
/>
|
||||
</div>
|
||||
<div class="column">
|
||||
<StatText
|
||||
class="column"
|
||||
@label="Non-entity clients"
|
||||
@value={{or @runningTotals.nonEntityClients "0"}}
|
||||
@size="l"
|
||||
@subText="Clients created with a shared set of permissions, but not associated with an entity. "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
Loading…
Reference in New Issue