Unverified Commit 2c7cadb7 authored by Chelsea Shaw's avatar Chelsea Shaw Committed by GitHub
Browse files

Ui/pricing metrics params (#10083)

metrics route takes start and end params and passes to the date display field, as well as the route's API call
parent 37678906
Showing with 256 additions and 30 deletions
+256 -30
......@@ -72,6 +72,18 @@
<StatusMenu @label="Status" @onLinkClick={{action Nav.closeDrawer}} />
</div>
<div class="navbar-separator is-hidden-mobile"></div>
{{else if (and (has-permission 'metrics' routeParams='activity') (not cluster.dr.isSecondary) auth.currentToken)}}
<div class="navbar-sections">
<div class="{{if (is-active-route 'vault.cluster.metrics') 'is-active'}}">
{{#link-to
"vault.cluster.metrics"
current-when="vault.cluster.metrics"
data-test-navbar-item='metrics'
}}
Metrics
{{/link-to}}
</div>
</div>
{{/if}}
<div class="navbar-item">
<button type="button" class="button is-transparent nav-console-button{{if consoleOpen " popup-open"}}"
......
......@@ -37,34 +37,53 @@
</nav>
</div>
<div class="box is-sideless is-fullwidth is-marginless is-bottomless">
{{#unless (eq model.config.enabled 'On')}}
<AlertBanner
@type="warning"
@title="Tracking is disabled"
>
This feature is currently disabled and data is not being collected. {{#link-to 'vault.cluster.metrics-config'}}Edit the configuration{{/link-to}} to enable tracking again.
</AlertBanner>
{{/unless}}
<p class="has-bottom-margin-s">The active clients metric contributes to billing. It is collected at the end of each month alongside unique entities and direct active tokens.</p>
<h2 class="title is-4">
{{date-format model.activity.startTime "MMM DD, YYYY"}} through {{date-format model.activity.endTime "MMM DD, YYYY"}}
</h2>
<div class="selectable-card-container">
<SelectableCard
@cardTitle="Active clients"
@total={{model.activity.total.clients}}
@subText="Current namespace"
/>
<SelectableCard
@cardTitle="Unique entities"
@total={{model.activity.total.distinct_entities}}
@subText="Current namespace"
/>
<SelectableCard
@cardTitle="Active direct tokens"
@total={{model.activity.total.non_entity_tokens}}
@subText="Current namespace"
{{#if (eq model.config.queriesAvailable false)}}
{{#if (eq model.config.enabled "On")}}
<EmptyState @title="No data is being received" @message='We haven’t yet gathered enough data to display here. We collect it at the end of each month, so your data will be available on the first of next month.' />
{{else}}
<EmptyState @title="No data is being received" @message='Tracking is disabled, and no data is being collected. To turn it on, edit the configuration.'>
<p>{{#link-to 'vault.cluster.metrics-config'}}Go to configuration{{/link-to}}</p>
</EmptyState>
{{/if}}
{{else}}
<div class="box is-sideless is-fullwidth is-marginless is-bottomless">
{{#if (eq model.config.enabled 'Off')}}
<AlertBanner
@type="warning"
@title="Tracking is disabled"
>
This feature is currently disabled and data is not being collected. {{#link-to 'vault.cluster.metrics-config'}}Edit the configuration{{/link-to}} to enable tracking again.
</AlertBanner>
{{/if}}
<p class="has-bottom-margin-s">The active clients metric contributes to billing. It is collected at the end of each month alongside unique entities and direct active tokens.</p>
<PricingMetricsDates
@queryStart={{model.queryStart}}
@queryEnd={{model.queryEnd}}
@resultStart={{model.activity.startTime}}
@resultEnd={{model.activity.endTime}}
/>
{{#unless model.activity.total}}
<EmptyState @title="No data found" @message="No data exists for that query period. Try searching again." />
{{else}}
<div class="selectable-card-container">
<SelectableCard
@cardTitle="Active clients"
@total={{model.activity.total.clients}}
@subText="Current namespace"
/>
<SelectableCard
@cardTitle="Unique entities"
@total={{model.activity.total.distinct_entities}}
@subText="Current namespace"
/>
<SelectableCard
@cardTitle="Active direct tokens"
@total={{model.activity.total.non_entity_tokens}}
@subText="Current namespace"
/>
</div>
{{/unless}}
</div>
</div>
{{/if}}
......@@ -25,7 +25,12 @@ module.exports = function(environment) {
// endpoints that UI uses to determine the cluster state
// calls to these endpoints will always go to the root namespace
// these also need to be updated in the open-api-explorer engine
NAMESPACE_ROOT_URLS: ['sys/health', 'sys/seal-status', 'sys/license/features'],
NAMESPACE_ROOT_URLS: [
'sys/health',
'sys/seal-status',
'sys/license/features',
'sys/internal/counters/config',
],
// number of records to show on a single page by default - this is used by the client-side pagination
DEFAULT_PAGE_SIZE: 100,
},
......
/**
* @module FormError
* FormError components are used to show an error on a form field that is more compact than the
* normal MessageError component. This component adds an icon and styling to the content of the
* component, so additionally styling (bold, italic) and links are allowed.
*
* @example
* ```js
* <FormError>Oh no <em>something bad</em>! <a href="#">Do something</a></FormError>
* ```
*/
import Component from '@ember/component';
import layout from '../templates/components/form-error';
export default Component.extend({
layout,
});
<div class="has-top-margin-s is-flex is-flex-center">
<div class="is-narrow message-icon">
<Icon
@size="s"
class="has-text-danger"
aria-hidden=true
@glyph="cancel-square-fill"
/>
</div>
<div class="has-text-danger">
{{yield}}
</div>
</div>
export { default } from 'core/components/form-error';
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | form-error', function(hooks) {
setupRenderingTest(hooks);
test('it renders', async function(assert) {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.set('myAction', function(val) { ... });
await render(hbs`{{form-error}}`);
assert.equal(this.element.textContent.trim(), '');
// Template block usage:
await render(hbs`
{{#form-error}}
template block text
{{/form-error}}
`);
assert.equal(this.element.textContent.trim(), 'template block text');
});
});
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import { subMonths, startOfToday, format } from 'date-fns';
module('Integration | Component | pricing-metrics-dates', function(hooks) {
setupRenderingTest(hooks);
test('by default it sets the start and end inputs', async function(assert) {
const expectedEnd = subMonths(startOfToday(), 1);
const expectedStart = subMonths(expectedEnd, 12);
await render(hbs`
<PricingMetricsDates />
`);
assert.dom('[data-test-end-input]').hasValue(format(expectedEnd, 'MM/YYYY'), 'End input is last month');
assert
.dom('[data-test-start-input]')
.hasValue(format(expectedStart, 'MM/YYYY'), 'Start input is 12 months before last month');
});
test('On init if end date passed, start is calculated', async function(assert) {
const expectedStart = subMonths(new Date(2020, 8, 15), 12);
this.set('queryEnd', '09-2020');
await render(hbs`
<PricingMetricsDates @queryEnd={{queryEnd}} />
`);
assert.dom('[data-test-end-input]').hasValue('09/2020', 'End input matches query');
assert
.dom('[data-test-start-input]')
.hasValue(format(expectedStart, 'MM/YYYY'), 'Start input is 12 months before end input');
});
test('On init if query start date passed, end is default', async function(assert) {
const expectedEnd = subMonths(startOfToday(), 1);
this.set('queryStart', '01-2020');
await render(hbs`
<PricingMetricsDates @queryStart={{queryStart}} />
`);
assert.dom('[data-test-end-input]').hasValue(format(expectedEnd, 'MM/YYYY'), 'End input is last month');
assert.dom('[data-test-start-input]').hasValue('01/2020', 'Start input matches query');
});
test('If result and query dates are within 1 day, warning is not shown', async function(assert) {
this.set('resultStart', new Date(2020, 1, 1));
this.set('resultEnd', new Date(2020, 9, 31));
await render(hbs`
<PricingMetricsDates
@queryStart="2-2020"
@queryEnd="10-2020"
@resultStart={{resultStart}}
@resultEnd={{resultEnd}}
/>
`);
assert.dom('[data-test-results-date-warning]').doesNotExist('Does not show result states warning');
});
test('If result and query start dates are > 1 day apart, warning is shown', async function(assert) {
this.set('resultStart', new Date(2020, 1, 20));
this.set('resultEnd', new Date(2020, 9, 31));
await render(hbs`
<PricingMetricsDates
@queryStart="2-2020"
@queryEnd="10-2020"
@resultStart={{resultStart}}
@resultEnd={{resultEnd}}
/>
`);
assert.dom('[data-test-results-date-warning]').exists('shows states warning');
});
test('If result and query end dates are > 1 day apart, warning is shown', async function(assert) {
this.set('resultStart', new Date(2020, 1, 1));
this.set('resultEnd', new Date(2020, 9, 15));
await render(hbs`
<PricingMetricsDates
@queryStart="2-2020"
@queryEnd="10-2020"
@resultStart={{resultStart}}
@resultEnd={{resultEnd}}
/>
`);
assert.dom('[data-test-results-date-warning]').exists('shows states warning');
});
});
import { parseDateString } from 'vault/helpers/parse-date-string';
import { module, test } from 'qunit';
import { compareAsc } from 'date-fns';
module('Unit | Helpers | parse-date-string', function() {
test('it returns the first of the month when date like MM-YYYY passed in', function(assert) {
let expected = new Date(2020, 3, 1);
let result = parseDateString('04-2020');
assert.equal(compareAsc(expected, result), 0);
});
test('it can handle a date format like MM/YYYY', function(assert) {
let expected = new Date(2020, 11, 1);
let result = parseDateString('12/2020', '/');
assert.equal(compareAsc(expected, result), 0);
});
test('it throws an error with passed separator if bad format', function(assert) {
let result;
try {
result = parseDateString('01-12-2020');
} catch (e) {
result = e.message;
}
assert.equal('Please use format MM-YYYY', result);
});
test('it throws an error with wrong separator', function(assert) {
let result;
try {
result = parseDateString('12/2020', '.');
} catch (e) {
result = e.message;
}
assert.equal('Please use format MM.YYYY', result);
});
test('it throws an error if month is invalid', function(assert) {
let result;
try {
result = parseDateString('13-2020');
} catch (e) {
result = e.message;
}
assert.equal('Not a valid month value', result);
});
});
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment