From 828185db4986f249c7efe1c17f3a16147f8c71d6 Mon Sep 17 00:00:00 2001 From: Noelle Daley Date: Thu, 1 Aug 2019 14:35:18 -0700 Subject: [PATCH] UI/add select dropdown (#7102) * add SelectDropdown * use SelectDropdown instead of HttpRequestsDropdown * use html selector instead of class name * ensure SelectDropdown still works when rendered inside a Toolbar * add tests * remove old HttpRequests component * make SelectDropdown example easier to read in Storybook * add isFullwidth prop * add SelectDropbown inside a Toolbar story * fix tests * remove actions block and call this.onChange directly * replace dropdownLabel with label * rename SelectDropdown to SelecT * add test for onChange * remove selectedItem prop since we don't need it * make Select accept options as an array of strings or objects * Revert "remove selectedItem prop since we don't need it" This reverts commit 7278516de87bb1df60482edb005137252819931e. * use Select inside TtlPicker * remove debugger * use a test selector * fix pki test selectors * improve storybook docs * fix selected value in ttl picker * ensure httprequests dropdown updates the selected item * ensure select dropdown correctly matches selectedItem * rename selectedItem to selectedValue * remove debugger lol * update selectedItem test * add valueAttribute and labelAttribute to Storybook knobs * udpate jsdocs * remove old httprequestsdropdown component * add note that onChange will receive value of select * use Select inside AuthForm * use correct test selector --- ui/app/components/http-requests-container.js | 15 ++++ ui/app/components/http-requests-dropdown.js | 51 ------------ ui/app/components/select.js | 34 ++++++++ ui/app/styles/components/toolbar.scss | 10 +-- ui/app/templates/components/auth-form.hbs | 30 +++----- .../components/http-requests-container.hbs | 9 ++- .../components/http-requests-dropdown.hbs | 12 --- ui/app/templates/components/select.hbs | 29 +++++++ ui/lib/core/addon/components/ttl-picker.js | 13 ++-- .../addon/templates/components/ttl-picker.hbs | 20 +++-- ui/stories/http-requests-dropdown.md | 22 ------ ui/stories/http-requests-dropdown.stories.js | 28 ------- ui/stories/select.md | 33 ++++++++ ui/stories/select.stories.js | 65 ++++++++++++++++ .../pki/section-crl-test.js | 4 +- .../settings/mount-secret-backend-test.js | 3 + .../http-requests-container-test.js | 4 +- .../components/http-requests-dropdown-test.js | 30 -------- .../integration/components/select-test.js | 77 +++++++++++++++++++ .../integration/components/wrap-ttl-test.js | 8 +- ui/tests/pages/components/auth-form.js | 2 +- ui/tests/pages/components/config-pki.js | 3 +- .../secrets/backend/pki/generate-cert.js | 6 +- .../pages/settings/mount-secret-backend.js | 4 +- 24 files changed, 309 insertions(+), 203 deletions(-) delete mode 100644 ui/app/components/http-requests-dropdown.js create mode 100644 ui/app/components/select.js delete mode 100644 ui/app/templates/components/http-requests-dropdown.hbs create mode 100644 ui/app/templates/components/select.hbs delete mode 100644 ui/stories/http-requests-dropdown.md delete mode 100644 ui/stories/http-requests-dropdown.stories.js create mode 100644 ui/stories/select.md create mode 100644 ui/stories/select.stories.js delete mode 100644 ui/tests/integration/components/http-requests-dropdown-test.js create mode 100644 ui/tests/integration/components/select-test.js diff --git a/ui/app/components/http-requests-container.js b/ui/app/components/http-requests-container.js index 962e13964..0dc71b928 100644 --- a/ui/app/components/http-requests-container.js +++ b/ui/app/components/http-requests-container.js @@ -25,6 +25,21 @@ export default Component.extend({ classNames: ['http-requests-container'], counters: null, timeWindow: 'All', + dropdownOptions: computed('counters', function() { + let counters = this.counters || []; + let options = ['All', 'Last 12 Months']; + if (counters.length) { + const years = counters + .map(counter => { + const year = new Date(counter.start_time); + return year.getUTCFullYear().toString(); + }) + .uniq(); + years.sort().reverse(); + options = options.concat(years); + } + return options; + }), filteredCounters: computed('counters', 'timeWindow', function() { const { counters, timeWindow } = this; if (timeWindow === 'All') { diff --git a/ui/app/components/http-requests-dropdown.js b/ui/app/components/http-requests-dropdown.js deleted file mode 100644 index edb2715d0..000000000 --- a/ui/app/components/http-requests-dropdown.js +++ /dev/null @@ -1,51 +0,0 @@ -import Component from '@ember/component'; -import { computed } from '@ember/object'; - -/** - * @module HttpRequestsDropdown - * HttpRequestsDropdown components are used to render a dropdown that filters the HttpRequestsBarChart. - * - * @example - * ```js - * - * ``` - * - * @param counters=null {Array} - A list of objects containing the total number of HTTP Requests for each month. `counters` should be the response from the `/internal/counters/requests` endpoint which looks like: - * COUNTERS = [ - * { - * "start_time": "2019-05-01T00:00:00Z", - * "total": 50 - * } - * ] - */ - -export default Component.extend({ - classNames: ['http-requests-dropdown'], - counters: null, - timeWindow: 'All', - options: computed('counters', function() { - let counters = this.counters || []; - let options = []; - if (counters.length) { - const years = counters - .map(counter => { - const year = new Date(counter.start_time); - return year.getUTCFullYear().toString(); - }) - .uniq(); - years.sort().reverse(); - options = options.concat(years); - } - return options; - }), - onChange() {}, - actions: { - onSelectTimeWindow(e) { - const newValue = e.target.value; - const { timeWindow } = this; - if (newValue !== timeWindow) { - this.onChange(newValue); - } - }, - }, -}); diff --git a/ui/app/components/select.js b/ui/app/components/select.js new file mode 100644 index 000000000..82f584c15 --- /dev/null +++ b/ui/app/components/select.js @@ -0,0 +1,34 @@ +import Component from '@ember/component'; + +/** + * @module Select + * Select components are used to render a dropdown. + * + * @example + * ```js + * - {{#each (supported-auth-backends) as |method|}} - - {{/each}} - - - - + diff --git a/ui/app/templates/components/http-requests-dropdown.hbs b/ui/app/templates/components/http-requests-dropdown.hbs deleted file mode 100644 index 97432a5a7..000000000 --- a/ui/app/templates/components/http-requests-dropdown.hbs +++ /dev/null @@ -1,12 +0,0 @@ - -
- -
diff --git a/ui/app/templates/components/select.hbs b/ui/app/templates/components/select.hbs new file mode 100644 index 000000000..591d85b0f --- /dev/null +++ b/ui/app/templates/components/select.hbs @@ -0,0 +1,29 @@ + + {{#if label}} + + {{/if}} +
+
+ +
+
+ diff --git a/ui/lib/core/addon/components/ttl-picker.js b/ui/lib/core/addon/components/ttl-picker.js index 2cfefba6a..7ac2fbbac 100644 --- a/ui/lib/core/addon/components/ttl-picker.js +++ b/ui/lib/core/addon/components/ttl-picker.js @@ -102,18 +102,17 @@ export default Component.extend({ }, actions: { - changedValue(key, event) { - let { type, value, checked } = event.target; - let val = type === 'checkbox' ? checked : value; - if (val && key === 'time') { - val = parseInt(val, 10); - if (Number.isNaN(val)) { + changedValue(key, value) { + if (value && key === 'time') { + value = parseInt(value, 10); + if (Number.isNaN(value)) { this.set('errorMessage', ERROR_MESSAGE); return; } } this.set('errorMessage', null); - set(this, key, val); + + set(this, key, value); this.onChange(this.TTL); }, }, diff --git a/ui/lib/core/addon/templates/components/ttl-picker.hbs b/ui/lib/core/addon/templates/components/ttl-picker.hbs index 613876ac6..b617cdef0 100644 --- a/ui/lib/core/addon/templates/components/ttl-picker.hbs +++ b/ui/lib/core/addon/templates/components/ttl-picker.hbs @@ -2,18 +2,16 @@
-
-
- -
+ +``` + +**See** + +- [Uses of Select](https://github.com/hashicorp/vault/search?l=Handlebars&q=Select+OR+select) +- [Select Source Code](https://github.com/hashicorp/vault/blob/master/ui/app/components/select.js) + +--- diff --git a/ui/stories/select.stories.js b/ui/stories/select.stories.js new file mode 100644 index 000000000..a6ae3c049 --- /dev/null +++ b/ui/stories/select.stories.js @@ -0,0 +1,65 @@ +/* eslint-disable import/extensions */ +import hbs from 'htmlbars-inline-precompile'; +import { storiesOf } from '@storybook/ember'; +import { withKnobs, object, text, boolean, select } from '@storybook/addon-knobs'; +import notes from './select.md'; + +const OPTIONS = [ + { value: 'mon', label: 'Monday', spanish: 'lunes' }, + { value: 'tues', label: 'Tuesday', spanish: 'martes' }, + { value: 'weds', label: 'Wednesday', spanish: 'miercoles' }, +]; + +storiesOf('Select/', module) + .addParameters({ options: { showPanel: true } }) + .addDecorator(withKnobs()) + .add( + `Select`, + () => ({ + template: hbs` +
Select
+ + + `, + context: { + label: text('label', 'Day of the week'), + options: object('options', OPTIONS), + valueAttribute: select('valueAttribute', Object.keys(OPTIONS[0]), 'value'), + labelAttribute: select('labelAttribute', Object.keys(OPTIONS[0]), 'label'), + }, + }), + { notes } + ); diff --git a/ui/tests/acceptance/settings/configure-secret-backends/pki/section-crl-test.js b/ui/tests/acceptance/settings/configure-secret-backends/pki/section-crl-test.js index e7f1645ad..f865e5b1c 100644 --- a/ui/tests/acceptance/settings/configure-secret-backends/pki/section-crl-test.js +++ b/ui/tests/acceptance/settings/configure-secret-backends/pki/section-crl-test.js @@ -18,8 +18,8 @@ module('Acceptance | settings/configure/secrets/pki/crl', function(hooks) { await page.visit({ backend: path, section: 'crl' }); assert.equal(currentRouteName(), 'vault.cluster.settings.configure-secret-backend.section'); - await page.form.fillInField('time', 3); - await page.form.fillInField('unit', 'h'); + await page.form.fillInValue(3); + await page.form.fillInUnit('h'); await page.form.submit(); assert.equal(page.lastMessage, 'The crl config for this backend has been updated.'); }); diff --git a/ui/tests/acceptance/settings/mount-secret-backend-test.js b/ui/tests/acceptance/settings/mount-secret-backend-test.js index b01eda63a..4813d6bb4 100644 --- a/ui/tests/acceptance/settings/mount-secret-backend-test.js +++ b/ui/tests/acceptance/settings/mount-secret-backend-test.js @@ -21,8 +21,10 @@ module('Acceptance | settings/mount-secret-backend', function(hooks) { const maxTTLSeconds = maxTTLHours * 60 * 60; await page.visit(); + assert.equal(currentRouteName(), 'vault.cluster.settings.mount-secret-backend'); await page.selectType('kv'); + await page .next() .path(path) @@ -32,6 +34,7 @@ module('Acceptance | settings/mount-secret-backend', function(hooks) { .maxTTLVal(maxTTLHours) .maxTTLUnit('h') .submit(); + await configPage.visit({ backend: path }); assert.equal(configPage.defaultTTL, defaultTTLSeconds, 'shows the proper TTL'); assert.equal(configPage.maxTTL, maxTTLSeconds, 'shows the proper max TTL'); diff --git a/ui/tests/integration/components/http-requests-container-test.js b/ui/tests/integration/components/http-requests-container-test.js index f770c6365..63ddbcc90 100644 --- a/ui/tests/integration/components/http-requests-container-test.js +++ b/ui/tests/integration/components/http-requests-container-test.js @@ -21,7 +21,7 @@ module('Integration | Component | http-requests-container', function(hooks) { await render(hbs``); assert.dom('.http-requests-container').exists(); - assert.dom('.http-requests-dropdown').exists(); + assert.dom('.select').exists(); assert.dom('.http-requests-bar-chart-container').exists(); assert.dom('.http-requests-table').exists(); }); @@ -43,7 +43,7 @@ module('Integration | Component | http-requests-container', function(hooks) { test('it filters the data according to the dropdown', async function(assert) { await render(hbs``); - await fillIn('[data-test-timewindow-select]', '2018'); + await fillIn('[data-test-select="requests-timewindow"]', '2018'); assert.dom('.shadow-bars> .bar').exists({ count: 1 }, 'filters the bar chart to the selected year'); assert.dom('.start-time').exists({ count: 1 }, 'filters the table to the selected year'); diff --git a/ui/tests/integration/components/http-requests-dropdown-test.js b/ui/tests/integration/components/http-requests-dropdown-test.js deleted file mode 100644 index eea5ec476..000000000 --- a/ui/tests/integration/components/http-requests-dropdown-test.js +++ /dev/null @@ -1,30 +0,0 @@ -import { module, test } from 'qunit'; -import { setupRenderingTest } from 'ember-qunit'; -import { render } from '@ember/test-helpers'; -import hbs from 'htmlbars-inline-precompile'; - -const COUNTERS = [ - { start_time: '2018-04-01T00:00:00Z', total: 5500 }, - { start_time: '2019-05-01T00:00:00Z', total: 4500 }, - { start_time: '2019-06-01T00:00:00Z', total: 5000 }, -]; - -module('Integration | Component | http-requests-dropdown', function(hooks) { - setupRenderingTest(hooks); - - hooks.beforeEach(function() { - this.set('counters', COUNTERS); - }); - - test('it renders with options', async function(assert) { - await render(hbs``); - - assert.dom('[data-test-date-range]').hasValue('All', 'shows all data by default'); - - assert.equal( - this.element.querySelector('[data-test-date-range]').options.length, - 4, - 'it adds an option for each year in the data set' - ); - }); -}); diff --git a/ui/tests/integration/components/select-test.js b/ui/tests/integration/components/select-test.js new file mode 100644 index 000000000..3bbd2392e --- /dev/null +++ b/ui/tests/integration/components/select-test.js @@ -0,0 +1,77 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render, fillIn } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; +import sinon from 'sinon'; + +const OPTIONS = ['foo', 'bar', 'baz']; +const LABEL = 'Boop'; + +module('Integration | Component | Select', function(hooks) { + setupRenderingTest(hooks); + + hooks.beforeEach(function() { + this.set('options', OPTIONS); + this.set('label', LABEL); + this.set('name', 'foo'); + }); + + test('it renders', async function(assert) { + await render(hbs``); + + assert.dom('[data-test-select="foo"]').hasValue('foo'); + assert.equal(this.element.querySelector('[data-test-select="foo"]').options.length, 3); + }); + + test('it renders when options is an array of objects', async function(assert) { + const objectOptions = [{ value: 'berry', label: 'Berry' }, { value: 'cherry', label: 'Cherry' }]; + this.set('options', objectOptions); + await render(hbs`` + ); + + assert.dom('[data-test-select="foo"]').hasValue('tues', 'sets selectedValue by default'); + assert.equal( + this.element.querySelector('[data-test-select="foo"]').options[1].textContent.trim(), + 'Tuesday', + 'uses the labelAttribute to determine the label' + ); + }); + + test('it calls onChange when an item is selected', async function(assert) { + this.set('onChange', sinon.spy()); + await render(hbs`