From e919f00287fc4a97ded706a6b30ab5ba20c0f128 Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Mon, 15 Jun 2020 14:48:30 -0500 Subject: [PATCH 01/24] Add inert search components --- ui/app/components/global-search/control.js | 23 ++++++++++++ ui/app/styles/components.scss | 2 + .../components/global-search-control.scss | 37 +++++++++++++++++++ .../components/global-search-dropdown.scss | 32 ++++++++++++++++ ui/app/styles/core/navbar.scss | 8 ++-- ui/app/templates/components/global-header.hbs | 1 + .../components/global-search/control.hbs | 13 +++++++ .../components/global-search/message.hbs | 0 .../components/global-search/trigger.hbs | 4 ++ 9 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 ui/app/components/global-search/control.js create mode 100644 ui/app/styles/components/global-search-control.scss create mode 100644 ui/app/styles/components/global-search-dropdown.scss create mode 100644 ui/app/templates/components/global-search/control.hbs create mode 100644 ui/app/templates/components/global-search/message.hbs create mode 100644 ui/app/templates/components/global-search/trigger.hbs diff --git a/ui/app/components/global-search/control.js b/ui/app/components/global-search/control.js new file mode 100644 index 00000000000..a37efbcf99a --- /dev/null +++ b/ui/app/components/global-search/control.js @@ -0,0 +1,23 @@ +import Component from '@ember/component'; +import { tagName } from '@ember-decorators/component'; +import { task } from 'ember-concurrency'; +import { action } from '@ember/object'; + +@tagName('') +export default class GlobalSearchControl extends Component { + @task(function*() {}) + search; + + @action select() {} + + calculatePosition(trigger) { + const { top, left, width } = trigger.getBoundingClientRect(); + return { + style: { + left, + width, + top, + }, + }; + } +} diff --git a/ui/app/styles/components.scss b/ui/app/styles/components.scss index 1bfd76d9b42..c0f81710ec7 100644 --- a/ui/app/styles/components.scss +++ b/ui/app/styles/components.scss @@ -11,6 +11,8 @@ @import './components/exec-button'; @import './components/exec-window'; @import './components/fs-explorer'; +@import './components/global-search-control'; +@import './components/global-search-dropdown'; @import './components/gutter'; @import './components/gutter-toggle'; @import './components/image-file.scss'; diff --git a/ui/app/styles/components/global-search-control.scss b/ui/app/styles/components/global-search-control.scss new file mode 100644 index 00000000000..e42243b5021 --- /dev/null +++ b/ui/app/styles/components/global-search-control.scss @@ -0,0 +1,37 @@ +.global-search { + width: 30em; + + .ember-power-select-trigger { + background: $nomad-green-darker; + + .icon { + margin-top: 1px; + margin-left: 2px; + + fill: white; + opacity: 0.7; + } + + .placeholder { + opacity: 0.7; + display: inline-block; + padding-left: 2px; + transform: translateY(-1px); + } + + &.ember-power-select-trigger--active { + background: white; + + .icon { + fill: black; + opacity: 1; + } + } + } + + .ember-basic-dropdown-content-wormhole-origin { + position: absolute; + top: 0; + width: 100%; + } +} diff --git a/ui/app/styles/components/global-search-dropdown.scss b/ui/app/styles/components/global-search-dropdown.scss new file mode 100644 index 00000000000..adbeaec857f --- /dev/null +++ b/ui/app/styles/components/global-search-dropdown.scss @@ -0,0 +1,32 @@ +.global-search-dropdown { + background: transparent; + border: 0; + position: fixed; + + input, + input:focus { + background: transparent; + border: 0; + outline: 0; + } + + .ember-power-select-options { + background: white; + padding: 0.35rem; + + &[role='listbox'] { + border: 1px solid $grey-blue; + box-shadow: 0 6px 8px -2px rgba($black, 0.05), 0 8px 4px -4px rgba($black, 0.1); + } + + .ember-power-select-option { + padding: 0.2rem 0.4rem; + border-radius: $radius; + + &[aria-current='true'] { + background: transparentize($blue, 0.8); + color: $blue; + } + } + } +} diff --git a/ui/app/styles/core/navbar.scss b/ui/app/styles/core/navbar.scss index 48a2a065d76..1ef5197921a 100644 --- a/ui/app/styles/core/navbar.scss +++ b/ui/app/styles/core/navbar.scss @@ -8,6 +8,8 @@ padding-left: 20px; padding-right: 20px; overflow: hidden; + align-items: center; + justify-content: space-between; .navbar-item { color: rgba($primary-invert, 0.8); @@ -35,7 +37,7 @@ display: block; position: absolute; left: 0px; - top: 1.25em; + top: 1.25em; // FIXME positioning is off with .is-primary align-items: center } } } @@ -44,7 +46,7 @@ display: flex; align-items: stretch; justify-content: flex-end; - margin-left: auto; + margin-left: inherit; } .navbar-end > a.navbar-item { @@ -100,7 +102,7 @@ display: flex; align-items: center; justify-content: flex-end; - margin-left: auto; + margin-left: inherit; } .navbar-end > a.navbar-item { diff --git a/ui/app/templates/components/global-header.hbs b/ui/app/templates/components/global-header.hbs index 23c5aa05ccf..4fe743ba46e 100644 --- a/ui/app/templates/components/global-header.hbs +++ b/ui/app/templates/components/global-header.hbs @@ -7,6 +7,7 @@ {{partial "partials/nomad-logo"}} </LinkTo> </div> + {{global-search/control}} <div class="navbar-end"> <a href="https://nomadproject.io/docs" class="navbar-item">Documentation</a> <LinkTo @route="settings.tokens" class="navbar-item">ACL Tokens</LinkTo> diff --git a/ui/app/templates/components/global-search/control.hbs b/ui/app/templates/components/global-search/control.hbs new file mode 100644 index 00000000000..c858a8883e4 --- /dev/null +++ b/ui/app/templates/components/global-search/control.hbs @@ -0,0 +1,13 @@ + <PowerSelect + @tagName="div" + class="global-search" + data-test-search + @searchEnabled={{true}} + @search={{perform this.search}} + @onChange={{action 'select'}} + @dropdownClass="global-search-dropdown" + @calculatePosition={{this.calculatePosition}} + @searchMessageComponent="global-search/message" + @triggerComponent="global-search/trigger" + as |option|> + </PowerSelect> \ No newline at end of file diff --git a/ui/app/templates/components/global-search/message.hbs b/ui/app/templates/components/global-search/message.hbs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ui/app/templates/components/global-search/trigger.hbs b/ui/app/templates/components/global-search/trigger.hbs new file mode 100644 index 00000000000..aa52b18cd3b --- /dev/null +++ b/ui/app/templates/components/global-search/trigger.hbs @@ -0,0 +1,4 @@ +{{x-icon "search" class="is-small"}} +{{#unless select.isOpen}} + <span class='placeholder'>Search</span> +{{/unless}} \ No newline at end of file From 73b6bb3bf565911c546cde15e149f125715fc021 Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Tue, 16 Jun 2020 09:24:39 -0500 Subject: [PATCH 02/24] Add preliminary job search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is an ungainly integration with the searchable mixin 😳 --- ui/app/components/global-search/control.js | 53 ++++++++++++++++++- .../components/global-search/control.hbs | 1 + ui/tests/acceptance/search-test.js | 28 ++++++++++ ui/tests/pages/layout.js | 18 +++++++ 4 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 ui/tests/acceptance/search-test.js diff --git a/ui/app/components/global-search/control.js b/ui/app/components/global-search/control.js index a37efbcf99a..ab2cfbcbdd2 100644 --- a/ui/app/components/global-search/control.js +++ b/ui/app/components/global-search/control.js @@ -1,11 +1,42 @@ import Component from '@ember/component'; import { tagName } from '@ember-decorators/component'; import { task } from 'ember-concurrency'; -import { action } from '@ember/object'; +import EmberObject, { action, computed } from '@ember/object'; +import { alias } from '@ember/object/computed'; +import { inject as service } from '@ember/service'; +import Searchable from 'nomad-ui/mixins/searchable'; +import classic from 'ember-classic-decorator'; @tagName('') export default class GlobalSearchControl extends Component { - @task(function*() {}) + @service store; + + searchString = null; + + constructor() { + super(...arguments); + + this.jobSearch = JobSearch.create({ + something: this, // FIXME what’s a good name? + }); + } + + @task(function*(string) { + this.searchString = string; + + // FIXME no need to fetch on every search! + const jobs = yield this.store.findAll('job'); + + this.jobs = jobs.toArray(); + + try { + const jobResults = this.jobSearch.listSearched; + return jobResults; + } catch (e) { + // eslint-disable-next-line + console.log('exception searching jobs', e); + } + }) search; @action select() {} @@ -21,3 +52,21 @@ export default class GlobalSearchControl extends Component { }; } } + +@classic +class JobSearch extends EmberObject.extend(Searchable) { + @computed + get searchProps() { + return ['id', 'name']; + } + + @computed + get fuzzySearchProps() { + return ['name']; + } + + @alias('something.jobs') listToSearch; + @alias('something.searchString') searchTerm; + + fuzzySearchEnabled = true; +} diff --git a/ui/app/templates/components/global-search/control.hbs b/ui/app/templates/components/global-search/control.hbs index c858a8883e4..fc2f5f1202e 100644 --- a/ui/app/templates/components/global-search/control.hbs +++ b/ui/app/templates/components/global-search/control.hbs @@ -10,4 +10,5 @@ @searchMessageComponent="global-search/message" @triggerComponent="global-search/trigger" as |option|> + {{option.name}} </PowerSelect> \ No newline at end of file diff --git a/ui/tests/acceptance/search-test.js b/ui/tests/acceptance/search-test.js new file mode 100644 index 00000000000..93215af7d3c --- /dev/null +++ b/ui/tests/acceptance/search-test.js @@ -0,0 +1,28 @@ +import { module, test } from 'qunit'; +import { visit } from '@ember/test-helpers'; +import { setupApplicationTest } from 'ember-qunit'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import PageLayout from 'nomad-ui/tests/pages/layout'; +import { selectSearch } from 'ember-power-select/test-support'; + +module('Acceptance | search', function(hooks) { + setupApplicationTest(hooks); + setupMirage(hooks); + + test('search searches jobs', async function(assert) { + server.create('node'); + server.create('job', { id: 'vwxyz', namespaceId: 'default' }); + server.create('job', { id: 'xyz', namespace: 'default' }); + server.create('job', { id: 'abc', namespace: 'default' }); + + await visit('/'); + + await selectSearch(PageLayout.navbar.search.scope, 'xy'); + + PageLayout.navbar.search.as(search => { + assert.equal(search.options.length, 2); + assert.equal(search.options[0].text, 'xyz'); + assert.equal(search.options[1].text, 'vwxyz'); + }); + }); +}); diff --git a/ui/tests/pages/layout.js b/ui/tests/pages/layout.js index 257caf2e804..21a447fc0e6 100644 --- a/ui/tests/pages/layout.js +++ b/ui/tests/pages/layout.js @@ -12,6 +12,24 @@ export default create({ label: text(), }), }, + + search: { + scope: '[data-test-search]', + + click: clickable('.ember-power-select-trigger'), + + options: collection('.ember-power-select-option', { + testContainer: '.ember-power-select-options', + resetScope: true, + label: text(), + }), + + field: { + scope: '.ember-power-select-dropdown--active', + testContainer: 'html', + resetScope: true, + }, + }, }, gutter: { From 6b466790e9c1a8e4fb668a246e2839e598e90329 Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Tue, 16 Jun 2020 09:56:57 -0500 Subject: [PATCH 03/24] Remove need to activate search with enter --- ui/app/components/global-search/control.js | 17 +++++++++++++++++ .../components/global-search/control.hbs | 1 + ui/tests/acceptance/search-test.js | 10 ++++++++++ ui/tests/pages/layout.js | 2 +- 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/ui/app/components/global-search/control.js b/ui/app/components/global-search/control.js index ab2cfbcbdd2..7f9319b5417 100644 --- a/ui/app/components/global-search/control.js +++ b/ui/app/components/global-search/control.js @@ -4,6 +4,7 @@ import { task } from 'ember-concurrency'; import EmberObject, { action, computed } from '@ember/object'; import { alias } from '@ember/object/computed'; import { inject as service } from '@ember/service'; +import { run } from '@ember/runloop'; import Searchable from 'nomad-ui/mixins/searchable'; import classic from 'ember-classic-decorator'; @@ -41,6 +42,22 @@ export default class GlobalSearchControl extends Component { @action select() {} + @action + openOnClickOrTab(select, { target }) { + // Bypass having to press enter to access search after clicking/tabbing + const targetClassList = target.classList; + const targetIsTrigger = targetClassList.contains('ember-power-select-trigger'); + + // Allow tabbing out of search + const triggerIsNotActive = !targetClassList.contains('ember-power-select-trigger--active'); + + if (targetIsTrigger && triggerIsNotActive) { + run.next(() => { + select.actions.open(); + }); + } + } + calculatePosition(trigger) { const { top, left, width } = trigger.getBoundingClientRect(); return { diff --git a/ui/app/templates/components/global-search/control.hbs b/ui/app/templates/components/global-search/control.hbs index fc2f5f1202e..759594085db 100644 --- a/ui/app/templates/components/global-search/control.hbs +++ b/ui/app/templates/components/global-search/control.hbs @@ -5,6 +5,7 @@ @searchEnabled={{true}} @search={{perform this.search}} @onChange={{action 'select'}} + @onFocus={{action 'openOnClickOrTab'}} @dropdownClass="global-search-dropdown" @calculatePosition={{this.calculatePosition}} @searchMessageComponent="global-search/message" diff --git a/ui/tests/acceptance/search-test.js b/ui/tests/acceptance/search-test.js index 93215af7d3c..f951fd429fd 100644 --- a/ui/tests/acceptance/search-test.js +++ b/ui/tests/acceptance/search-test.js @@ -25,4 +25,14 @@ module('Acceptance | search', function(hooks) { assert.equal(search.options[1].text, 'vwxyz'); }); }); + + test('clicking the search field starts search immediately', async function(assert) { + await visit('/'); + + assert.notOk(PageLayout.navbar.search.field.isPresent); + + await PageLayout.navbar.search.click(); + + assert.ok(PageLayout.navbar.search.field.isPresent); + }); }); diff --git a/ui/tests/pages/layout.js b/ui/tests/pages/layout.js index 21a447fc0e6..f2d398f2b94 100644 --- a/ui/tests/pages/layout.js +++ b/ui/tests/pages/layout.js @@ -25,7 +25,7 @@ export default create({ }), field: { - scope: '.ember-power-select-dropdown--active', + scope: '.ember-power-select-search input', testContainer: 'html', resetScope: true, }, From 8fca80e4a143b1130f90e99d23df36e2861eb43c Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Tue, 16 Jun 2020 10:39:47 -0500 Subject: [PATCH 04/24] Add client search --- ui/app/components/global-search/control.js | 39 ++++++++++++++++++- .../components/global-search-dropdown.scss | 8 ++++ ui/tests/acceptance/search-test.js | 23 ++++++++--- ui/tests/pages/layout.js | 8 +++- 4 files changed, 69 insertions(+), 9 deletions(-) diff --git a/ui/app/components/global-search/control.js b/ui/app/components/global-search/control.js index 7f9319b5417..338e969cc0f 100644 --- a/ui/app/components/global-search/control.js +++ b/ui/app/components/global-search/control.js @@ -20,6 +20,10 @@ export default class GlobalSearchControl extends Component { this.jobSearch = JobSearch.create({ something: this, // FIXME what’s a good name? }); + + this.nodeSearch = NodeSearch.create({ + something: this, + }); } @task(function*(string) { @@ -27,15 +31,28 @@ export default class GlobalSearchControl extends Component { // FIXME no need to fetch on every search! const jobs = yield this.store.findAll('job'); + const nodes = yield this.store.findAll('node'); this.jobs = jobs.toArray(); + this.nodes = nodes.toArray(); try { const jobResults = this.jobSearch.listSearched; - return jobResults; + const nodeResults = this.nodeSearch.listSearched; + + return [ + { + groupName: `Jobs (${jobResults.length})`, + options: jobResults, + }, + { + groupName: `Clients (${nodeResults.length})`, + options: nodeResults, + }, + ]; } catch (e) { // eslint-disable-next-line - console.log('exception searching jobs', e); + console.log('exception searching', e); } }) search; @@ -87,3 +104,21 @@ class JobSearch extends EmberObject.extend(Searchable) { fuzzySearchEnabled = true; } + +@classic +class NodeSearch extends EmberObject.extend(Searchable) { + @computed + get searchProps() { + return ['id', 'name']; + } + + @computed + get fuzzySearchProps() { + return ['name']; + } + + @alias('something.nodes') listToSearch; + @alias('something.searchString') searchTerm; + + fuzzySearchEnabled = true; +} diff --git a/ui/app/styles/components/global-search-dropdown.scss b/ui/app/styles/components/global-search-dropdown.scss index adbeaec857f..7994e6397ac 100644 --- a/ui/app/styles/components/global-search-dropdown.scss +++ b/ui/app/styles/components/global-search-dropdown.scss @@ -29,4 +29,12 @@ } } } + + .ember-power-select-group-name { + text-transform: uppercase; + display: inline; + color: darken($grey-blue, 20%); + font-size: $size-7; + font-weight: $weight-semibold; + } } diff --git a/ui/tests/acceptance/search-test.js b/ui/tests/acceptance/search-test.js index f951fd429fd..6f186b4d487 100644 --- a/ui/tests/acceptance/search-test.js +++ b/ui/tests/acceptance/search-test.js @@ -9,8 +9,10 @@ module('Acceptance | search', function(hooks) { setupApplicationTest(hooks); setupMirage(hooks); - test('search searches jobs', async function(assert) { - server.create('node'); + test('search searches jobs and nodes', async function(assert) { + server.create('node', { name: 'xyz' }); + server.create('node', { name: 'aaa' }); + server.create('job', { id: 'vwxyz', namespaceId: 'default' }); server.create('job', { id: 'xyz', namespace: 'default' }); server.create('job', { id: 'abc', namespace: 'default' }); @@ -20,9 +22,20 @@ module('Acceptance | search', function(hooks) { await selectSearch(PageLayout.navbar.search.scope, 'xy'); PageLayout.navbar.search.as(search => { - assert.equal(search.options.length, 2); - assert.equal(search.options[0].text, 'xyz'); - assert.equal(search.options[1].text, 'vwxyz'); + assert.equal(search.groups.length, 2); + + search.groups[0].as(jobs => { + assert.equal(jobs.name, 'Jobs (2)'); + assert.equal(jobs.options.length, 2); + assert.equal(jobs.options[0].text, 'xyz'); + assert.equal(jobs.options[1].text, 'vwxyz'); + }); + + search.groups[1].as(clients => { + assert.equal(clients.name, 'Clients (1)'); + assert.equal(clients.options.length, 1); + assert.equal(clients.options[0].text, 'xyz'); + }); }); }); diff --git a/ui/tests/pages/layout.js b/ui/tests/pages/layout.js index f2d398f2b94..315646aaecc 100644 --- a/ui/tests/pages/layout.js +++ b/ui/tests/pages/layout.js @@ -18,10 +18,14 @@ export default create({ click: clickable('.ember-power-select-trigger'), - options: collection('.ember-power-select-option', { + groups: collection('.ember-power-select-group', { testContainer: '.ember-power-select-options', resetScope: true, - label: text(), + name: text('.ember-power-select-group-name'), + + options: collection('.ember-power-select-option', { + label: text(), + }), }), field: { From 00ced6ffc0b0002cac5b8a4855ccae1dce66670b Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Tue, 16 Jun 2020 11:11:52 -0500 Subject: [PATCH 05/24] Add navigation upon search selection --- ui/app/components/global-search/control.js | 11 ++++++++++- ui/tests/acceptance/search-test.js | 15 ++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/ui/app/components/global-search/control.js b/ui/app/components/global-search/control.js index 338e969cc0f..7d7a6548242 100644 --- a/ui/app/components/global-search/control.js +++ b/ui/app/components/global-search/control.js @@ -10,6 +10,7 @@ import classic from 'ember-classic-decorator'; @tagName('') export default class GlobalSearchControl extends Component { + @service router; @service store; searchString = null; @@ -57,7 +58,15 @@ export default class GlobalSearchControl extends Component { }) search; - @action select() {} + @action select(model) { + const itemModelName = model.constructor.modelName; + + if (itemModelName === 'job') { + this.router.transitionTo('jobs.job', model.name); + } else if (itemModelName === 'node') { + this.router.transitionTo('clients.client', model.id); + } + } @action openOnClickOrTab(select, { target }) { diff --git a/ui/tests/acceptance/search-test.js b/ui/tests/acceptance/search-test.js index 6f186b4d487..556f91974d1 100644 --- a/ui/tests/acceptance/search-test.js +++ b/ui/tests/acceptance/search-test.js @@ -1,5 +1,5 @@ import { module, test } from 'qunit'; -import { visit } from '@ember/test-helpers'; +import { currentURL, visit } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import { setupMirage } from 'ember-cli-mirage/test-support'; import PageLayout from 'nomad-ui/tests/pages/layout'; @@ -9,9 +9,9 @@ module('Acceptance | search', function(hooks) { setupApplicationTest(hooks); setupMirage(hooks); - test('search searches jobs and nodes', async function(assert) { + test('search searches jobs and nodes and navigates to chosen items', async function(assert) { server.create('node', { name: 'xyz' }); - server.create('node', { name: 'aaa' }); + const otherNode = server.create('node', { name: 'aaa' }); server.create('job', { id: 'vwxyz', namespaceId: 'default' }); server.create('job', { id: 'xyz', namespace: 'default' }); @@ -37,6 +37,15 @@ module('Acceptance | search', function(hooks) { assert.equal(clients.options[0].text, 'xyz'); }); }); + + await PageLayout.navbar.search.groups[0].options[0].click(); + assert.equal(currentURL(), '/jobs/xyz'); + + // Search only works once for unknown reasons! + // await selectSearch(PageLayout.navbar.search.scope, otherNode.id.substr(0, 3)); + + // await PageLayout.navbar.search.groups[0].options[0].click(); + // assert.equal(currentURL(), `/clients/${otherNode.id}`); }); test('clicking the search field starts search immediately', async function(assert) { From a7ded9bb9e05d1887efe88b3b99f3a53029bad77 Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Tue, 16 Jun 2020 11:14:28 -0500 Subject: [PATCH 06/24] Add indent to fix search icon overlap --- ui/app/styles/components/global-search-dropdown.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ui/app/styles/components/global-search-dropdown.scss b/ui/app/styles/components/global-search-dropdown.scss index 7994e6397ac..4e9c2f5ef3e 100644 --- a/ui/app/styles/components/global-search-dropdown.scss +++ b/ui/app/styles/components/global-search-dropdown.scss @@ -3,6 +3,11 @@ border: 0; position: fixed; + .ember-power-select-search { + margin-left: $icon-dimensions; + border: 0; + } + input, input:focus { background: transparent; From b24f73bc2b60b3abd26d87d8d64710871fc78a8c Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Tue, 16 Jun 2020 11:21:48 -0500 Subject: [PATCH 07/24] Remove currently-unused variable --- ui/tests/acceptance/search-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/tests/acceptance/search-test.js b/ui/tests/acceptance/search-test.js index 556f91974d1..fae05de17d1 100644 --- a/ui/tests/acceptance/search-test.js +++ b/ui/tests/acceptance/search-test.js @@ -11,7 +11,7 @@ module('Acceptance | search', function(hooks) { test('search searches jobs and nodes and navigates to chosen items', async function(assert) { server.create('node', { name: 'xyz' }); - const otherNode = server.create('node', { name: 'aaa' }); + server.create('node', { name: 'aaa' }); server.create('job', { id: 'vwxyz', namespaceId: 'default' }); server.create('job', { id: 'xyz', namespace: 'default' }); From 180f54a3f75029ef1cf35b937b4add80b34a0df9 Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Tue, 16 Jun 2020 12:13:10 -0500 Subject: [PATCH 08/24] Add / shortcut to open search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I’d prefer for this to be covered by automated tests but it seems like it only half-works… the placeholder in the field disappears with the use of keyDown but the field doesn’t look active. Actually pressing / does work though. --- ui/app/components/global-search/control.js | 17 +- .../components/global-search/control.hbs | 6 +- ui/config/environment.js | 4 + ui/package.json | 1 + ui/tests/acceptance/search-test.js | 12 +- ui/yarn.lock | 378 +++++++++++++----- 6 files changed, 305 insertions(+), 113 deletions(-) diff --git a/ui/app/components/global-search/control.js b/ui/app/components/global-search/control.js index 7d7a6548242..dffd372d362 100644 --- a/ui/app/components/global-search/control.js +++ b/ui/app/components/global-search/control.js @@ -58,7 +58,15 @@ export default class GlobalSearchControl extends Component { }) search; - @action select(model) { + @action + open() { + if (this.select) { + this.select.actions.open(); + } + } + + @action + selectOption(model) { const itemModelName = model.constructor.modelName; if (itemModelName === 'job') { @@ -68,6 +76,13 @@ export default class GlobalSearchControl extends Component { } } + @action + storeSelect(select) { + if (select) { + this.select = select; + } + } + @action openOnClickOrTab(select, { target }) { // Bypass having to press enter to access search after clicking/tabbing diff --git a/ui/app/templates/components/global-search/control.hbs b/ui/app/templates/components/global-search/control.hbs index 759594085db..72395b8b299 100644 --- a/ui/app/templates/components/global-search/control.hbs +++ b/ui/app/templates/components/global-search/control.hbs @@ -4,12 +4,14 @@ data-test-search @searchEnabled={{true}} @search={{perform this.search}} - @onChange={{action 'select'}} + @onChange={{action 'selectOption'}} @onFocus={{action 'openOnClickOrTab'}} @dropdownClass="global-search-dropdown" @calculatePosition={{this.calculatePosition}} @searchMessageComponent="global-search/message" @triggerComponent="global-search/trigger" + @registerAPI={{action 'storeSelect'}} as |option|> {{option.name}} - </PowerSelect> \ No newline at end of file + </PowerSelect> + {{on-key '/' (action 'open')}} \ No newline at end of file diff --git a/ui/config/environment.js b/ui/config/environment.js index 9d758e0a62c..d464ba5a0b8 100644 --- a/ui/config/environment.js +++ b/ui/config/environment.js @@ -30,6 +30,10 @@ module.exports = function(environment) { mirageWithTokens: true, mirageWithRegions: true, }, + + emberKeyboard: { + disableInputsInitializer: true, + }, }; if (environment === 'development') { diff --git a/ui/package.json b/ui/package.json index b4158b09f68..80956783b42 100644 --- a/ui/package.json +++ b/ui/package.json @@ -82,6 +82,7 @@ "ember-fetch": "^6.5.0", "ember-inflector": "^3.0.0", "ember-inline-svg": "^0.3.0", + "ember-keyboard": "^6.0.0", "ember-load-initializers": "^2.0.0", "ember-maybe-import-regenerator": "^0.1.6", "ember-moment": "^7.8.1", diff --git a/ui/tests/acceptance/search-test.js b/ui/tests/acceptance/search-test.js index fae05de17d1..78d8b394bd1 100644 --- a/ui/tests/acceptance/search-test.js +++ b/ui/tests/acceptance/search-test.js @@ -1,9 +1,10 @@ -import { module, test } from 'qunit'; +import { module, skip, test } from 'qunit'; import { currentURL, visit } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import { setupMirage } from 'ember-cli-mirage/test-support'; import PageLayout from 'nomad-ui/tests/pages/layout'; import { selectSearch } from 'ember-power-select/test-support'; +import { keyDown } from 'ember-keyboard/test-support/test-helpers'; module('Acceptance | search', function(hooks) { setupApplicationTest(hooks); @@ -57,4 +58,13 @@ module('Acceptance | search', function(hooks) { assert.ok(PageLayout.navbar.search.field.isPresent); }); + + // This DOES work but keyDown doesn’t seem quite right… + skip('pressing slash starts a search', async function(assert) { + await visit('/'); + + assert.notOk(PageLayout.navbar.search.field.isPresent); + await keyDown('/'); + assert.ok(PageLayout.navbar.search.field.isPresent); + }); }); diff --git a/ui/yarn.lock b/ui/yarn.lock index f90cf1b363a..f16a9672b2e 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -52,6 +52,28 @@ semver "^5.4.1" source-map "^0.5.0" +"@babel/core@^7.10.2", "@babel/core@^7.8.3", "@babel/core@^7.9.0": + version "7.10.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.2.tgz#bd6786046668a925ac2bd2fd95b579b92a23b36a" + integrity sha512-KQmV9yguEjQsXqyOUGKjS4+3K8/DlOCE2pZcq4augdQmtTy5iv5EHtmMSJ7V4c1BIPjuwtZYqYLCq9Ga+hGBRQ== + dependencies: + "@babel/code-frame" "^7.10.1" + "@babel/generator" "^7.10.2" + "@babel/helper-module-transforms" "^7.10.1" + "@babel/helpers" "^7.10.1" + "@babel/parser" "^7.10.2" + "@babel/template" "^7.10.1" + "@babel/traverse" "^7.10.1" + "@babel/types" "^7.10.2" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.13" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + "@babel/core@^7.2.2": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.5.5.tgz#17b2686ef0d6bc58f963dddd68ab669755582c30" @@ -92,28 +114,6 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.8.3", "@babel/core@^7.9.0": - version "7.10.2" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.2.tgz#bd6786046668a925ac2bd2fd95b579b92a23b36a" - integrity sha512-KQmV9yguEjQsXqyOUGKjS4+3K8/DlOCE2pZcq4augdQmtTy5iv5EHtmMSJ7V4c1BIPjuwtZYqYLCq9Ga+hGBRQ== - dependencies: - "@babel/code-frame" "^7.10.1" - "@babel/generator" "^7.10.2" - "@babel/helper-module-transforms" "^7.10.1" - "@babel/helpers" "^7.10.1" - "@babel/parser" "^7.10.2" - "@babel/template" "^7.10.1" - "@babel/traverse" "^7.10.1" - "@babel/types" "^7.10.2" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.1" - json5 "^2.1.2" - lodash "^4.17.13" - resolve "^1.3.2" - semver "^5.4.1" - source-map "^0.5.0" - "@babel/generator@^7.10.1", "@babel/generator@^7.10.2": version "7.10.2" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.2.tgz#0fa5b5b2389db8bfdfcc3492b551ee20f5dd69a9" @@ -663,6 +663,15 @@ "@babel/helper-create-class-features-plugin" "^7.4.0" "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-proposal-decorators@^7.10.1", "@babel/plugin-proposal-decorators@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.10.1.tgz#9373c2d8db45345c6e30452ad77b469758e5c8f7" + integrity sha512-xBfteh352MTke2U1NpclzMDmAmCdQ2fBZjhZQQfGTjXw6qcRYMkt528sA1U8o0ThDCSeuETXIj5bOGdxN+5gkw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/plugin-syntax-decorators" "^7.10.1" + "@babel/plugin-proposal-decorators@^7.3.0": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.4.4.tgz#de9b2a1a8ab0196f378e2a82f10b6e2a36f21cc0" @@ -672,15 +681,6 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-decorators" "^7.2.0" -"@babel/plugin-proposal-decorators@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.10.1.tgz#9373c2d8db45345c6e30452ad77b469758e5c8f7" - integrity sha512-xBfteh352MTke2U1NpclzMDmAmCdQ2fBZjhZQQfGTjXw6qcRYMkt528sA1U8o0ThDCSeuETXIj5bOGdxN+5gkw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-syntax-decorators" "^7.10.1" - "@babel/plugin-proposal-dynamic-import@^7.10.1": version "7.10.1" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.1.tgz#e36979dc1dc3b73f6d6816fc4951da2363488ef0" @@ -1424,6 +1424,16 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-runtime@^7.10.1", "@babel/plugin-transform-runtime@^7.9.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.10.1.tgz#fd1887f749637fb2ed86dc278e79eb41df37f4b1" + integrity sha512-4w2tcglDVEwXJ5qxsY++DgWQdNJcCCsPxfT34wCUwIf2E7dI7pMpH8JczkMBbgBTNzBX62SZlNJ9H+De6Zebaw== + dependencies: + "@babel/helper-module-imports" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + resolve "^1.8.1" + semver "^5.5.1" + "@babel/plugin-transform-runtime@^7.2.0": version "7.5.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.5.0.tgz#45242c2c9281158c5f06d25beebac63e498a284e" @@ -1434,16 +1444,6 @@ resolve "^1.8.1" semver "^5.5.1" -"@babel/plugin-transform-runtime@^7.9.0": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.10.1.tgz#fd1887f749637fb2ed86dc278e79eb41df37f4b1" - integrity sha512-4w2tcglDVEwXJ5qxsY++DgWQdNJcCCsPxfT34wCUwIf2E7dI7pMpH8JczkMBbgBTNzBX62SZlNJ9H+De6Zebaw== - dependencies: - "@babel/helper-module-imports" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - resolve "^1.8.1" - semver "^5.5.1" - "@babel/plugin-transform-shorthand-properties@^7.10.1": version "7.10.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.1.tgz#e8b54f238a1ccbae482c4dce946180ae7b3143f3" @@ -1525,7 +1525,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-typescript@^7.9.0": +"@babel/plugin-transform-typescript@^7.10.1", "@babel/plugin-transform-typescript@^7.9.0": version "7.10.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.10.1.tgz#2c54daea231f602468686d9faa76f182a94507a6" integrity sha512-v+QWKlmCnsaimLeqq9vyCsVRMViZG1k2SZTlcZvB+TqyH570Zsij8nvVUZzOASCRiQFUxkLrn9Wg/kH0zgy5OQ== @@ -1583,7 +1583,7 @@ core-js "^2.6.5" regenerator-runtime "^0.13.2" -"@babel/polyfill@^7.8.3", "@babel/polyfill@^7.8.7": +"@babel/polyfill@^7.10.1", "@babel/polyfill@^7.8.3", "@babel/polyfill@^7.8.7": version "7.10.1" resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.10.1.tgz#d56d4c8be8dd6ec4dce2649474e9b707089f739f" integrity sha512-TviueJ4PBW5p48ra8IMtLXVkDucrlOZAIZ+EXqS3Ot4eukHbWiqcn7DcqpA1k5PcKtmJ4Xl9xwdv6yQvvcA+3g== @@ -1647,63 +1647,7 @@ js-levenshtein "^1.1.3" semver "^5.5.0" -"@babel/preset-env@^7.4.5": - version "7.6.2" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.6.2.tgz#abbb3ed785c7fe4220d4c82a53621d71fc0c75d3" - integrity sha512-Ru7+mfzy9M1/YTEtlDS8CD45jd22ngb9tXnn64DvQK3ooyqSw9K4K9DUWmYknTTVk4TqygL9dqCrZgm1HMea/Q== - dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-proposal-async-generator-functions" "^7.2.0" - "@babel/plugin-proposal-dynamic-import" "^7.5.0" - "@babel/plugin-proposal-json-strings" "^7.2.0" - "@babel/plugin-proposal-object-rest-spread" "^7.6.2" - "@babel/plugin-proposal-optional-catch-binding" "^7.2.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.6.2" - "@babel/plugin-syntax-async-generators" "^7.2.0" - "@babel/plugin-syntax-dynamic-import" "^7.2.0" - "@babel/plugin-syntax-json-strings" "^7.2.0" - "@babel/plugin-syntax-object-rest-spread" "^7.2.0" - "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" - "@babel/plugin-transform-arrow-functions" "^7.2.0" - "@babel/plugin-transform-async-to-generator" "^7.5.0" - "@babel/plugin-transform-block-scoped-functions" "^7.2.0" - "@babel/plugin-transform-block-scoping" "^7.6.2" - "@babel/plugin-transform-classes" "^7.5.5" - "@babel/plugin-transform-computed-properties" "^7.2.0" - "@babel/plugin-transform-destructuring" "^7.6.0" - "@babel/plugin-transform-dotall-regex" "^7.6.2" - "@babel/plugin-transform-duplicate-keys" "^7.5.0" - "@babel/plugin-transform-exponentiation-operator" "^7.2.0" - "@babel/plugin-transform-for-of" "^7.4.4" - "@babel/plugin-transform-function-name" "^7.4.4" - "@babel/plugin-transform-literals" "^7.2.0" - "@babel/plugin-transform-member-expression-literals" "^7.2.0" - "@babel/plugin-transform-modules-amd" "^7.5.0" - "@babel/plugin-transform-modules-commonjs" "^7.6.0" - "@babel/plugin-transform-modules-systemjs" "^7.5.0" - "@babel/plugin-transform-modules-umd" "^7.2.0" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.6.2" - "@babel/plugin-transform-new-target" "^7.4.4" - "@babel/plugin-transform-object-super" "^7.5.5" - "@babel/plugin-transform-parameters" "^7.4.4" - "@babel/plugin-transform-property-literals" "^7.2.0" - "@babel/plugin-transform-regenerator" "^7.4.5" - "@babel/plugin-transform-reserved-words" "^7.2.0" - "@babel/plugin-transform-shorthand-properties" "^7.2.0" - "@babel/plugin-transform-spread" "^7.6.2" - "@babel/plugin-transform-sticky-regex" "^7.2.0" - "@babel/plugin-transform-template-literals" "^7.4.4" - "@babel/plugin-transform-typeof-symbol" "^7.2.0" - "@babel/plugin-transform-unicode-regex" "^7.6.2" - "@babel/types" "^7.6.0" - browserslist "^4.6.0" - core-js-compat "^3.1.1" - invariant "^2.2.2" - js-levenshtein "^1.1.3" - semver "^5.5.0" - -"@babel/preset-env@^7.9.0": +"@babel/preset-env@^7.10.2", "@babel/preset-env@^7.9.0": version "7.10.2" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.10.2.tgz#715930f2cf8573b0928005ee562bed52fb65fdfb" integrity sha512-MjqhX0RZaEgK/KueRzh+3yPSk30oqDKJ5HP5tqTSB1e2gzGS3PLy7K0BIpnp78+0anFuSwOeuCf1zZO7RzRvEA== @@ -1773,6 +1717,62 @@ levenary "^1.1.1" semver "^5.5.0" +"@babel/preset-env@^7.4.5": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.6.2.tgz#abbb3ed785c7fe4220d4c82a53621d71fc0c75d3" + integrity sha512-Ru7+mfzy9M1/YTEtlDS8CD45jd22ngb9tXnn64DvQK3ooyqSw9K4K9DUWmYknTTVk4TqygL9dqCrZgm1HMea/Q== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-async-generator-functions" "^7.2.0" + "@babel/plugin-proposal-dynamic-import" "^7.5.0" + "@babel/plugin-proposal-json-strings" "^7.2.0" + "@babel/plugin-proposal-object-rest-spread" "^7.6.2" + "@babel/plugin-proposal-optional-catch-binding" "^7.2.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.6.2" + "@babel/plugin-syntax-async-generators" "^7.2.0" + "@babel/plugin-syntax-dynamic-import" "^7.2.0" + "@babel/plugin-syntax-json-strings" "^7.2.0" + "@babel/plugin-syntax-object-rest-spread" "^7.2.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" + "@babel/plugin-transform-arrow-functions" "^7.2.0" + "@babel/plugin-transform-async-to-generator" "^7.5.0" + "@babel/plugin-transform-block-scoped-functions" "^7.2.0" + "@babel/plugin-transform-block-scoping" "^7.6.2" + "@babel/plugin-transform-classes" "^7.5.5" + "@babel/plugin-transform-computed-properties" "^7.2.0" + "@babel/plugin-transform-destructuring" "^7.6.0" + "@babel/plugin-transform-dotall-regex" "^7.6.2" + "@babel/plugin-transform-duplicate-keys" "^7.5.0" + "@babel/plugin-transform-exponentiation-operator" "^7.2.0" + "@babel/plugin-transform-for-of" "^7.4.4" + "@babel/plugin-transform-function-name" "^7.4.4" + "@babel/plugin-transform-literals" "^7.2.0" + "@babel/plugin-transform-member-expression-literals" "^7.2.0" + "@babel/plugin-transform-modules-amd" "^7.5.0" + "@babel/plugin-transform-modules-commonjs" "^7.6.0" + "@babel/plugin-transform-modules-systemjs" "^7.5.0" + "@babel/plugin-transform-modules-umd" "^7.2.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.6.2" + "@babel/plugin-transform-new-target" "^7.4.4" + "@babel/plugin-transform-object-super" "^7.5.5" + "@babel/plugin-transform-parameters" "^7.4.4" + "@babel/plugin-transform-property-literals" "^7.2.0" + "@babel/plugin-transform-regenerator" "^7.4.5" + "@babel/plugin-transform-reserved-words" "^7.2.0" + "@babel/plugin-transform-shorthand-properties" "^7.2.0" + "@babel/plugin-transform-spread" "^7.6.2" + "@babel/plugin-transform-sticky-regex" "^7.2.0" + "@babel/plugin-transform-template-literals" "^7.4.4" + "@babel/plugin-transform-typeof-symbol" "^7.2.0" + "@babel/plugin-transform-unicode-regex" "^7.6.2" + "@babel/types" "^7.6.0" + browserslist "^4.6.0" + core-js-compat "^3.1.1" + invariant "^2.2.2" + js-levenshtein "^1.1.3" + semver "^5.5.0" + "@babel/preset-modules@^0.1.3": version "0.1.3" resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.3.tgz#13242b53b5ef8c883c3cf7dddd55b36ce80fbc72" @@ -1798,6 +1798,13 @@ dependencies: regenerator-runtime "^0.13.2" +"@babel/runtime@^7.10.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.0": + version "7.10.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.2.tgz#d103f21f2602497d38348a32e008637d506db839" + integrity sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/runtime@^7.2.0": version "7.5.4" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.4.tgz#cb7d1ad7c6d65676e66b47186577930465b5271b" @@ -1812,13 +1819,6 @@ dependencies: regenerator-runtime "^0.13.2" -"@babel/runtime@^7.8.4", "@babel/runtime@^7.9.0": - version "7.10.2" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.2.tgz#d103f21f2602497d38348a32e008637d506db839" - integrity sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg== - dependencies: - regenerator-runtime "^0.13.4" - "@babel/runtime@^7.8.7": version "7.9.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" @@ -2076,6 +2076,11 @@ dependencies: ember-cli-babel "^7.1.3" +"@ember/edition-utils@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ember/edition-utils/-/edition-utils-1.2.0.tgz#a039f542dc14c8e8299c81cd5abba95e2459cfa6" + integrity sha512-VmVq/8saCaPdesQmftPqbFtxJWrzxNGSQ+e8x8LLe3Hjm36pJ04Q8LeORGZkAeOhldoUX9seLGmSaHeXkIqoog== + "@ember/optional-features@^0.7.0": version "0.7.0" resolved "https://registry.yarnpkg.com/@ember/optional-features/-/optional-features-0.7.0.tgz#f65a858007020ddfb8342f586112750c32abd2d9" @@ -4050,7 +4055,7 @@ babel-plugin-debug-macros@^0.3.0: dependencies: semver "^5.3.0" -babel-plugin-debug-macros@^0.3.2: +babel-plugin-debug-macros@^0.3.2, babel-plugin-debug-macros@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/babel-plugin-debug-macros/-/babel-plugin-debug-macros-0.3.3.tgz#29c3449d663f61c7385f5b8c72d8015b069a5cb7" integrity sha512-E+NI8TKpxJDBbVkdWkwHrKgJi696mnRL8XYrOPYw82veNHPDORM9WIQifl6TpIo8PNy2tU2skPqbfkmHXrHKQA== @@ -4133,6 +4138,11 @@ babel-plugin-htmlbars-inline-precompile@^1.0.0: resolved "https://registry.yarnpkg.com/babel-plugin-htmlbars-inline-precompile/-/babel-plugin-htmlbars-inline-precompile-1.0.0.tgz#a9d2f6eaad8a3f3d361602de593a8cbef8179c22" integrity sha512-4jvKEHR1bAX03hBDZ94IXsYCj3bwk9vYsn6ux6JZNL2U5pvzCWjqyrGahfsGNrhERyxw8IqcirOi9Q6WCo3dkQ== +babel-plugin-htmlbars-inline-precompile@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/babel-plugin-htmlbars-inline-precompile/-/babel-plugin-htmlbars-inline-precompile-3.1.0.tgz#85085b50385277f2b331ebd54e22fa91aadc24e8" + integrity sha512-ar6c4YVX6OV7Dzpq7xRyllQrHwVEzJf41qysYULnD6tu6TS+y1FxT2VcEvMC6b9Rq9xoHMzvB79HO3av89JCGg== + babel-plugin-macros@^2.0.0, babel-plugin-macros@^2.4.5: version "2.6.1" resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.6.1.tgz#41f7ead616fc36f6a93180e89697f69f51671181" @@ -5002,6 +5012,23 @@ broccoli-babel-transpiler@^7.4.0: rsvp "^4.8.4" workerpool "^3.1.1" +broccoli-babel-transpiler@^7.5.0: + version "7.5.0" + resolved "https://registry.yarnpkg.com/broccoli-babel-transpiler/-/broccoli-babel-transpiler-7.5.0.tgz#6137e2c9324eebe1d83b6a01c7b445bfc5612d49" + integrity sha512-CrLrI8HX7mDqVR+/r8WiTZuQQh6hGMGwal9jyKk+kpk7t/4hqL6oQ06FRt81kazbHm4bil4WJ+kGB+aOfAx+XA== + dependencies: + "@babel/core" "^7.10.2" + "@babel/polyfill" "^7.10.1" + broccoli-funnel "^2.0.2" + broccoli-merge-trees "^3.0.2" + broccoli-persistent-filter "^2.2.1" + clone "^2.1.2" + hash-for-dep "^1.4.7" + heimdalljs-logger "^0.1.9" + json-stable-stringify "^1.0.1" + rsvp "^4.8.4" + workerpool "^3.1.1" + broccoli-builder@^0.18.14: version "0.18.14" resolved "https://registry.yarnpkg.com/broccoli-builder/-/broccoli-builder-0.18.14.tgz#4b79e2f844de11a4e1b816c3f49c6df4776c312d" @@ -5302,6 +5329,11 @@ broccoli-module-unification-reexporter@^1.0.0: mkdirp "^0.5.1" walk-sync "^0.3.2" +broccoli-node-api@^1.6.0, broccoli-node-api@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/broccoli-node-api/-/broccoli-node-api-1.7.0.tgz#391aa6edecd2a42c63c111b4162956b2fa288cb6" + integrity sha512-QIqLSVJWJUVOhclmkmypJJH9u9s/aWH4+FH6Q6Ju5l+Io4dtwqdPUNmDfw40o6sxhbZHhqGujDJuHTML1wG8Yw== + broccoli-node-info@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/broccoli-node-info/-/broccoli-node-info-1.1.0.tgz#3aa2e31e07e5bdb516dd25214f7c45ba1c459412" @@ -5312,6 +5344,13 @@ broccoli-node-info@^2.1.0: resolved "https://registry.yarnpkg.com/broccoli-node-info/-/broccoli-node-info-2.1.0.tgz#ca84560e8570ff78565bea1699866ddbf58ad644" integrity sha512-l6qDuboJThHfRVVWQVaTs++bFdrFTP0gJXgsWenczc1PavRVUmL1Eyb2swTAXXMpDOnr2zhNOBLx4w9AxkqbPQ== +broccoli-output-wrapper@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/broccoli-output-wrapper/-/broccoli-output-wrapper-2.0.0.tgz#f1e0b9b2f259a67fd41a380141c3c20b096828e6" + integrity sha512-V/ozejo+snzNf75i/a6iTmp71k+rlvqjE3+jYfimuMwR1tjNNRdtfno+NGNQB2An9bIAeqZnKhMDurAznHAdtA== + dependencies: + heimdalljs-logger "^0.1.10" + broccoli-persistent-filter@^1.1.5, broccoli-persistent-filter@^1.1.6, broccoli-persistent-filter@^1.4.3: version "1.4.6" resolved "https://registry.yarnpkg.com/broccoli-persistent-filter/-/broccoli-persistent-filter-1.4.6.tgz#80762d19000880a77da33c34373299c0f6a3e615" @@ -5400,6 +5439,19 @@ broccoli-plugin@^2.0.0: rimraf "^2.3.4" symlink-or-copy "^1.1.8" +broccoli-plugin@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/broccoli-plugin/-/broccoli-plugin-3.1.0.tgz#54ba6dd90a42ec3db5624063292610e326b1e542" + integrity sha512-7w7FP8WJYjLvb0eaw27LO678TGGaom++49O1VYIuzjhXjK5kn2+AMlDm7CaUFw4F7CLGoVQeZ84d8gICMJa4lA== + dependencies: + broccoli-node-api "^1.6.0" + broccoli-output-wrapper "^2.0.0" + fs-merger "^3.0.1" + promise-map-series "^0.2.1" + quick-temp "^0.1.3" + rimraf "^2.3.4" + symlink-or-copy "^1.1.8" + broccoli-rollup@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/broccoli-rollup/-/broccoli-rollup-2.1.1.tgz#0b77dc4b7560a53e998ea85f3b56772612d4988d" @@ -7616,6 +7668,38 @@ ember-cli-babel@^7.11.1: rimraf "^3.0.1" semver "^5.5.0" +ember-cli-babel@^7.19.0: + version "7.21.0" + resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.21.0.tgz#c79e888876aee87dfc3260aee7cb580b74264bbc" + integrity sha512-jHVi9melAibo0DrAG3GAxid+29xEyjBoU53652B4qcu3Xp58feZGTH/JGXovH7TjvbeNn65zgNyoV3bk1onULw== + dependencies: + "@babel/core" "^7.10.2" + "@babel/helper-compilation-targets" "^7.10.2" + "@babel/plugin-proposal-class-properties" "^7.10.1" + "@babel/plugin-proposal-decorators" "^7.10.1" + "@babel/plugin-transform-modules-amd" "^7.10.1" + "@babel/plugin-transform-runtime" "^7.10.1" + "@babel/plugin-transform-typescript" "^7.10.1" + "@babel/polyfill" "^7.10.1" + "@babel/preset-env" "^7.10.2" + "@babel/runtime" "^7.10.2" + amd-name-resolver "^1.2.1" + babel-plugin-debug-macros "^0.3.3" + babel-plugin-ember-data-packages-polyfill "^0.1.2" + babel-plugin-ember-modules-api-polyfill "^2.13.4" + babel-plugin-module-resolver "^3.1.1" + broccoli-babel-transpiler "^7.5.0" + broccoli-debug "^0.6.4" + broccoli-funnel "^2.0.1" + broccoli-source "^1.1.0" + clone "^2.1.2" + ember-cli-babel-plugin-helpers "^1.1.0" + ember-cli-version-checker "^4.1.0" + ensure-posix-path "^1.0.2" + fixturify-project "^1.10.0" + rimraf "^3.0.1" + semver "^5.5.0" + ember-cli-broccoli-sane-watcher@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ember-cli-broccoli-sane-watcher/-/ember-cli-broccoli-sane-watcher-3.0.0.tgz#dc1812c047e1ceec4413d3c41b51a9ffc61b4cfe" @@ -7720,6 +7804,26 @@ ember-cli-htmlbars@^3.0.1, ember-cli-htmlbars@^3.1.0: json-stable-stringify "^1.0.1" strip-bom "^3.0.0" +ember-cli-htmlbars@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-4.3.1.tgz#4af8adc21ab3c4953f768956b7f7d207782cb175" + integrity sha512-CW6AY/yzjeVqoRtItOKj3hcYzc5dWPRETmeCzr2Iqjt5vxiVtpl0z5VTqHqIlT5fsFx6sGWBQXNHIe+ivYsxXQ== + dependencies: + "@ember/edition-utils" "^1.2.0" + babel-plugin-htmlbars-inline-precompile "^3.0.1" + broccoli-debug "^0.6.5" + broccoli-persistent-filter "^2.3.1" + broccoli-plugin "^3.1.0" + common-tags "^1.8.0" + ember-cli-babel-plugin-helpers "^1.1.0" + fs-tree-diff "^2.0.1" + hash-for-dep "^1.5.1" + heimdalljs-logger "^0.1.10" + json-stable-stringify "^1.0.1" + semver "^6.3.0" + strip-bom "^4.0.0" + walk-sync "^2.0.2" + ember-cli-import-polyfill@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/ember-cli-import-polyfill/-/ember-cli-import-polyfill-0.2.0.tgz#c1a08a8affb45c97b675926272fe78cf4ca166f2" @@ -8043,6 +8147,15 @@ ember-compatibility-helpers@^1.1.1, ember-compatibility-helpers@^1.2.0: ember-cli-version-checker "^2.1.1" semver "^5.4.1" +ember-compatibility-helpers@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ember-compatibility-helpers/-/ember-compatibility-helpers-1.2.1.tgz#87c92c4303f990ff455c28ca39fb3ee11441aa16" + integrity sha512-6wzYvnhg1ihQUT5yGqnLtleq3Nv5KNv79WhrEuNU9SwR4uIxCO+KpyC7r3d5VI0EM7/Nmv9Nd0yTkzmTMdVG1A== + dependencies: + babel-plugin-debug-macros "^0.2.0" + ember-cli-version-checker "^2.1.1" + semver "^5.4.1" + ember-composable-helpers@^2.0.3: version "2.3.1" resolved "https://registry.yarnpkg.com/ember-composable-helpers/-/ember-composable-helpers-2.3.1.tgz#db98ad8b55d053e2ac216b9da091c9e7a3b9f453" @@ -8198,6 +8311,16 @@ ember-inline-svg@^0.3.0: svgo "~1.2.2" walk-sync "^0.3.1" +ember-keyboard@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/ember-keyboard/-/ember-keyboard-6.0.0.tgz#0bf929bfa1a5935109e59c1b816e65ad2db0383b" + integrity sha512-QJAhs8173hqy8e/Wj/98+5tJ43I5UKq35lsx3w8icHL5iisfXo8s89gOtn1C2015Y4JoMNsVOkNSCnBfhx1UQg== + dependencies: + ember-cli-babel "^7.19.0" + ember-cli-htmlbars "^4.3.1" + ember-compatibility-helpers "^1.2.1" + ember-modifier "^1.0.3" + ember-load-initializers@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ember-load-initializers/-/ember-load-initializers-2.1.0.tgz#b402815ab9c823ff48a1369b52633721987e72d4" @@ -8233,7 +8356,7 @@ ember-maybe-in-element@^0.4.0: dependencies: ember-cli-babel "^7.1.0" -ember-modifier-manager-polyfill@^1.1.0: +ember-modifier-manager-polyfill@^1.1.0, ember-modifier-manager-polyfill@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/ember-modifier-manager-polyfill/-/ember-modifier-manager-polyfill-1.2.0.tgz#cf4444e11a42ac84f5c8badd85e635df57565dda" integrity sha512-bnaKF1LLKMkBNeDoetvIJ4vhwRPKIIumWr6dbVuW6W6p4QV8ZiO+GdF8J7mxDNlog9CeL9Z/7wam4YS86G8BYA== @@ -8242,6 +8365,17 @@ ember-modifier-manager-polyfill@^1.1.0: ember-cli-version-checker "^2.1.2" ember-compatibility-helpers "^1.2.0" +ember-modifier@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/ember-modifier/-/ember-modifier-1.0.3.tgz#ab18250666aad17c0d9170feb178e954148eb4ed" + integrity sha512-vWuFyvdkULUyasvEXxe5lcfuPZV/Uqe+b0IQ1yU+TY1RSJnFdVUu/CVHT8Bu4HUJInqzAihwPMTwty7fypzi5Q== + dependencies: + ember-cli-babel "^7.11.1" + ember-cli-is-package-missing "^1.0.0" + ember-cli-normalize-entity-name "^1.0.0" + ember-cli-string-utils "^1.1.0" + ember-modifier-manager-polyfill "^1.2.0" + ember-moment@^7.8.1: version "7.8.1" resolved "https://registry.yarnpkg.com/ember-moment/-/ember-moment-7.8.1.tgz#6f77cf941d1a92e231b2f4b810e113b2fae50c5f" @@ -9547,6 +9681,18 @@ fs-extra@^8.0.1: jsonfile "^4.0.0" universalify "^0.1.0" +fs-merger@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/fs-merger/-/fs-merger-3.1.0.tgz#f30f74f6c70b2ff7333ec074f3d2f22298152f3b" + integrity sha512-RZ9JtqugaE8Rkt7idO5NSwcxEGSDZpLmVFjtVQUm3f+bWun7JAU6fKyU6ZJUeUnKdJwGx8uaro+K4QQfOR7vpA== + dependencies: + broccoli-node-api "^1.7.0" + broccoli-node-info "^2.1.0" + fs-extra "^8.0.1" + fs-tree-diff "^2.0.1" + rimraf "^2.6.3" + walk-sync "^2.0.2" + fs-minipass@^1.2.5: version "1.2.6" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07" @@ -15484,6 +15630,11 @@ strip-bom@^3.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" @@ -16416,6 +16567,15 @@ walk-sync@^1.0.0, walk-sync@^1.1.3: ensure-posix-path "^1.1.0" matcher-collection "^1.1.1" +walk-sync@^2.0.2: + version "2.1.0" + resolved "https://registry.yarnpkg.com/walk-sync/-/walk-sync-2.1.0.tgz#e214248e5f5cf497ddd87db5a2a02e47e29c6501" + integrity sha512-KpH9Xw64LNSx7/UI+3guRZvJWlDxVA4+KKb/4puRoVrG8GkvZRxnF3vhxdjgpoKJGL2TVg1OrtkXIE/VuGPLHQ== + dependencies: + "@types/minimatch" "^3.0.3" + ensure-posix-path "^1.1.0" + matcher-collection "^2.0.0" + walker@~1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" From e7cc5f59d6d14d8351e4105f4f1ea2cf9cd55d47 Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Tue, 16 Jun 2020 13:16:44 -0500 Subject: [PATCH 09/24] Add test for navigating to nodes --- ui/tests/acceptance/search-test.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ui/tests/acceptance/search-test.js b/ui/tests/acceptance/search-test.js index 78d8b394bd1..9caf85ec81f 100644 --- a/ui/tests/acceptance/search-test.js +++ b/ui/tests/acceptance/search-test.js @@ -12,7 +12,7 @@ module('Acceptance | search', function(hooks) { test('search searches jobs and nodes and navigates to chosen items', async function(assert) { server.create('node', { name: 'xyz' }); - server.create('node', { name: 'aaa' }); + const otherNode = server.create('node', { name: 'aaa' }); server.create('job', { id: 'vwxyz', namespaceId: 'default' }); server.create('job', { id: 'xyz', namespace: 'default' }); @@ -42,11 +42,10 @@ module('Acceptance | search', function(hooks) { await PageLayout.navbar.search.groups[0].options[0].click(); assert.equal(currentURL(), '/jobs/xyz'); - // Search only works once for unknown reasons! - // await selectSearch(PageLayout.navbar.search.scope, otherNode.id.substr(0, 3)); + await selectSearch(PageLayout.navbar.search.scope, otherNode.id.substr(0, 3)); - // await PageLayout.navbar.search.groups[0].options[0].click(); - // assert.equal(currentURL(), `/clients/${otherNode.id}`); + await PageLayout.navbar.search.groups[1].options[0].click(); + assert.equal(currentURL(), `/clients/${otherNode.id}`); }); test('clicking the search field starts search immediately', async function(assert) { From 94f42d02c1c92f09db03de0d93f3d929e8eb1fa3 Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Wed, 17 Jun 2020 08:33:02 -0500 Subject: [PATCH 10/24] Expand scope of try to catch more errors Has it always been that errors are silently swallowed? Mysterious --- ui/app/components/global-search/control.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ui/app/components/global-search/control.js b/ui/app/components/global-search/control.js index dffd372d362..5405ee42e0d 100644 --- a/ui/app/components/global-search/control.js +++ b/ui/app/components/global-search/control.js @@ -28,16 +28,16 @@ export default class GlobalSearchControl extends Component { } @task(function*(string) { - this.searchString = string; + try { + this.searchString = string; - // FIXME no need to fetch on every search! - const jobs = yield this.store.findAll('job'); - const nodes = yield this.store.findAll('node'); + // FIXME no need to fetch on every search! + const jobs = yield this.store.findAll('job'); + const nodes = yield this.store.findAll('node'); - this.jobs = jobs.toArray(); - this.nodes = nodes.toArray(); + this.jobs = jobs.toArray(); + this.nodes = nodes.toArray(); - try { const jobResults = this.jobSearch.listSearched; const nodeResults = this.nodeSearch.listSearched; From de076d62081d9944045960d2339528661eeabeaa Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Wed, 17 Jun 2020 08:36:56 -0500 Subject: [PATCH 11/24] Change to use set for search properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the expanded try, I found that the reason for search only working once was that it was required to use set for these properties, I guess because they’re used by computed properties. --- ui/app/components/global-search/control.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/app/components/global-search/control.js b/ui/app/components/global-search/control.js index 5405ee42e0d..b1eba783c9b 100644 --- a/ui/app/components/global-search/control.js +++ b/ui/app/components/global-search/control.js @@ -1,7 +1,7 @@ import Component from '@ember/component'; import { tagName } from '@ember-decorators/component'; import { task } from 'ember-concurrency'; -import EmberObject, { action, computed } from '@ember/object'; +import EmberObject, { action, computed, set } from '@ember/object'; import { alias } from '@ember/object/computed'; import { inject as service } from '@ember/service'; import { run } from '@ember/runloop'; @@ -29,14 +29,14 @@ export default class GlobalSearchControl extends Component { @task(function*(string) { try { - this.searchString = string; + set(this, 'searchString', string); // FIXME no need to fetch on every search! const jobs = yield this.store.findAll('job'); const nodes = yield this.store.findAll('node'); - this.jobs = jobs.toArray(); - this.nodes = nodes.toArray(); + set(this, 'jobs', jobs.toArray()); + set(this, 'nodes', nodes.toArray()); const jobResults = this.jobSearch.listSearched; const nodeResults = this.nodeSearch.listSearched; From db0ceaca8eda967a23723b3b7b626da335560e0e Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Wed, 17 Jun 2020 09:08:28 -0500 Subject: [PATCH 12/24] Change interface for search classes This is still unwieldly but maybe a bit more sensible now. --- ui/app/components/global-search/control.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/app/components/global-search/control.js b/ui/app/components/global-search/control.js index b1eba783c9b..8614f38ddcd 100644 --- a/ui/app/components/global-search/control.js +++ b/ui/app/components/global-search/control.js @@ -19,11 +19,11 @@ export default class GlobalSearchControl extends Component { super(...arguments); this.jobSearch = JobSearch.create({ - something: this, // FIXME what’s a good name? + dataSource: this, }); this.nodeSearch = NodeSearch.create({ - something: this, + dataSource: this, }); } @@ -123,8 +123,8 @@ class JobSearch extends EmberObject.extend(Searchable) { return ['name']; } - @alias('something.jobs') listToSearch; - @alias('something.searchString') searchTerm; + @alias('dataSource.jobs') listToSearch; + @alias('dataSource.searchString') searchTerm; fuzzySearchEnabled = true; } @@ -141,8 +141,8 @@ class NodeSearch extends EmberObject.extend(Searchable) { return ['name']; } - @alias('something.nodes') listToSearch; - @alias('something.searchString') searchTerm; + @alias('dataSource.nodes') listToSearch; + @alias('dataSource.searchString') searchTerm; fuzzySearchEnabled = true; } From a2131767e9faf1d4a998d69e2cfc3b7af0347ce4 Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Wed, 17 Jun 2020 11:57:13 -0500 Subject: [PATCH 13/24] Stop control from capturing / in input fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I’ve adapted the listener from @DingoEatingFuzz’s keyboard event- handling here, obviating the need for ember-keyboard: https://github.com/hashicorp/nomad/pull/8177/files#diff-eb4d61e1e080830b844012c6617c6fa2 The filtering to prevent stealing events from input fields is adapted from here: https://stackoverflow.com/a/4171758 --- ui/app/components/global-search/control.js | 23 ++ .../components/global-search/control.hbs | 1 - ui/config/environment.js | 4 - ui/package.json | 1 - ui/tests/acceptance/search-test.js | 32 +- ui/yarn.lock | 378 +++++------------- 6 files changed, 158 insertions(+), 281 deletions(-) diff --git a/ui/app/components/global-search/control.js b/ui/app/components/global-search/control.js index 8614f38ddcd..2d5f1d30746 100644 --- a/ui/app/components/global-search/control.js +++ b/ui/app/components/global-search/control.js @@ -8,6 +8,8 @@ import { run } from '@ember/runloop'; import Searchable from 'nomad-ui/mixins/searchable'; import classic from 'ember-classic-decorator'; +const SLASH_KEY = 191; + @tagName('') export default class GlobalSearchControl extends Component { @service router; @@ -27,6 +29,27 @@ export default class GlobalSearchControl extends Component { }); } + keyDownHandler(e) { + const targetElementName = e.target.nodeName.toLowerCase(); + + // FIXME are more and/or other-approach exceptions needed? + if (targetElementName != 'input' && targetElementName != 'textarea') { + if (e.keyCode === SLASH_KEY) { + e.preventDefault(); + this.open(); + } + } + } + + didInsertElement() { + this.set('_keyDownHandler', this.keyDownHandler.bind(this)); + document.addEventListener('keydown', this._keyDownHandler); + } + + willDestroyElement() { + document.removeEventListener('keydown', this._keyDownHandler); + } + @task(function*(string) { try { set(this, 'searchString', string); diff --git a/ui/app/templates/components/global-search/control.hbs b/ui/app/templates/components/global-search/control.hbs index 72395b8b299..d1f2c5c949f 100644 --- a/ui/app/templates/components/global-search/control.hbs +++ b/ui/app/templates/components/global-search/control.hbs @@ -14,4 +14,3 @@ as |option|> {{option.name}} </PowerSelect> - {{on-key '/' (action 'open')}} \ No newline at end of file diff --git a/ui/config/environment.js b/ui/config/environment.js index d464ba5a0b8..9d758e0a62c 100644 --- a/ui/config/environment.js +++ b/ui/config/environment.js @@ -30,10 +30,6 @@ module.exports = function(environment) { mirageWithTokens: true, mirageWithRegions: true, }, - - emberKeyboard: { - disableInputsInitializer: true, - }, }; if (environment === 'development') { diff --git a/ui/package.json b/ui/package.json index 80956783b42..b4158b09f68 100644 --- a/ui/package.json +++ b/ui/package.json @@ -82,7 +82,6 @@ "ember-fetch": "^6.5.0", "ember-inflector": "^3.0.0", "ember-inline-svg": "^0.3.0", - "ember-keyboard": "^6.0.0", "ember-load-initializers": "^2.0.0", "ember-maybe-import-regenerator": "^0.1.6", "ember-moment": "^7.8.1", diff --git a/ui/tests/acceptance/search-test.js b/ui/tests/acceptance/search-test.js index 9caf85ec81f..5f43ba1c0af 100644 --- a/ui/tests/acceptance/search-test.js +++ b/ui/tests/acceptance/search-test.js @@ -1,10 +1,9 @@ -import { module, skip, test } from 'qunit'; -import { currentURL, visit } from '@ember/test-helpers'; +import { module, test } from 'qunit'; +import { click, currentURL, triggerEvent, visit } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import { setupMirage } from 'ember-cli-mirage/test-support'; import PageLayout from 'nomad-ui/tests/pages/layout'; import { selectSearch } from 'ember-power-select/test-support'; -import { keyDown } from 'ember-keyboard/test-support/test-helpers'; module('Acceptance | search', function(hooks) { setupApplicationTest(hooks); @@ -58,12 +57,33 @@ module('Acceptance | search', function(hooks) { assert.ok(PageLayout.navbar.search.field.isPresent); }); - // This DOES work but keyDown doesn’t seem quite right… - skip('pressing slash starts a search', async function(assert) { + test('pressing slash starts a search', async function(assert) { await visit('/'); assert.notOk(PageLayout.navbar.search.field.isPresent); - await keyDown('/'); + + await triggerEvent('.page-layout', 'keydown', { + keyCode: 191, // slash + }); + assert.ok(PageLayout.navbar.search.field.isPresent); }); + + test('pressing slash when an input element is focused does not start a search', async function(assert) { + server.create('node'); + server.create('job'); + + await visit('/'); + + assert.notOk(PageLayout.navbar.search.field.isPresent); + + // FIXME use page objects for this and below? 🤔 + await click('.search-box input'); + + await triggerEvent('.search-box input', 'keydown', { + keyCode: 191, // slash + }); + + assert.notOk(PageLayout.navbar.search.field.isPresent); + }); }); diff --git a/ui/yarn.lock b/ui/yarn.lock index f16a9672b2e..f90cf1b363a 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -52,28 +52,6 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.10.2", "@babel/core@^7.8.3", "@babel/core@^7.9.0": - version "7.10.2" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.2.tgz#bd6786046668a925ac2bd2fd95b579b92a23b36a" - integrity sha512-KQmV9yguEjQsXqyOUGKjS4+3K8/DlOCE2pZcq4augdQmtTy5iv5EHtmMSJ7V4c1BIPjuwtZYqYLCq9Ga+hGBRQ== - dependencies: - "@babel/code-frame" "^7.10.1" - "@babel/generator" "^7.10.2" - "@babel/helper-module-transforms" "^7.10.1" - "@babel/helpers" "^7.10.1" - "@babel/parser" "^7.10.2" - "@babel/template" "^7.10.1" - "@babel/traverse" "^7.10.1" - "@babel/types" "^7.10.2" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.1" - json5 "^2.1.2" - lodash "^4.17.13" - resolve "^1.3.2" - semver "^5.4.1" - source-map "^0.5.0" - "@babel/core@^7.2.2": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.5.5.tgz#17b2686ef0d6bc58f963dddd68ab669755582c30" @@ -114,6 +92,28 @@ semver "^5.4.1" source-map "^0.5.0" +"@babel/core@^7.8.3", "@babel/core@^7.9.0": + version "7.10.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.2.tgz#bd6786046668a925ac2bd2fd95b579b92a23b36a" + integrity sha512-KQmV9yguEjQsXqyOUGKjS4+3K8/DlOCE2pZcq4augdQmtTy5iv5EHtmMSJ7V4c1BIPjuwtZYqYLCq9Ga+hGBRQ== + dependencies: + "@babel/code-frame" "^7.10.1" + "@babel/generator" "^7.10.2" + "@babel/helper-module-transforms" "^7.10.1" + "@babel/helpers" "^7.10.1" + "@babel/parser" "^7.10.2" + "@babel/template" "^7.10.1" + "@babel/traverse" "^7.10.1" + "@babel/types" "^7.10.2" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.13" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + "@babel/generator@^7.10.1", "@babel/generator@^7.10.2": version "7.10.2" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.2.tgz#0fa5b5b2389db8bfdfcc3492b551ee20f5dd69a9" @@ -663,15 +663,6 @@ "@babel/helper-create-class-features-plugin" "^7.4.0" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-proposal-decorators@^7.10.1", "@babel/plugin-proposal-decorators@^7.8.3": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.10.1.tgz#9373c2d8db45345c6e30452ad77b469758e5c8f7" - integrity sha512-xBfteh352MTke2U1NpclzMDmAmCdQ2fBZjhZQQfGTjXw6qcRYMkt528sA1U8o0ThDCSeuETXIj5bOGdxN+5gkw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-syntax-decorators" "^7.10.1" - "@babel/plugin-proposal-decorators@^7.3.0": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.4.4.tgz#de9b2a1a8ab0196f378e2a82f10b6e2a36f21cc0" @@ -681,6 +672,15 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-decorators" "^7.2.0" +"@babel/plugin-proposal-decorators@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.10.1.tgz#9373c2d8db45345c6e30452ad77b469758e5c8f7" + integrity sha512-xBfteh352MTke2U1NpclzMDmAmCdQ2fBZjhZQQfGTjXw6qcRYMkt528sA1U8o0ThDCSeuETXIj5bOGdxN+5gkw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/plugin-syntax-decorators" "^7.10.1" + "@babel/plugin-proposal-dynamic-import@^7.10.1": version "7.10.1" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.1.tgz#e36979dc1dc3b73f6d6816fc4951da2363488ef0" @@ -1424,16 +1424,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-runtime@^7.10.1", "@babel/plugin-transform-runtime@^7.9.0": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.10.1.tgz#fd1887f749637fb2ed86dc278e79eb41df37f4b1" - integrity sha512-4w2tcglDVEwXJ5qxsY++DgWQdNJcCCsPxfT34wCUwIf2E7dI7pMpH8JczkMBbgBTNzBX62SZlNJ9H+De6Zebaw== - dependencies: - "@babel/helper-module-imports" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - resolve "^1.8.1" - semver "^5.5.1" - "@babel/plugin-transform-runtime@^7.2.0": version "7.5.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.5.0.tgz#45242c2c9281158c5f06d25beebac63e498a284e" @@ -1444,6 +1434,16 @@ resolve "^1.8.1" semver "^5.5.1" +"@babel/plugin-transform-runtime@^7.9.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.10.1.tgz#fd1887f749637fb2ed86dc278e79eb41df37f4b1" + integrity sha512-4w2tcglDVEwXJ5qxsY++DgWQdNJcCCsPxfT34wCUwIf2E7dI7pMpH8JczkMBbgBTNzBX62SZlNJ9H+De6Zebaw== + dependencies: + "@babel/helper-module-imports" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + resolve "^1.8.1" + semver "^5.5.1" + "@babel/plugin-transform-shorthand-properties@^7.10.1": version "7.10.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.1.tgz#e8b54f238a1ccbae482c4dce946180ae7b3143f3" @@ -1525,7 +1525,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-typescript@^7.10.1", "@babel/plugin-transform-typescript@^7.9.0": +"@babel/plugin-transform-typescript@^7.9.0": version "7.10.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.10.1.tgz#2c54daea231f602468686d9faa76f182a94507a6" integrity sha512-v+QWKlmCnsaimLeqq9vyCsVRMViZG1k2SZTlcZvB+TqyH570Zsij8nvVUZzOASCRiQFUxkLrn9Wg/kH0zgy5OQ== @@ -1583,7 +1583,7 @@ core-js "^2.6.5" regenerator-runtime "^0.13.2" -"@babel/polyfill@^7.10.1", "@babel/polyfill@^7.8.3", "@babel/polyfill@^7.8.7": +"@babel/polyfill@^7.8.3", "@babel/polyfill@^7.8.7": version "7.10.1" resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.10.1.tgz#d56d4c8be8dd6ec4dce2649474e9b707089f739f" integrity sha512-TviueJ4PBW5p48ra8IMtLXVkDucrlOZAIZ+EXqS3Ot4eukHbWiqcn7DcqpA1k5PcKtmJ4Xl9xwdv6yQvvcA+3g== @@ -1647,7 +1647,63 @@ js-levenshtein "^1.1.3" semver "^5.5.0" -"@babel/preset-env@^7.10.2", "@babel/preset-env@^7.9.0": +"@babel/preset-env@^7.4.5": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.6.2.tgz#abbb3ed785c7fe4220d4c82a53621d71fc0c75d3" + integrity sha512-Ru7+mfzy9M1/YTEtlDS8CD45jd22ngb9tXnn64DvQK3ooyqSw9K4K9DUWmYknTTVk4TqygL9dqCrZgm1HMea/Q== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-async-generator-functions" "^7.2.0" + "@babel/plugin-proposal-dynamic-import" "^7.5.0" + "@babel/plugin-proposal-json-strings" "^7.2.0" + "@babel/plugin-proposal-object-rest-spread" "^7.6.2" + "@babel/plugin-proposal-optional-catch-binding" "^7.2.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.6.2" + "@babel/plugin-syntax-async-generators" "^7.2.0" + "@babel/plugin-syntax-dynamic-import" "^7.2.0" + "@babel/plugin-syntax-json-strings" "^7.2.0" + "@babel/plugin-syntax-object-rest-spread" "^7.2.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" + "@babel/plugin-transform-arrow-functions" "^7.2.0" + "@babel/plugin-transform-async-to-generator" "^7.5.0" + "@babel/plugin-transform-block-scoped-functions" "^7.2.0" + "@babel/plugin-transform-block-scoping" "^7.6.2" + "@babel/plugin-transform-classes" "^7.5.5" + "@babel/plugin-transform-computed-properties" "^7.2.0" + "@babel/plugin-transform-destructuring" "^7.6.0" + "@babel/plugin-transform-dotall-regex" "^7.6.2" + "@babel/plugin-transform-duplicate-keys" "^7.5.0" + "@babel/plugin-transform-exponentiation-operator" "^7.2.0" + "@babel/plugin-transform-for-of" "^7.4.4" + "@babel/plugin-transform-function-name" "^7.4.4" + "@babel/plugin-transform-literals" "^7.2.0" + "@babel/plugin-transform-member-expression-literals" "^7.2.0" + "@babel/plugin-transform-modules-amd" "^7.5.0" + "@babel/plugin-transform-modules-commonjs" "^7.6.0" + "@babel/plugin-transform-modules-systemjs" "^7.5.0" + "@babel/plugin-transform-modules-umd" "^7.2.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.6.2" + "@babel/plugin-transform-new-target" "^7.4.4" + "@babel/plugin-transform-object-super" "^7.5.5" + "@babel/plugin-transform-parameters" "^7.4.4" + "@babel/plugin-transform-property-literals" "^7.2.0" + "@babel/plugin-transform-regenerator" "^7.4.5" + "@babel/plugin-transform-reserved-words" "^7.2.0" + "@babel/plugin-transform-shorthand-properties" "^7.2.0" + "@babel/plugin-transform-spread" "^7.6.2" + "@babel/plugin-transform-sticky-regex" "^7.2.0" + "@babel/plugin-transform-template-literals" "^7.4.4" + "@babel/plugin-transform-typeof-symbol" "^7.2.0" + "@babel/plugin-transform-unicode-regex" "^7.6.2" + "@babel/types" "^7.6.0" + browserslist "^4.6.0" + core-js-compat "^3.1.1" + invariant "^2.2.2" + js-levenshtein "^1.1.3" + semver "^5.5.0" + +"@babel/preset-env@^7.9.0": version "7.10.2" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.10.2.tgz#715930f2cf8573b0928005ee562bed52fb65fdfb" integrity sha512-MjqhX0RZaEgK/KueRzh+3yPSk30oqDKJ5HP5tqTSB1e2gzGS3PLy7K0BIpnp78+0anFuSwOeuCf1zZO7RzRvEA== @@ -1717,62 +1773,6 @@ levenary "^1.1.1" semver "^5.5.0" -"@babel/preset-env@^7.4.5": - version "7.6.2" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.6.2.tgz#abbb3ed785c7fe4220d4c82a53621d71fc0c75d3" - integrity sha512-Ru7+mfzy9M1/YTEtlDS8CD45jd22ngb9tXnn64DvQK3ooyqSw9K4K9DUWmYknTTVk4TqygL9dqCrZgm1HMea/Q== - dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-proposal-async-generator-functions" "^7.2.0" - "@babel/plugin-proposal-dynamic-import" "^7.5.0" - "@babel/plugin-proposal-json-strings" "^7.2.0" - "@babel/plugin-proposal-object-rest-spread" "^7.6.2" - "@babel/plugin-proposal-optional-catch-binding" "^7.2.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.6.2" - "@babel/plugin-syntax-async-generators" "^7.2.0" - "@babel/plugin-syntax-dynamic-import" "^7.2.0" - "@babel/plugin-syntax-json-strings" "^7.2.0" - "@babel/plugin-syntax-object-rest-spread" "^7.2.0" - "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" - "@babel/plugin-transform-arrow-functions" "^7.2.0" - "@babel/plugin-transform-async-to-generator" "^7.5.0" - "@babel/plugin-transform-block-scoped-functions" "^7.2.0" - "@babel/plugin-transform-block-scoping" "^7.6.2" - "@babel/plugin-transform-classes" "^7.5.5" - "@babel/plugin-transform-computed-properties" "^7.2.0" - "@babel/plugin-transform-destructuring" "^7.6.0" - "@babel/plugin-transform-dotall-regex" "^7.6.2" - "@babel/plugin-transform-duplicate-keys" "^7.5.0" - "@babel/plugin-transform-exponentiation-operator" "^7.2.0" - "@babel/plugin-transform-for-of" "^7.4.4" - "@babel/plugin-transform-function-name" "^7.4.4" - "@babel/plugin-transform-literals" "^7.2.0" - "@babel/plugin-transform-member-expression-literals" "^7.2.0" - "@babel/plugin-transform-modules-amd" "^7.5.0" - "@babel/plugin-transform-modules-commonjs" "^7.6.0" - "@babel/plugin-transform-modules-systemjs" "^7.5.0" - "@babel/plugin-transform-modules-umd" "^7.2.0" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.6.2" - "@babel/plugin-transform-new-target" "^7.4.4" - "@babel/plugin-transform-object-super" "^7.5.5" - "@babel/plugin-transform-parameters" "^7.4.4" - "@babel/plugin-transform-property-literals" "^7.2.0" - "@babel/plugin-transform-regenerator" "^7.4.5" - "@babel/plugin-transform-reserved-words" "^7.2.0" - "@babel/plugin-transform-shorthand-properties" "^7.2.0" - "@babel/plugin-transform-spread" "^7.6.2" - "@babel/plugin-transform-sticky-regex" "^7.2.0" - "@babel/plugin-transform-template-literals" "^7.4.4" - "@babel/plugin-transform-typeof-symbol" "^7.2.0" - "@babel/plugin-transform-unicode-regex" "^7.6.2" - "@babel/types" "^7.6.0" - browserslist "^4.6.0" - core-js-compat "^3.1.1" - invariant "^2.2.2" - js-levenshtein "^1.1.3" - semver "^5.5.0" - "@babel/preset-modules@^0.1.3": version "0.1.3" resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.3.tgz#13242b53b5ef8c883c3cf7dddd55b36ce80fbc72" @@ -1798,13 +1798,6 @@ dependencies: regenerator-runtime "^0.13.2" -"@babel/runtime@^7.10.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.0": - version "7.10.2" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.2.tgz#d103f21f2602497d38348a32e008637d506db839" - integrity sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg== - dependencies: - regenerator-runtime "^0.13.4" - "@babel/runtime@^7.2.0": version "7.5.4" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.4.tgz#cb7d1ad7c6d65676e66b47186577930465b5271b" @@ -1819,6 +1812,13 @@ dependencies: regenerator-runtime "^0.13.2" +"@babel/runtime@^7.8.4", "@babel/runtime@^7.9.0": + version "7.10.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.2.tgz#d103f21f2602497d38348a32e008637d506db839" + integrity sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/runtime@^7.8.7": version "7.9.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" @@ -2076,11 +2076,6 @@ dependencies: ember-cli-babel "^7.1.3" -"@ember/edition-utils@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@ember/edition-utils/-/edition-utils-1.2.0.tgz#a039f542dc14c8e8299c81cd5abba95e2459cfa6" - integrity sha512-VmVq/8saCaPdesQmftPqbFtxJWrzxNGSQ+e8x8LLe3Hjm36pJ04Q8LeORGZkAeOhldoUX9seLGmSaHeXkIqoog== - "@ember/optional-features@^0.7.0": version "0.7.0" resolved "https://registry.yarnpkg.com/@ember/optional-features/-/optional-features-0.7.0.tgz#f65a858007020ddfb8342f586112750c32abd2d9" @@ -4055,7 +4050,7 @@ babel-plugin-debug-macros@^0.3.0: dependencies: semver "^5.3.0" -babel-plugin-debug-macros@^0.3.2, babel-plugin-debug-macros@^0.3.3: +babel-plugin-debug-macros@^0.3.2: version "0.3.3" resolved "https://registry.yarnpkg.com/babel-plugin-debug-macros/-/babel-plugin-debug-macros-0.3.3.tgz#29c3449d663f61c7385f5b8c72d8015b069a5cb7" integrity sha512-E+NI8TKpxJDBbVkdWkwHrKgJi696mnRL8XYrOPYw82veNHPDORM9WIQifl6TpIo8PNy2tU2skPqbfkmHXrHKQA== @@ -4138,11 +4133,6 @@ babel-plugin-htmlbars-inline-precompile@^1.0.0: resolved "https://registry.yarnpkg.com/babel-plugin-htmlbars-inline-precompile/-/babel-plugin-htmlbars-inline-precompile-1.0.0.tgz#a9d2f6eaad8a3f3d361602de593a8cbef8179c22" integrity sha512-4jvKEHR1bAX03hBDZ94IXsYCj3bwk9vYsn6ux6JZNL2U5pvzCWjqyrGahfsGNrhERyxw8IqcirOi9Q6WCo3dkQ== -babel-plugin-htmlbars-inline-precompile@^3.0.1: - version "3.1.0" - resolved "https://registry.yarnpkg.com/babel-plugin-htmlbars-inline-precompile/-/babel-plugin-htmlbars-inline-precompile-3.1.0.tgz#85085b50385277f2b331ebd54e22fa91aadc24e8" - integrity sha512-ar6c4YVX6OV7Dzpq7xRyllQrHwVEzJf41qysYULnD6tu6TS+y1FxT2VcEvMC6b9Rq9xoHMzvB79HO3av89JCGg== - babel-plugin-macros@^2.0.0, babel-plugin-macros@^2.4.5: version "2.6.1" resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.6.1.tgz#41f7ead616fc36f6a93180e89697f69f51671181" @@ -5012,23 +5002,6 @@ broccoli-babel-transpiler@^7.4.0: rsvp "^4.8.4" workerpool "^3.1.1" -broccoli-babel-transpiler@^7.5.0: - version "7.5.0" - resolved "https://registry.yarnpkg.com/broccoli-babel-transpiler/-/broccoli-babel-transpiler-7.5.0.tgz#6137e2c9324eebe1d83b6a01c7b445bfc5612d49" - integrity sha512-CrLrI8HX7mDqVR+/r8WiTZuQQh6hGMGwal9jyKk+kpk7t/4hqL6oQ06FRt81kazbHm4bil4WJ+kGB+aOfAx+XA== - dependencies: - "@babel/core" "^7.10.2" - "@babel/polyfill" "^7.10.1" - broccoli-funnel "^2.0.2" - broccoli-merge-trees "^3.0.2" - broccoli-persistent-filter "^2.2.1" - clone "^2.1.2" - hash-for-dep "^1.4.7" - heimdalljs-logger "^0.1.9" - json-stable-stringify "^1.0.1" - rsvp "^4.8.4" - workerpool "^3.1.1" - broccoli-builder@^0.18.14: version "0.18.14" resolved "https://registry.yarnpkg.com/broccoli-builder/-/broccoli-builder-0.18.14.tgz#4b79e2f844de11a4e1b816c3f49c6df4776c312d" @@ -5329,11 +5302,6 @@ broccoli-module-unification-reexporter@^1.0.0: mkdirp "^0.5.1" walk-sync "^0.3.2" -broccoli-node-api@^1.6.0, broccoli-node-api@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/broccoli-node-api/-/broccoli-node-api-1.7.0.tgz#391aa6edecd2a42c63c111b4162956b2fa288cb6" - integrity sha512-QIqLSVJWJUVOhclmkmypJJH9u9s/aWH4+FH6Q6Ju5l+Io4dtwqdPUNmDfw40o6sxhbZHhqGujDJuHTML1wG8Yw== - broccoli-node-info@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/broccoli-node-info/-/broccoli-node-info-1.1.0.tgz#3aa2e31e07e5bdb516dd25214f7c45ba1c459412" @@ -5344,13 +5312,6 @@ broccoli-node-info@^2.1.0: resolved "https://registry.yarnpkg.com/broccoli-node-info/-/broccoli-node-info-2.1.0.tgz#ca84560e8570ff78565bea1699866ddbf58ad644" integrity sha512-l6qDuboJThHfRVVWQVaTs++bFdrFTP0gJXgsWenczc1PavRVUmL1Eyb2swTAXXMpDOnr2zhNOBLx4w9AxkqbPQ== -broccoli-output-wrapper@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/broccoli-output-wrapper/-/broccoli-output-wrapper-2.0.0.tgz#f1e0b9b2f259a67fd41a380141c3c20b096828e6" - integrity sha512-V/ozejo+snzNf75i/a6iTmp71k+rlvqjE3+jYfimuMwR1tjNNRdtfno+NGNQB2An9bIAeqZnKhMDurAznHAdtA== - dependencies: - heimdalljs-logger "^0.1.10" - broccoli-persistent-filter@^1.1.5, broccoli-persistent-filter@^1.1.6, broccoli-persistent-filter@^1.4.3: version "1.4.6" resolved "https://registry.yarnpkg.com/broccoli-persistent-filter/-/broccoli-persistent-filter-1.4.6.tgz#80762d19000880a77da33c34373299c0f6a3e615" @@ -5439,19 +5400,6 @@ broccoli-plugin@^2.0.0: rimraf "^2.3.4" symlink-or-copy "^1.1.8" -broccoli-plugin@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/broccoli-plugin/-/broccoli-plugin-3.1.0.tgz#54ba6dd90a42ec3db5624063292610e326b1e542" - integrity sha512-7w7FP8WJYjLvb0eaw27LO678TGGaom++49O1VYIuzjhXjK5kn2+AMlDm7CaUFw4F7CLGoVQeZ84d8gICMJa4lA== - dependencies: - broccoli-node-api "^1.6.0" - broccoli-output-wrapper "^2.0.0" - fs-merger "^3.0.1" - promise-map-series "^0.2.1" - quick-temp "^0.1.3" - rimraf "^2.3.4" - symlink-or-copy "^1.1.8" - broccoli-rollup@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/broccoli-rollup/-/broccoli-rollup-2.1.1.tgz#0b77dc4b7560a53e998ea85f3b56772612d4988d" @@ -7668,38 +7616,6 @@ ember-cli-babel@^7.11.1: rimraf "^3.0.1" semver "^5.5.0" -ember-cli-babel@^7.19.0: - version "7.21.0" - resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.21.0.tgz#c79e888876aee87dfc3260aee7cb580b74264bbc" - integrity sha512-jHVi9melAibo0DrAG3GAxid+29xEyjBoU53652B4qcu3Xp58feZGTH/JGXovH7TjvbeNn65zgNyoV3bk1onULw== - dependencies: - "@babel/core" "^7.10.2" - "@babel/helper-compilation-targets" "^7.10.2" - "@babel/plugin-proposal-class-properties" "^7.10.1" - "@babel/plugin-proposal-decorators" "^7.10.1" - "@babel/plugin-transform-modules-amd" "^7.10.1" - "@babel/plugin-transform-runtime" "^7.10.1" - "@babel/plugin-transform-typescript" "^7.10.1" - "@babel/polyfill" "^7.10.1" - "@babel/preset-env" "^7.10.2" - "@babel/runtime" "^7.10.2" - amd-name-resolver "^1.2.1" - babel-plugin-debug-macros "^0.3.3" - babel-plugin-ember-data-packages-polyfill "^0.1.2" - babel-plugin-ember-modules-api-polyfill "^2.13.4" - babel-plugin-module-resolver "^3.1.1" - broccoli-babel-transpiler "^7.5.0" - broccoli-debug "^0.6.4" - broccoli-funnel "^2.0.1" - broccoli-source "^1.1.0" - clone "^2.1.2" - ember-cli-babel-plugin-helpers "^1.1.0" - ember-cli-version-checker "^4.1.0" - ensure-posix-path "^1.0.2" - fixturify-project "^1.10.0" - rimraf "^3.0.1" - semver "^5.5.0" - ember-cli-broccoli-sane-watcher@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ember-cli-broccoli-sane-watcher/-/ember-cli-broccoli-sane-watcher-3.0.0.tgz#dc1812c047e1ceec4413d3c41b51a9ffc61b4cfe" @@ -7804,26 +7720,6 @@ ember-cli-htmlbars@^3.0.1, ember-cli-htmlbars@^3.1.0: json-stable-stringify "^1.0.1" strip-bom "^3.0.0" -ember-cli-htmlbars@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-4.3.1.tgz#4af8adc21ab3c4953f768956b7f7d207782cb175" - integrity sha512-CW6AY/yzjeVqoRtItOKj3hcYzc5dWPRETmeCzr2Iqjt5vxiVtpl0z5VTqHqIlT5fsFx6sGWBQXNHIe+ivYsxXQ== - dependencies: - "@ember/edition-utils" "^1.2.0" - babel-plugin-htmlbars-inline-precompile "^3.0.1" - broccoli-debug "^0.6.5" - broccoli-persistent-filter "^2.3.1" - broccoli-plugin "^3.1.0" - common-tags "^1.8.0" - ember-cli-babel-plugin-helpers "^1.1.0" - fs-tree-diff "^2.0.1" - hash-for-dep "^1.5.1" - heimdalljs-logger "^0.1.10" - json-stable-stringify "^1.0.1" - semver "^6.3.0" - strip-bom "^4.0.0" - walk-sync "^2.0.2" - ember-cli-import-polyfill@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/ember-cli-import-polyfill/-/ember-cli-import-polyfill-0.2.0.tgz#c1a08a8affb45c97b675926272fe78cf4ca166f2" @@ -8147,15 +8043,6 @@ ember-compatibility-helpers@^1.1.1, ember-compatibility-helpers@^1.2.0: ember-cli-version-checker "^2.1.1" semver "^5.4.1" -ember-compatibility-helpers@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ember-compatibility-helpers/-/ember-compatibility-helpers-1.2.1.tgz#87c92c4303f990ff455c28ca39fb3ee11441aa16" - integrity sha512-6wzYvnhg1ihQUT5yGqnLtleq3Nv5KNv79WhrEuNU9SwR4uIxCO+KpyC7r3d5VI0EM7/Nmv9Nd0yTkzmTMdVG1A== - dependencies: - babel-plugin-debug-macros "^0.2.0" - ember-cli-version-checker "^2.1.1" - semver "^5.4.1" - ember-composable-helpers@^2.0.3: version "2.3.1" resolved "https://registry.yarnpkg.com/ember-composable-helpers/-/ember-composable-helpers-2.3.1.tgz#db98ad8b55d053e2ac216b9da091c9e7a3b9f453" @@ -8311,16 +8198,6 @@ ember-inline-svg@^0.3.0: svgo "~1.2.2" walk-sync "^0.3.1" -ember-keyboard@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/ember-keyboard/-/ember-keyboard-6.0.0.tgz#0bf929bfa1a5935109e59c1b816e65ad2db0383b" - integrity sha512-QJAhs8173hqy8e/Wj/98+5tJ43I5UKq35lsx3w8icHL5iisfXo8s89gOtn1C2015Y4JoMNsVOkNSCnBfhx1UQg== - dependencies: - ember-cli-babel "^7.19.0" - ember-cli-htmlbars "^4.3.1" - ember-compatibility-helpers "^1.2.1" - ember-modifier "^1.0.3" - ember-load-initializers@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ember-load-initializers/-/ember-load-initializers-2.1.0.tgz#b402815ab9c823ff48a1369b52633721987e72d4" @@ -8356,7 +8233,7 @@ ember-maybe-in-element@^0.4.0: dependencies: ember-cli-babel "^7.1.0" -ember-modifier-manager-polyfill@^1.1.0, ember-modifier-manager-polyfill@^1.2.0: +ember-modifier-manager-polyfill@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/ember-modifier-manager-polyfill/-/ember-modifier-manager-polyfill-1.2.0.tgz#cf4444e11a42ac84f5c8badd85e635df57565dda" integrity sha512-bnaKF1LLKMkBNeDoetvIJ4vhwRPKIIumWr6dbVuW6W6p4QV8ZiO+GdF8J7mxDNlog9CeL9Z/7wam4YS86G8BYA== @@ -8365,17 +8242,6 @@ ember-modifier-manager-polyfill@^1.1.0, ember-modifier-manager-polyfill@^1.2.0: ember-cli-version-checker "^2.1.2" ember-compatibility-helpers "^1.2.0" -ember-modifier@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/ember-modifier/-/ember-modifier-1.0.3.tgz#ab18250666aad17c0d9170feb178e954148eb4ed" - integrity sha512-vWuFyvdkULUyasvEXxe5lcfuPZV/Uqe+b0IQ1yU+TY1RSJnFdVUu/CVHT8Bu4HUJInqzAihwPMTwty7fypzi5Q== - dependencies: - ember-cli-babel "^7.11.1" - ember-cli-is-package-missing "^1.0.0" - ember-cli-normalize-entity-name "^1.0.0" - ember-cli-string-utils "^1.1.0" - ember-modifier-manager-polyfill "^1.2.0" - ember-moment@^7.8.1: version "7.8.1" resolved "https://registry.yarnpkg.com/ember-moment/-/ember-moment-7.8.1.tgz#6f77cf941d1a92e231b2f4b810e113b2fae50c5f" @@ -9681,18 +9547,6 @@ fs-extra@^8.0.1: jsonfile "^4.0.0" universalify "^0.1.0" -fs-merger@^3.0.1: - version "3.1.0" - resolved "https://registry.yarnpkg.com/fs-merger/-/fs-merger-3.1.0.tgz#f30f74f6c70b2ff7333ec074f3d2f22298152f3b" - integrity sha512-RZ9JtqugaE8Rkt7idO5NSwcxEGSDZpLmVFjtVQUm3f+bWun7JAU6fKyU6ZJUeUnKdJwGx8uaro+K4QQfOR7vpA== - dependencies: - broccoli-node-api "^1.7.0" - broccoli-node-info "^2.1.0" - fs-extra "^8.0.1" - fs-tree-diff "^2.0.1" - rimraf "^2.6.3" - walk-sync "^2.0.2" - fs-minipass@^1.2.5: version "1.2.6" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07" @@ -15630,11 +15484,6 @@ strip-bom@^3.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= -strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" @@ -16567,15 +16416,6 @@ walk-sync@^1.0.0, walk-sync@^1.1.3: ensure-posix-path "^1.1.0" matcher-collection "^1.1.1" -walk-sync@^2.0.2: - version "2.1.0" - resolved "https://registry.yarnpkg.com/walk-sync/-/walk-sync-2.1.0.tgz#e214248e5f5cf497ddd87db5a2a02e47e29c6501" - integrity sha512-KpH9Xw64LNSx7/UI+3guRZvJWlDxVA4+KKb/4puRoVrG8GkvZRxnF3vhxdjgpoKJGL2TVg1OrtkXIE/VuGPLHQ== - dependencies: - "@types/minimatch" "^3.0.3" - ensure-posix-path "^1.1.0" - matcher-collection "^2.0.0" - walker@~1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" From 2fa2ef8311c4b7dfa57b7fcba32057546200e1d7 Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Wed, 17 Jun 2020 12:43:22 -0500 Subject: [PATCH 14/24] Add preliminary use of cached collections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This still means a query will be sent for every search, but if we’re in a route we know has a watched collection, the cached collection will be used. --- ui/app/components/global-search/control.js | 20 ++++++++++-- ui/tests/acceptance/search-test.js | 36 +++++++++++++++++++++- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/ui/app/components/global-search/control.js b/ui/app/components/global-search/control.js index 2d5f1d30746..fcb9bd56984 100644 --- a/ui/app/components/global-search/control.js +++ b/ui/app/components/global-search/control.js @@ -55,8 +55,8 @@ export default class GlobalSearchControl extends Component { set(this, 'searchString', string); // FIXME no need to fetch on every search! - const jobs = yield this.store.findAll('job'); - const nodes = yield this.store.findAll('node'); + const jobs = yield this.fetchJobs(); + const nodes = yield this.fetchNodes(); set(this, 'jobs', jobs.toArray()); set(this, 'nodes', nodes.toArray()); @@ -132,6 +132,22 @@ export default class GlobalSearchControl extends Component { }, }; } + + async fetchJobs() { + if (this.router.isActive('jobs')) { + return this.store.peekAll('job'); + } else { + return this.store.findAll('job'); + } + } + + async fetchNodes() { + if (this.router.isActive('clients')) { + return this.store.peekAll('node'); + } else { + return this.store.findAll('node'); + } + } } @classic diff --git a/ui/tests/acceptance/search-test.js b/ui/tests/acceptance/search-test.js index 5f43ba1c0af..4ce0e755284 100644 --- a/ui/tests/acceptance/search-test.js +++ b/ui/tests/acceptance/search-test.js @@ -5,11 +5,15 @@ import { setupMirage } from 'ember-cli-mirage/test-support'; import PageLayout from 'nomad-ui/tests/pages/layout'; import { selectSearch } from 'ember-power-select/test-support'; +function getRequestCount(server, url) { + return server.pretender.handledRequests.filterBy('url', url).length; +} + module('Acceptance | search', function(hooks) { setupApplicationTest(hooks); setupMirage(hooks); - test('search searches jobs and nodes and navigates to chosen items', async function(assert) { + test('search searches jobs and nodes with route-based caching and navigates to chosen items', async function(assert) { server.create('node', { name: 'xyz' }); const otherNode = server.create('node', { name: 'aaa' }); @@ -19,6 +23,9 @@ module('Acceptance | search', function(hooks) { await visit('/'); + let presearchJobsRequestCount = getRequestCount(server, '/v1/jobs'); + let presearchNodesRequestCount = getRequestCount(server, '/v1/nodes'); + await selectSearch(PageLayout.navbar.search.scope, 'xy'); PageLayout.navbar.search.as(search => { @@ -38,6 +45,17 @@ module('Acceptance | search', function(hooks) { }); }); + assert.equal( + presearchJobsRequestCount, + getRequestCount(server, '/v1/jobs'), + 'no new jobs request should be sent when in the jobs hierarchy' + ); + assert.equal( + presearchNodesRequestCount + 1, + getRequestCount(server, '/v1/nodes'), + 'a nodes request should happen when not in the clients hierarchy' + ); + await PageLayout.navbar.search.groups[0].options[0].click(); assert.equal(currentURL(), '/jobs/xyz'); @@ -45,6 +63,22 @@ module('Acceptance | search', function(hooks) { await PageLayout.navbar.search.groups[1].options[0].click(); assert.equal(currentURL(), `/clients/${otherNode.id}`); + + presearchJobsRequestCount = getRequestCount(server, '/v1/jobs'); + presearchNodesRequestCount = getRequestCount(server, '/v1/nodes'); + + await selectSearch(PageLayout.navbar.search.scope, otherNode.id.substr(0, 3)); + + assert.equal( + presearchJobsRequestCount + 1, + getRequestCount(server, '/v1/jobs'), + 'a jobs request should happen when not not in the jobs hierarchy' + ); + assert.equal( + presearchNodesRequestCount, + getRequestCount(server, '/v1/nodes'), + 'no new nodes request should happen when in the clients hierarchy' + ); }); test('clicking the search field starts search immediately', async function(assert) { From def4851845a5d6467c1608d47fb90a8f3f7732eb Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Thu, 18 Jun 2020 08:47:29 -0500 Subject: [PATCH 15/24] Remove manual vertical positioning of divider Since the container now has align-items: center, this is no longer needed. --- ui/app/styles/core/navbar.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/app/styles/core/navbar.scss b/ui/app/styles/core/navbar.scss index 1ef5197921a..9018db8afb8 100644 --- a/ui/app/styles/core/navbar.scss +++ b/ui/app/styles/core/navbar.scss @@ -37,7 +37,6 @@ display: block; position: absolute; left: 0px; - top: 1.25em; // FIXME positioning is off with .is-primary align-items: center } } } From a9f97cb868d9d2a006083e25f2961b1015075b52 Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Thu, 18 Jun 2020 10:42:48 -0500 Subject: [PATCH 16/24] Add time-based caching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This isn’t perfect as it only updates the last-fetched time of a watched collection when a search is performed. It also has the cache duration as 5s which is probably too short. --- ui/app/components/global-search/control.js | 22 ++------------ ui/app/services/data-caches.js | 34 ++++++++++++++++++++++ ui/tests/acceptance/search-test.js | 32 ++++++++++++++++---- 3 files changed, 63 insertions(+), 25 deletions(-) create mode 100644 ui/app/services/data-caches.js diff --git a/ui/app/components/global-search/control.js b/ui/app/components/global-search/control.js index fcb9bd56984..6ef42e2da3a 100644 --- a/ui/app/components/global-search/control.js +++ b/ui/app/components/global-search/control.js @@ -12,6 +12,7 @@ const SLASH_KEY = 191; @tagName('') export default class GlobalSearchControl extends Component { + @service dataCaches; @service router; @service store; @@ -54,9 +55,8 @@ export default class GlobalSearchControl extends Component { try { set(this, 'searchString', string); - // FIXME no need to fetch on every search! - const jobs = yield this.fetchJobs(); - const nodes = yield this.fetchNodes(); + const jobs = yield this.dataCaches.fetch('job'); + const nodes = yield this.dataCaches.fetch('node'); set(this, 'jobs', jobs.toArray()); set(this, 'nodes', nodes.toArray()); @@ -132,22 +132,6 @@ export default class GlobalSearchControl extends Component { }, }; } - - async fetchJobs() { - if (this.router.isActive('jobs')) { - return this.store.peekAll('job'); - } else { - return this.store.findAll('job'); - } - } - - async fetchNodes() { - if (this.router.isActive('clients')) { - return this.store.peekAll('node'); - } else { - return this.store.findAll('node'); - } - } } @classic diff --git a/ui/app/services/data-caches.js b/ui/app/services/data-caches.js new file mode 100644 index 00000000000..98c100088ca --- /dev/null +++ b/ui/app/services/data-caches.js @@ -0,0 +1,34 @@ +import Service, { inject as service } from '@ember/service'; + +export const COLLECTION_CACHE_DURATION = 5000; + +export default class DataCachesService extends Service { + @service router; + @service store; + @service system; + + collectionLastFetched = {}; + + async fetch(modelName) { + // Could this be dynamically generated based on use of watchers? 🤔 + const modelNameToRoute = { + job: 'jobs', + node: 'clients', + }; + + const route = modelNameToRoute[modelName]; + const lastFetched = this.collectionLastFetched[modelName]; + const now = Date.now(); + + if (this.router.isActive(route)) { + // FIXME Incorrect because it’s constantly being fetched by watchers, shouldn’t be marked as last fetched only on search + this.collectionLastFetched[modelName] = now; + return this.store.peekAll(modelName); + } else if (lastFetched && now - lastFetched < COLLECTION_CACHE_DURATION) { + return this.store.peekAll(modelName); + } else { + this.collectionLastFetched[modelName] = now; + return this.store.findAll(modelName); + } + } +} diff --git a/ui/tests/acceptance/search-test.js b/ui/tests/acceptance/search-test.js index 4ce0e755284..304778f1f4e 100644 --- a/ui/tests/acceptance/search-test.js +++ b/ui/tests/acceptance/search-test.js @@ -4,6 +4,9 @@ import { setupApplicationTest } from 'ember-qunit'; import { setupMirage } from 'ember-cli-mirage/test-support'; import PageLayout from 'nomad-ui/tests/pages/layout'; import { selectSearch } from 'ember-power-select/test-support'; +import sinon from 'sinon'; + +import { COLLECTION_CACHE_DURATION } from 'nomad-ui/services/data-caches'; function getRequestCount(server, url) { return server.pretender.handledRequests.filterBy('url', url).length; @@ -13,7 +16,7 @@ module('Acceptance | search', function(hooks) { setupApplicationTest(hooks); setupMirage(hooks); - test('search searches jobs and nodes with route-based caching and navigates to chosen items', async function(assert) { + test('search searches jobs and nodes with route- and time-based caching and navigates to chosen items', async function(assert) { server.create('node', { name: 'xyz' }); const otherNode = server.create('node', { name: 'aaa' }); @@ -23,6 +26,11 @@ module('Acceptance | search', function(hooks) { await visit('/'); + const clock = sinon.useFakeTimers({ + now: new Date(), + shouldAdvanceTime: true, + }); + let presearchJobsRequestCount = getRequestCount(server, '/v1/jobs'); let presearchNodesRequestCount = getRequestCount(server, '/v1/nodes'); @@ -46,13 +54,13 @@ module('Acceptance | search', function(hooks) { }); assert.equal( - presearchJobsRequestCount, getRequestCount(server, '/v1/jobs'), + presearchJobsRequestCount, 'no new jobs request should be sent when in the jobs hierarchy' ); assert.equal( - presearchNodesRequestCount + 1, getRequestCount(server, '/v1/nodes'), + presearchNodesRequestCount + 1, 'a nodes request should happen when not in the clients hierarchy' ); @@ -67,18 +75,30 @@ module('Acceptance | search', function(hooks) { presearchJobsRequestCount = getRequestCount(server, '/v1/jobs'); presearchNodesRequestCount = getRequestCount(server, '/v1/nodes'); - await selectSearch(PageLayout.navbar.search.scope, otherNode.id.substr(0, 3)); + await selectSearch(PageLayout.navbar.search.scope, 'zzzzzzzzzzz'); assert.equal( - presearchJobsRequestCount + 1, getRequestCount(server, '/v1/jobs'), - 'a jobs request should happen when not not in the jobs hierarchy' + presearchJobsRequestCount, + 'a jobs request should not because the cache hasn’t expired' ); assert.equal( presearchNodesRequestCount, getRequestCount(server, '/v1/nodes'), 'no new nodes request should happen when in the clients hierarchy' ); + + clock.tick(COLLECTION_CACHE_DURATION * 2); + + await selectSearch(PageLayout.navbar.search.scope, otherNode.id.substr(0, 3)); + + assert.equal( + getRequestCount(server, '/v1/jobs'), + presearchJobsRequestCount + 1, + 'a jobs request should happen because the cache has expired' + ); + + clock.restore(); }); test('clicking the search field starts search immediately', async function(assert) { From 95904b7fdd13e0c8093ac65d638c8429034b2a2d Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Thu, 18 Jun 2020 10:59:02 -0500 Subject: [PATCH 17/24] Fix invocation of search component --- ui/app/templates/components/global-header.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/templates/components/global-header.hbs b/ui/app/templates/components/global-header.hbs index 4fe743ba46e..ef05d0a5a4d 100644 --- a/ui/app/templates/components/global-header.hbs +++ b/ui/app/templates/components/global-header.hbs @@ -7,7 +7,7 @@ {{partial "partials/nomad-logo"}} </LinkTo> </div> - {{global-search/control}} + <GlobalSearch::Control /> <div class="navbar-end"> <a href="https://nomadproject.io/docs" class="navbar-item">Documentation</a> <LinkTo @route="settings.tokens" class="navbar-item">ACL Tokens</LinkTo> From 44c4f1c82a06fe0cfc6172501e0f847cdf5a3f15 Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Thu, 18 Jun 2020 11:00:08 -0500 Subject: [PATCH 18/24] Add media query to hide search on mobile --- ui/app/templates/components/global-header.hbs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/app/templates/components/global-header.hbs b/ui/app/templates/components/global-header.hbs index ef05d0a5a4d..e129a42c321 100644 --- a/ui/app/templates/components/global-header.hbs +++ b/ui/app/templates/components/global-header.hbs @@ -7,7 +7,9 @@ {{partial "partials/nomad-logo"}} </LinkTo> </div> - <GlobalSearch::Control /> + {{#unless (media "isMobile")}} + <GlobalSearch::Control /> + {{/unless}} <div class="navbar-end"> <a href="https://nomadproject.io/docs" class="navbar-item">Documentation</a> <LinkTo @route="settings.tokens" class="navbar-item">ACL Tokens</LinkTo> From 93602c0c88065ab5527155c7587b61eb292e2382 Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Thu, 18 Jun 2020 12:32:36 -0500 Subject: [PATCH 19/24] Change cache duration --- ui/app/services/data-caches.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/services/data-caches.js b/ui/app/services/data-caches.js index 98c100088ca..fe7fe7b82a8 100644 --- a/ui/app/services/data-caches.js +++ b/ui/app/services/data-caches.js @@ -1,6 +1,6 @@ import Service, { inject as service } from '@ember/service'; -export const COLLECTION_CACHE_DURATION = 5000; +export const COLLECTION_CACHE_DURATION = 60000; // one minute export default class DataCachesService extends Service { @service router; From fd14f250de7fc9b03fcfe28359e04f0e26d7659a Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Thu, 18 Jun 2020 12:34:12 -0500 Subject: [PATCH 20/24] Fix assertion description --- ui/tests/acceptance/search-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/tests/acceptance/search-test.js b/ui/tests/acceptance/search-test.js index 304778f1f4e..c488476944c 100644 --- a/ui/tests/acceptance/search-test.js +++ b/ui/tests/acceptance/search-test.js @@ -80,7 +80,7 @@ module('Acceptance | search', function(hooks) { assert.equal( getRequestCount(server, '/v1/jobs'), presearchJobsRequestCount, - 'a jobs request should not because the cache hasn’t expired' + 'a jobs request should not happen because the cache hasn’t expired' ); assert.equal( presearchNodesRequestCount, From 379ad0c160b781110be3583f8cf77fc46a94555a Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Thu, 18 Jun 2020 14:29:32 -0500 Subject: [PATCH 21/24] Remove/update notes --- ui/app/components/global-search/control.js | 1 - ui/app/services/data-caches.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/app/components/global-search/control.js b/ui/app/components/global-search/control.js index 6ef42e2da3a..3b1b68129ad 100644 --- a/ui/app/components/global-search/control.js +++ b/ui/app/components/global-search/control.js @@ -33,7 +33,6 @@ export default class GlobalSearchControl extends Component { keyDownHandler(e) { const targetElementName = e.target.nodeName.toLowerCase(); - // FIXME are more and/or other-approach exceptions needed? if (targetElementName != 'input' && targetElementName != 'textarea') { if (e.keyCode === SLASH_KEY) { e.preventDefault(); diff --git a/ui/app/services/data-caches.js b/ui/app/services/data-caches.js index fe7fe7b82a8..c72433e881b 100644 --- a/ui/app/services/data-caches.js +++ b/ui/app/services/data-caches.js @@ -21,7 +21,7 @@ export default class DataCachesService extends Service { const now = Date.now(); if (this.router.isActive(route)) { - // FIXME Incorrect because it’s constantly being fetched by watchers, shouldn’t be marked as last fetched only on search + // TODO Incorrect because it’s constantly being fetched by watchers, shouldn’t be marked as last fetched only on search this.collectionLastFetched[modelName] = now; return this.store.peekAll(modelName); } else if (lastFetched && now - lastFetched < COLLECTION_CACHE_DURATION) { From 894de232ec6a0f42e3bbbff8db6648dc7038778a Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Thu, 18 Jun 2020 14:46:19 -0500 Subject: [PATCH 22/24] Remove/update notes --- ui/app/services/data-caches.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/app/services/data-caches.js b/ui/app/services/data-caches.js index c72433e881b..7617c61c583 100644 --- a/ui/app/services/data-caches.js +++ b/ui/app/services/data-caches.js @@ -10,7 +10,6 @@ export default class DataCachesService extends Service { collectionLastFetched = {}; async fetch(modelName) { - // Could this be dynamically generated based on use of watchers? 🤔 const modelNameToRoute = { job: 'jobs', node: 'clients', From 5749ec1cc8cfe73df23aa978138b1873c70db280 Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Thu, 18 Jun 2020 14:47:05 -0500 Subject: [PATCH 23/24] Change non-slash-stealing test to use page objects --- ui/tests/acceptance/jobs-list-test.js | 4 ++-- ui/tests/acceptance/search-test.js | 11 ++++------- ui/tests/pages/jobs/list.js | 7 +++++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ui/tests/acceptance/jobs-list-test.js b/ui/tests/acceptance/jobs-list-test.js index 1a23555abcb..bf0d7b1a55a 100644 --- a/ui/tests/acceptance/jobs-list-test.js +++ b/ui/tests/acceptance/jobs-list-test.js @@ -155,7 +155,7 @@ module('Acceptance | jobs list', function(hooks) { await JobsList.visit(); - await JobsList.search('dog'); + await JobsList.search.fillIn('dog'); assert.ok(JobsList.isEmpty, 'The empty message is shown'); assert.equal(JobsList.emptyState.headline, 'No Matches', 'The message is appropriate'); }); @@ -168,7 +168,7 @@ module('Acceptance | jobs list', function(hooks) { assert.equal(currentURL(), '/jobs?page=2', 'Page query param captures page=2'); - await JobsList.search('foobar'); + await JobsList.search.fillIn('foobar'); assert.equal(currentURL(), '/jobs?search=foobar', 'No page query param'); }); diff --git a/ui/tests/acceptance/search-test.js b/ui/tests/acceptance/search-test.js index c488476944c..3a8240223b7 100644 --- a/ui/tests/acceptance/search-test.js +++ b/ui/tests/acceptance/search-test.js @@ -1,8 +1,9 @@ import { module, test } from 'qunit'; -import { click, currentURL, triggerEvent, visit } from '@ember/test-helpers'; +import { currentURL, triggerEvent, visit } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import { setupMirage } from 'ember-cli-mirage/test-support'; import PageLayout from 'nomad-ui/tests/pages/layout'; +import JobsList from 'nomad-ui/tests/pages/jobs/list'; import { selectSearch } from 'ember-power-select/test-support'; import sinon from 'sinon'; @@ -131,12 +132,8 @@ module('Acceptance | search', function(hooks) { assert.notOk(PageLayout.navbar.search.field.isPresent); - // FIXME use page objects for this and below? 🤔 - await click('.search-box input'); - - await triggerEvent('.search-box input', 'keydown', { - keyCode: 191, // slash - }); + await JobsList.search.click(); + await JobsList.search.keydown({ keyCode: 191 }); assert.notOk(PageLayout.navbar.search.field.isPresent); }); diff --git a/ui/tests/pages/jobs/list.js b/ui/tests/pages/jobs/list.js index 2cad7087e0f..425ede744b8 100644 --- a/ui/tests/pages/jobs/list.js +++ b/ui/tests/pages/jobs/list.js @@ -3,10 +3,10 @@ import { create, collection, clickable, - fillable, isPresent, property, text, + triggerable, visitable, } from 'ember-cli-page-object'; @@ -18,7 +18,10 @@ export default create({ visit: visitable('/jobs'), - search: fillable('[data-test-jobs-search] input'), + search: { + scope: '[data-test-jobs-search] input', + keydown: triggerable('keydown'), + }, runJobButton: { scope: '[data-test-run-job]', From e4e7c78ddbcd445d228adfee9a0e989d42989ce5 Mon Sep 17 00:00:00 2001 From: Buck Doyle <buck@hashicorp.com> Date: Fri, 19 Jun 2020 08:25:34 -0500 Subject: [PATCH 24/24] Add namespace to job navigation --- ui/app/components/global-search/control.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/app/components/global-search/control.js b/ui/app/components/global-search/control.js index 3b1b68129ad..1a061ba8943 100644 --- a/ui/app/components/global-search/control.js +++ b/ui/app/components/global-search/control.js @@ -92,7 +92,9 @@ export default class GlobalSearchControl extends Component { const itemModelName = model.constructor.modelName; if (itemModelName === 'job') { - this.router.transitionTo('jobs.job', model.name); + this.router.transitionTo('jobs.job', model.name, { + queryParams: { namespace: model.get('namespace.name') }, + }); } else if (itemModelName === 'node') { this.router.transitionTo('clients.client', model.id); }