- {{#unless (eq model.config.enabled 'On')}}
-
- 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.
-
- {{/unless}}
-
The active clients metric contributes to billing. It is collected at the end of each month alongside unique entities and direct active tokens.
{{#link-to 'vault.cluster.metrics-config'}}Go to configuration{{/link-to}}
+
+ {{/if}}
+{{else}}
+
+ {{#if (eq model.config.enabled 'Off')}}
+
+ 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.
+
+ {{/if}}
+
The active clients metric contributes to billing. It is collected at the end of each month alongside unique entities and direct active tokens.
+{{/if}}
diff --git a/ui/config/environment.js b/ui/config/environment.js
index e150100b0b..401413bc76 100644
--- a/ui/config/environment.js
+++ b/ui/config/environment.js
@@ -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,
},
diff --git a/ui/lib/core/addon/components/form-error.js b/ui/lib/core/addon/components/form-error.js
new file mode 100644
index 0000000000..62378ff10d
--- /dev/null
+++ b/ui/lib/core/addon/components/form-error.js
@@ -0,0 +1,18 @@
+/**
+ * @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
+ * Oh no something bad! Do something
+ * ```
+ */
+
+import Component from '@ember/component';
+import layout from '../templates/components/form-error';
+
+export default Component.extend({
+ layout,
+});
diff --git a/ui/lib/core/addon/templates/components/form-error.hbs b/ui/lib/core/addon/templates/components/form-error.hbs
new file mode 100644
index 0000000000..42447a9ac0
--- /dev/null
+++ b/ui/lib/core/addon/templates/components/form-error.hbs
@@ -0,0 +1,13 @@
+
+
+
+
+
+ {{yield}}
+
+
diff --git a/ui/lib/core/app/components/form-error.js b/ui/lib/core/app/components/form-error.js
new file mode 100644
index 0000000000..c227ba885b
--- /dev/null
+++ b/ui/lib/core/app/components/form-error.js
@@ -0,0 +1 @@
+export { default } from 'core/components/form-error';
diff --git a/ui/tests/integration/components/form-error-test.js b/ui/tests/integration/components/form-error-test.js
new file mode 100644
index 0000000000..72b5361108
--- /dev/null
+++ b/ui/tests/integration/components/form-error-test.js
@@ -0,0 +1,26 @@
+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');
+ });
+});
diff --git a/ui/tests/integration/components/pricing-metrics-dates-test.js b/ui/tests/integration/components/pricing-metrics-dates-test.js
new file mode 100644
index 0000000000..e241194663
--- /dev/null
+++ b/ui/tests/integration/components/pricing-metrics-dates-test.js
@@ -0,0 +1,85 @@
+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`
+
+ `);
+ 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`
+
+ `);
+ 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`
+
+ `);
+ 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`
+
+ `);
+ 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`
+
+ `);
+ 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`
+
+ `);
+ assert.dom('[data-test-results-date-warning]').exists('shows states warning');
+ });
+});
diff --git a/ui/tests/unit/helpers/parse-date-string-test.js b/ui/tests/unit/helpers/parse-date-string-test.js
new file mode 100644
index 0000000000..5a094ff4bd
--- /dev/null
+++ b/ui/tests/unit/helpers/parse-date-string-test.js
@@ -0,0 +1,47 @@
+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);
+ });
+});