Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI: Add global search #7720

Closed
wants to merge 35 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
3627cb4
Add preliminary global search
backspace Apr 14, 2020
8d018a8
Add grouping of search results
backspace Apr 14, 2020
b2059fb
Remove empty groups from results
backspace Apr 14, 2020
c7193ba
Add preliminary styling
backspace Apr 14, 2020
8a7c35d
Remove timeout
backspace Apr 14, 2020
29fe15f
Add dynamic mock search endpoint
backspace Apr 14, 2020
aa23fe4
Add keyboard shortcut to open search
backspace Apr 15, 2020
f6f50be
Remove debugging code
backspace Apr 15, 2020
0333f60
Fix construction of mock search results
backspace Apr 15, 2020
7f3b3ff
Add navigation on search item selection
backspace Apr 15, 2020
72e0d0b
Remove debugging code
backspace Apr 15, 2020
634a2a4
Move search width into CSS
backspace Apr 15, 2020
edc4874
Update styling
backspace Apr 15, 2020
5a26bcc
Add count to results headings
backspace Apr 15, 2020
e14b2c2
Add preliminary styling of search results
backspace Apr 15, 2020
fa369b3
Add explicit selection of desired result types
backspace Apr 15, 2020
a748cee
Add humanising of result group names
backspace Apr 15, 2020
86f747a
Change model-fetching from peek to find
backspace Apr 15, 2020
58f41a5
Add status lights to search results
backspace Apr 16, 2020
216ea00
Remove precalculated labels
backspace Apr 16, 2020
e3b513c
Remove need to press enter after clicking control
backspace Apr 16, 2020
11f603b
Update appearance of search field
backspace Apr 16, 2020
2ba6704
Add explanation of focus action
backspace Apr 16, 2020
e483e13
Add namespace-awareness
backspace Apr 16, 2020
a37a503
Remove legacy of confusion 😝
backspace Apr 16, 2020
fcadd4b
Add region-handling
backspace Apr 16, 2020
0edaf12
Remove outdated FIXME
backspace Apr 16, 2020
516d50e
Add fallback for missing namespace
backspace Apr 16, 2020
d47483e
Add test for error-handling
backspace Apr 16, 2020
221f2da
Remove impossible test
backspace Apr 16, 2020
38fcf1b
Remove unused import
backspace Apr 16, 2020
dd3026a
Add check to ensure control doesn’t trap focus
backspace Apr 16, 2020
059e734
Fix position of dropdown
backspace Apr 17, 2020
50f55b2
Remove dropdown styles from control file
backspace Apr 17, 2020
a7dec52
Remove mistakenly-committed line
backspace Apr 20, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 169 additions & 0 deletions ui/app/components/global-search/control.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import Component from '@ember/component';
import { inject as service } from '@ember/service';
import { task } from 'ember-concurrency';
import fetch from 'nomad-ui/utils/fetch';
import { getOwner } from '@ember/application';
import { bindKeyboardShortcuts, unbindKeyboardShortcuts } from 'ember-keyboard-shortcuts';
import { run } from '@ember/runloop';

const SEARCH_PROPERTY_TO_LABEL = {
allocs: 'Allocations',
jobs: 'Jobs',
nodes: 'Clients',
};

export default Component.extend({
tagName: '',

router: service(),
store: service(),
system: service(),

opened: false,

keyboardShortcuts: {
'/': {
action: 'open',
global: false,
preventDefault: true,
},
},

didInsertElement() {
this._super(...arguments);
bindKeyboardShortcuts(this);
},

willDestroyElement() {
this._super(...arguments);
unbindKeyboardShortcuts(this);
},

actions: {
open() {
if (this.select) {
this.select.actions.open();
}
},

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();
});
}
},

storeSelect(select) {
if (select) {
this.select = select;
}
},

async select({ model }) {
const resolvedModel = await model.then();
const itemModelName = resolvedModel.constructor.modelName;

if (itemModelName === 'job') {
this.router.transitionTo('jobs.job', resolvedModel.name);
} else if (itemModelName === 'allocation') {
this.router.transitionTo('allocations.allocation', resolvedModel.id);
} else if (itemModelName === 'node') {
this.router.transitionTo('clients.client', resolvedModel.id);
}
},
},

search: task(function*(prefix) {
const applicationAdapter = getOwner(this).lookup('adapter:application');
const searchUrl = applicationAdapter.urlForFindAll('job').replace('jobs', 'search');
// FIXME hackery!
const query = new URLSearchParams();

if (this.system.get('activeNamespace.id')) {
query.append('namespace', this.system.activeNamespace.id);
}

if (this.system.activeRegion) {
query.append('region', this.system.activeRegion);
}

const response = yield fetch(`${searchUrl}?${query}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
Prefix: prefix,
Context: 'all',
}),
});
const json = yield response.json();

return ['allocs', 'jobs', 'nodes']
.filter(key => json.Matches[key])
.map(key => {
const matches = json.Matches[key];
const label = `${SEARCH_PROPERTY_TO_LABEL[key]} (${matches.length})`;

return {
groupName: label,
options: collectModels(
this.store,
this.system.get('activeNamespace.id') || 'default',
key,
json.Matches[key]
),
};
});
}),

calculatePosition(trigger) {
const { top, left, width } = trigger.getBoundingClientRect();
return {
style: {
left,
width,
top,
},
};
},
});

function collectModels(store, namespace, searchResultsTypeKey, matches) {
if (searchResultsTypeKey === 'jobs') {
return matches.map(id => {
const model = store.findRecord('job', JSON.stringify([id, namespace]));
return {
model,
labelProperty: 'name',
statusProperty: 'status',
};
});
} else if (searchResultsTypeKey === 'allocs') {
return matches.map(id => {
const model = store.findRecord('allocation', id);
return {
model,
labelProperty: 'id',
statusProperty: 'clientStatus',
};
});
} else if (searchResultsTypeKey === 'nodes') {
return matches.map(id => {
const model = store.findRecord('node', id);
return {
model,
labelProperty: 'id',
statusProperty: 'status',
};
});
}
}
2 changes: 2 additions & 0 deletions ui/app/styles/components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
@import './components/error-container';
@import './components/exec';
@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';
Expand Down
37 changes: 37 additions & 0 deletions ui/app/styles/components/global-search-control.scss
Original file line number Diff line number Diff line change
@@ -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%;
}
}
104 changes: 104 additions & 0 deletions ui/app/styles/components/global-search-dropdown.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
$queued: $grey-lighter;
$starting: $grey-lighter;
$running: $primary;
$complete: $nomad-green-dark;
$failed: $danger;
$lost: $dark;

.global-search-dropdown {
background: transparent;
border: 0;
position: fixed;

.ember-power-select-search {
margin-left: $icon-dimensions;
border: 0;
}

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;
}
}

// FIXME this is uncritically copied from charts/colors.scss
.color-swatch {
display: inline-block;
height: 0.6rem;
width: 0.6rem;
margin: 0.25rem 0.25rem 0.4rem 0.25rem;
vertical-align: middle;
border-radius: 1rem;

&.queued {
box-shadow: inset 0 0 0 1px rgba($black, 0.3);
background: $queued;
}

&.starting,
&.pending {
background: repeating-linear-gradient(
-45deg,
$starting,
$starting 3px,
darken($starting, 25%) 3px,
darken($starting, 25%) 6px
);
}

&.running,
&.ready {
background: $running;
}

&.complete {
background: $complete;
}

&.failed {
background: $failed;
}

&.lost,
&.dead {
background: $lost;
}

@each $name, $pair in $colors {
$color: nth($pair, 1);

&.is-#{$name} {
background: $color;
}
}
}
}

.ember-power-select-group-name {
text-transform: uppercase;
display: inline;
color: darken($grey-blue, 20%);
font-size: $size-7;
font-weight: $weight-semibold;
}
}
6 changes: 4 additions & 2 deletions ui/app/styles/core/navbar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -44,7 +46,7 @@
display: flex;
align-items: stretch;
justify-content: flex-end;
margin-left: auto;
margin-left: inherit;
}

.navbar-end > a.navbar-item {
Expand Down Expand Up @@ -100,7 +102,7 @@
display: flex;
align-items: center;
justify-content: flex-end;
margin-left: auto;
margin-left: inherit;
}

.navbar-end > a.navbar-item {
Expand Down
1 change: 1 addition & 0 deletions ui/app/templates/components/global-header.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
{{partial "partials/nomad-logo"}}
{{/link-to}}
</div>
{{global-search/control}}
<div class="navbar-end">
<a href="https://nomadproject.io/docs" class="navbar-item">Documentation</a>
{{#link-to "settings.tokens" class="navbar-item"}}ACL Tokens{{/link-to}}
Expand Down
17 changes: 17 additions & 0 deletions ui/app/templates/components/global-search/control.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<PowerSelect
@tagName="div"
class="global-search"
data-test-search
@searchEnabled={{true}}
@search={{perform this.search}}
@onChange={{action 'select'}}
@onFocus={{action 'openOnClickOrTab'}}
@dropdownClass="global-search-dropdown"
@calculatePosition={{this.calculatePosition}}
@searchMessageComponent="global-search/message"
@triggerComponent="global-search/trigger"
@registerAPI={{action 'storeSelect'}}
as |option|>
<div class="color-swatch {{get option.model option.statusProperty}}"></div>
{{get option.model option.labelProperty}}
</PowerSelect>
Empty file.
4 changes: 4 additions & 0 deletions ui/app/templates/components/global-search/trigger.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{{x-icon "search" class="is-small"}}
{{#unless select.isOpen}}
<span class='placeholder'>Search</span>
{{/unless}}
Loading