2020-06-19 18:05:28 +00:00
|
|
|
import Component from '@ember/component';
|
2020-06-25 13:51:52 +00:00
|
|
|
import { classNames } from '@ember-decorators/component';
|
2020-06-19 18:05:28 +00:00
|
|
|
import { task } from 'ember-concurrency';
|
|
|
|
import EmberObject, { action, computed, set } from '@ember/object';
|
|
|
|
import { alias } from '@ember/object/computed';
|
|
|
|
import { inject as service } from '@ember/service';
|
2020-06-25 13:51:52 +00:00
|
|
|
import { debounce, run } from '@ember/runloop';
|
2020-06-19 18:05:28 +00:00
|
|
|
import Searchable from 'nomad-ui/mixins/searchable';
|
|
|
|
import classic from 'ember-classic-decorator';
|
|
|
|
|
|
|
|
const SLASH_KEY = 191;
|
2020-08-05 20:58:44 +00:00
|
|
|
const MAXIMUM_RESULTS = 10;
|
2020-06-19 18:05:28 +00:00
|
|
|
|
2020-06-25 13:51:52 +00:00
|
|
|
@classNames('global-search-container')
|
2020-06-19 18:05:28 +00:00
|
|
|
export default class GlobalSearchControl extends Component {
|
|
|
|
@service dataCaches;
|
|
|
|
@service router;
|
|
|
|
@service store;
|
|
|
|
|
|
|
|
searchString = null;
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
super(...arguments);
|
|
|
|
|
|
|
|
this.jobSearch = JobSearch.create({
|
|
|
|
dataSource: this,
|
|
|
|
});
|
|
|
|
|
2020-11-04 18:12:31 +00:00
|
|
|
this.nodeNameSearch = NodeNameSearch.create({
|
|
|
|
dataSource: this,
|
|
|
|
});
|
|
|
|
|
|
|
|
this.nodeIdSearch = NodeIdSearch.create({
|
2020-06-19 18:05:28 +00:00
|
|
|
dataSource: this,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
keyDownHandler(e) {
|
|
|
|
const targetElementName = e.target.nodeName.toLowerCase();
|
|
|
|
|
|
|
|
if (targetElementName != 'input' && targetElementName != 'textarea') {
|
|
|
|
if (e.keyCode === SLASH_KEY) {
|
|
|
|
e.preventDefault();
|
|
|
|
this.open();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
didInsertElement() {
|
2020-06-22 18:23:28 +00:00
|
|
|
set(this, '_keyDownHandler', this.keyDownHandler.bind(this));
|
2020-06-19 18:05:28 +00:00
|
|
|
document.addEventListener('keydown', this._keyDownHandler);
|
|
|
|
}
|
|
|
|
|
|
|
|
willDestroyElement() {
|
|
|
|
document.removeEventListener('keydown', this._keyDownHandler);
|
|
|
|
}
|
|
|
|
|
|
|
|
@task(function*(string) {
|
|
|
|
try {
|
|
|
|
set(this, 'searchString', string);
|
|
|
|
|
|
|
|
const jobs = yield this.dataCaches.fetch('job');
|
|
|
|
const nodes = yield this.dataCaches.fetch('node');
|
|
|
|
|
|
|
|
set(this, 'jobs', jobs.toArray());
|
|
|
|
set(this, 'nodes', nodes.toArray());
|
|
|
|
|
2020-08-05 20:58:44 +00:00
|
|
|
const jobResults = this.jobSearch.listSearched.slice(0, MAXIMUM_RESULTS);
|
2020-11-04 18:12:31 +00:00
|
|
|
|
|
|
|
const mergedNodeListSearched = this.nodeIdSearch.listSearched.concat(this.nodeNameSearch.listSearched).uniq();
|
|
|
|
const nodeResults = mergedNodeListSearched.slice(0, MAXIMUM_RESULTS);
|
2020-06-19 18:05:28 +00:00
|
|
|
|
|
|
|
return [
|
|
|
|
{
|
2020-08-05 20:58:44 +00:00
|
|
|
groupName: resultsGroupLabel('Jobs', jobResults, this.jobSearch.listSearched),
|
2020-06-19 18:05:28 +00:00
|
|
|
options: jobResults,
|
|
|
|
},
|
|
|
|
{
|
2020-11-04 18:12:31 +00:00
|
|
|
groupName: resultsGroupLabel('Clients', nodeResults, mergedNodeListSearched),
|
2020-06-19 18:05:28 +00:00
|
|
|
options: nodeResults,
|
|
|
|
},
|
|
|
|
];
|
|
|
|
} catch (e) {
|
|
|
|
// eslint-disable-next-line
|
|
|
|
console.log('exception searching', e);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
search;
|
|
|
|
|
|
|
|
@action
|
|
|
|
open() {
|
|
|
|
if (this.select) {
|
|
|
|
this.select.actions.open();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
selectOption(model) {
|
|
|
|
const itemModelName = model.constructor.modelName;
|
|
|
|
|
|
|
|
if (itemModelName === 'job') {
|
2020-07-30 14:10:08 +00:00
|
|
|
this.router.transitionTo('jobs.job', model.plainId, {
|
2020-06-19 18:05:28 +00:00
|
|
|
queryParams: { namespace: model.get('namespace.name') },
|
|
|
|
});
|
|
|
|
} else if (itemModelName === 'node') {
|
|
|
|
this.router.transitionTo('clients.client', model.id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
storeSelect(select) {
|
|
|
|
if (select) {
|
|
|
|
this.select = 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) {
|
2020-06-25 13:51:52 +00:00
|
|
|
debounce(this, this.open, 150);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
onCloseEvent(select, event) {
|
|
|
|
if (event.key === 'Escape') {
|
2020-06-19 18:05:28 +00:00
|
|
|
run.next(() => {
|
2020-06-30 20:23:02 +00:00
|
|
|
this.element.querySelector('.ember-power-select-trigger').blur();
|
2020-06-19 18:05:28 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
calculatePosition(trigger) {
|
|
|
|
const { top, left, width } = trigger.getBoundingClientRect();
|
|
|
|
return {
|
|
|
|
style: {
|
|
|
|
left,
|
|
|
|
width,
|
|
|
|
top,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@classic
|
|
|
|
class JobSearch extends EmberObject.extend(Searchable) {
|
|
|
|
@computed
|
|
|
|
get searchProps() {
|
|
|
|
return ['id', 'name'];
|
|
|
|
}
|
|
|
|
|
|
|
|
@computed
|
|
|
|
get fuzzySearchProps() {
|
|
|
|
return ['name'];
|
|
|
|
}
|
|
|
|
|
|
|
|
@alias('dataSource.jobs') listToSearch;
|
|
|
|
@alias('dataSource.searchString') searchTerm;
|
|
|
|
|
|
|
|
fuzzySearchEnabled = true;
|
2020-06-25 13:51:52 +00:00
|
|
|
includeFuzzySearchMatches = true;
|
2020-06-19 18:05:28 +00:00
|
|
|
}
|
|
|
|
@classic
|
2020-11-04 18:12:31 +00:00
|
|
|
class NodeNameSearch extends EmberObject.extend(Searchable) {
|
2020-06-19 18:05:28 +00:00
|
|
|
@computed
|
|
|
|
get searchProps() {
|
2020-11-04 18:12:31 +00:00
|
|
|
return ['name'];
|
2020-06-19 18:05:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@computed
|
|
|
|
get fuzzySearchProps() {
|
|
|
|
return ['name'];
|
|
|
|
}
|
|
|
|
|
|
|
|
@alias('dataSource.nodes') listToSearch;
|
|
|
|
@alias('dataSource.searchString') searchTerm;
|
|
|
|
|
|
|
|
fuzzySearchEnabled = true;
|
2020-06-25 13:51:52 +00:00
|
|
|
includeFuzzySearchMatches = true;
|
2020-06-19 18:05:28 +00:00
|
|
|
}
|
2020-08-05 20:58:44 +00:00
|
|
|
|
2020-11-04 18:12:31 +00:00
|
|
|
@classic
|
|
|
|
class NodeIdSearch extends EmberObject.extend(Searchable) {
|
|
|
|
@computed
|
|
|
|
get regexSearchProps() {
|
|
|
|
return ['id'];
|
|
|
|
}
|
|
|
|
|
|
|
|
@alias('dataSource.nodes') listToSearch;
|
|
|
|
@computed('dataSource.searchString')
|
|
|
|
get searchTerm() {
|
|
|
|
return `^${this.get('dataSource.searchString')}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
exactMatchEnabled = false;
|
|
|
|
regexEnabled = true;
|
|
|
|
}
|
|
|
|
|
2020-08-05 20:58:44 +00:00
|
|
|
function resultsGroupLabel(type, renderedResults, allResults) {
|
|
|
|
let countString;
|
|
|
|
|
|
|
|
if (renderedResults.length < allResults.length) {
|
|
|
|
countString = `showing ${renderedResults.length} of ${allResults.length}`;
|
|
|
|
} else {
|
|
|
|
countString = renderedResults.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
return `${type} (${countString})`;
|
|
|
|
}
|