ui: Remove old style 'filterable' searching (#9356)
* Switch upstream-instances to use new style of searchable * Add search action to DataCollection plus basic README * Use DataCollection for PowerSelect searching in child-selectors * Remove old style filterable search for role/policies and instances * Remove old helpers/components related to search/sort/filter
This commit is contained in:
parent
52ca0f2a2b
commit
7f4cd240b8
|
@ -1,7 +0,0 @@
|
|||
{{yield}}
|
||||
<YieldSlot @name="content" @params={{block-params items}}>{{yield}}</YieldSlot>
|
||||
{{#if (gt items.length 0)}}
|
||||
<YieldSlot @name="set" @params={{block-params items}}>{{yield}}</YieldSlot>
|
||||
{{else}}
|
||||
<YieldSlot @name="empty">{{yield}}</YieldSlot>
|
||||
{{/if}}
|
|
@ -1,28 +0,0 @@
|
|||
import Component from '@ember/component';
|
||||
import { get, set } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import Slotted from 'block-slots';
|
||||
|
||||
export default Component.extend(Slotted, {
|
||||
tagName: '',
|
||||
dom: service('dom'),
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this._listeners = this.dom.listeners();
|
||||
},
|
||||
willDestroyElement: function() {
|
||||
this._listeners.remove();
|
||||
this._super(...arguments);
|
||||
},
|
||||
didReceiveAttrs: function() {
|
||||
this._super(...arguments);
|
||||
if (this.items !== this.dispatcher.data) {
|
||||
this._listeners.remove();
|
||||
this._listeners.add(this.dispatcher, {
|
||||
change: e => set(this, 'items', e.target.data),
|
||||
});
|
||||
set(this, 'items', get(this.dispatcher, 'data'));
|
||||
}
|
||||
this.dispatcher.search(this.terms);
|
||||
},
|
||||
});
|
|
@ -1,4 +1,5 @@
|
|||
<div
|
||||
class="child-selector {{type}}-child-selector"
|
||||
...attributes
|
||||
>
|
||||
{{yield}}
|
||||
|
@ -11,18 +12,28 @@
|
|||
@onchange={{action (mut allOptions) value="data"}}
|
||||
/>
|
||||
{{/if}}
|
||||
<DataCollection
|
||||
@type={{type}}
|
||||
@sort='Name:asc'
|
||||
@filters={{hash
|
||||
searchproperties=(array 'Name')
|
||||
}}
|
||||
@items={{options}}
|
||||
as |collection|>
|
||||
<PowerSelect
|
||||
@searchEnabled={{true}}
|
||||
@search={{action "search"}}
|
||||
@options={{options}}
|
||||
@loadingMessage="Loading..."
|
||||
@searchMessage="No possible options"
|
||||
@searchPlaceholder={{placeholder}}
|
||||
@onOpen={{action (mut isOpen) true}}
|
||||
@onClose={{action (mut isOpen) false}}
|
||||
@onChange={{action "change" "items[]" items}} as |item|>
|
||||
@searchEnabled={{true}}
|
||||
@search={{action collection.search}}
|
||||
@options={{options}}
|
||||
@loadingMessage="Loading..."
|
||||
@searchMessage="No possible options"
|
||||
@searchPlaceholder={{placeholder}}
|
||||
@onOpen={{action (mut isOpen) true}}
|
||||
@onClose={{action (mut isOpen) false}}
|
||||
@onChange={{action "change" "items[]" items}}
|
||||
as |item|>
|
||||
<YieldSlot @name="option" @params={{block-params item}}>{{yield}}</YieldSlot>
|
||||
</PowerSelect>
|
||||
</DataCollection>
|
||||
</label>
|
||||
{{#if (gt items.length 0)}}
|
||||
<YieldSlot @name="set">{{yield}}</YieldSlot>
|
||||
|
|
|
@ -15,8 +15,6 @@ export default Component.extend(Slotted, {
|
|||
type: '',
|
||||
|
||||
dom: service('dom'),
|
||||
search: service('search'),
|
||||
sort: service('sort'),
|
||||
formContainer: service('form'),
|
||||
|
||||
item: alias('form.data'),
|
||||
|
@ -26,7 +24,6 @@ export default Component.extend(Slotted, {
|
|||
init: function() {
|
||||
this._super(...arguments);
|
||||
this._listeners = this.dom.listeners();
|
||||
this.searchable = this.search.searchable(this.type);
|
||||
this.form = this.formContainer.form(this.type);
|
||||
this.form.clear({ Datacenter: this.dc, Namespace: this.nspace });
|
||||
},
|
||||
|
@ -34,14 +31,10 @@ export default Component.extend(Slotted, {
|
|||
this._super(...arguments);
|
||||
this._listeners.remove();
|
||||
},
|
||||
sortedOptions: sort('allOptions.[]', 'comparator'),
|
||||
comparator: computed(function() {
|
||||
return this.sort.comparator(this.type)();
|
||||
}),
|
||||
options: computed('selectedOptions.[]', 'sortedOptions.[]', function() {
|
||||
options: computed('selectedOptions.[]', 'allOptions.[]', function() {
|
||||
// It's not massively important here that we are defaulting `items` and
|
||||
// losing reference as its just to figure out the diff
|
||||
let options = this.sortedOptions || [];
|
||||
let options = this.allOptions || [];
|
||||
const items = this.selectedOptions || [];
|
||||
if (get(items, 'length') > 0) {
|
||||
// filter out any items from the available options that have already been
|
||||
|
@ -49,7 +42,6 @@ export default Component.extend(Slotted, {
|
|||
// TODO: find a proper ember-data diff
|
||||
options = options.filter(item => !items.findBy('ID', get(item, 'ID')));
|
||||
}
|
||||
this.searchable.add(options);
|
||||
return options;
|
||||
}),
|
||||
save: task(function*(item, items, success = function() {}) {
|
||||
|
@ -72,19 +64,6 @@ export default Component.extend(Slotted, {
|
|||
}
|
||||
}),
|
||||
actions: {
|
||||
search: function(term) {
|
||||
// TODO: make sure we can either search before things are loaded
|
||||
// or wait until we are loaded, guess power select take care of that
|
||||
return new Promise(resolve => {
|
||||
const remove = this._listeners.add(this.searchable, {
|
||||
change: e => {
|
||||
remove();
|
||||
resolve(e.target.data);
|
||||
},
|
||||
});
|
||||
this.searchable.search(term);
|
||||
});
|
||||
},
|
||||
reset: function() {
|
||||
this.form.clear({ Datacenter: this.dc, Namespace: this.nspace });
|
||||
},
|
||||
|
|
|
@ -2,65 +2,61 @@
|
|||
class="consul-upstream-instance-list"
|
||||
...attributes
|
||||
>
|
||||
{{#if (gt this.items.length 0)}}
|
||||
<ul>
|
||||
{{#each this.items as |item|}}
|
||||
<li>
|
||||
<div class="header">
|
||||
<p>
|
||||
{{item.DestinationName}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="detail">
|
||||
{{#if (env 'CONSUL_NSPACES_ENABLED')}}
|
||||
{{#if (not-eq item.DestinationType 'prepared_query')}}
|
||||
<dl class="nspace">
|
||||
<dt>
|
||||
<Tooltip>
|
||||
Namespace
|
||||
</Tooltip>
|
||||
</dt>
|
||||
<dd>
|
||||
{{or item.DestinationNamespace 'default'}}
|
||||
</dd>
|
||||
</dl>
|
||||
{{/if}}
|
||||
<ul>
|
||||
{{#each @items as |item|}}
|
||||
<li>
|
||||
<div class="header">
|
||||
<p>
|
||||
{{item.DestinationName}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="detail">
|
||||
{{#if (env 'CONSUL_NSPACES_ENABLED')}}
|
||||
{{#if (not-eq item.DestinationType 'prepared_query')}}
|
||||
<dl class="nspace">
|
||||
<dt>
|
||||
<Tooltip>
|
||||
Namespace
|
||||
</Tooltip>
|
||||
</dt>
|
||||
<dd>
|
||||
{{or item.DestinationNamespace 'default'}}
|
||||
</dd>
|
||||
</dl>
|
||||
{{/if}}
|
||||
{{#if (and (not-eq item.Datacenter @dc) (not-eq item.Datacenter ""))}}
|
||||
<dl class="datacenter">
|
||||
<dt>
|
||||
<Tooltip>
|
||||
Datacenter
|
||||
</Tooltip>
|
||||
</dt>
|
||||
<dd>
|
||||
{{item.Datacenter}}
|
||||
</dd>
|
||||
</dl>
|
||||
{{/if}}
|
||||
{{#if (gt item.LocalBindPort 0)}}
|
||||
{{/if}}
|
||||
{{#if (and (not-eq item.Datacenter @dc) (not-eq item.Datacenter ""))}}
|
||||
<dl class="datacenter">
|
||||
<dt>
|
||||
<Tooltip>
|
||||
Datacenter
|
||||
</Tooltip>
|
||||
</dt>
|
||||
<dd>
|
||||
{{item.Datacenter}}
|
||||
</dd>
|
||||
</dl>
|
||||
{{/if}}
|
||||
{{#if (gt item.LocalBindPort 0)}}
|
||||
{{#let (concat (or item.LocalBindAddress '127.0.0.1') ':' item.LocalBindPort) as |combinedAddress|}}
|
||||
<dl class="local-bind-address">
|
||||
<dt>
|
||||
<span>
|
||||
Address
|
||||
</span>
|
||||
</dt>
|
||||
<dd>
|
||||
<CopyButton
|
||||
@value={{combinedAddress}}
|
||||
@name="Address"
|
||||
/>
|
||||
{{combinedAddress}}
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="local-bind-address">
|
||||
<dt>
|
||||
<span>
|
||||
Address
|
||||
</span>
|
||||
</dt>
|
||||
<dd>
|
||||
<CopyButton
|
||||
@value={{combinedAddress}}
|
||||
@name="Address"
|
||||
/>
|
||||
{{combinedAddress}}
|
||||
</dd>
|
||||
</dl>
|
||||
{{/let}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{else}}
|
||||
{{yield api to="empty"}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
import Component from '@glimmer/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { sort } from '@ember/object/computed';
|
||||
|
||||
export default class ConsulUpstreamInstanceList extends Component {
|
||||
@service('sort') sort;
|
||||
@service('search') search;
|
||||
|
||||
@sort('searched', 'comparator') sorted;
|
||||
|
||||
get items() {
|
||||
return this.sorted;
|
||||
}
|
||||
get searched() {
|
||||
if (typeof this.args.search === 'undefined') {
|
||||
return this.args.items;
|
||||
}
|
||||
const predicate = this.search.predicate('upstream-instance');
|
||||
return this.args.items.filter(predicate(this.args.search));
|
||||
}
|
||||
get comparator() {
|
||||
return this.sort.comparator('upstream-instance')(this.args.sort);
|
||||
}
|
||||
}
|
|
@ -2,11 +2,33 @@
|
|||
class="consul-upstream-instance-search-bar filter-bar"
|
||||
...attributes
|
||||
>
|
||||
<FreetextFilter
|
||||
@onsearch={{action @onsearch}}
|
||||
@value={{@search}}
|
||||
@placeholder="Search"
|
||||
/>
|
||||
<div class="search">
|
||||
<FreetextFilter
|
||||
@onsearch={{action @onsearch}}
|
||||
@value={{@search}}
|
||||
@placeholder="Search"
|
||||
>
|
||||
<PopoverSelect
|
||||
class="type-search-properties"
|
||||
@position="right"
|
||||
@onchange={{action @onfilter.searchproperty}}
|
||||
@multiple={{true}}
|
||||
as |components|>
|
||||
<BlockSlot @name="selected">
|
||||
<span>
|
||||
Search across
|
||||
</span>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="options">
|
||||
{{#let components.Optgroup components.Option as |Optgroup Option|}}
|
||||
{{#each @searchproperties as |prop|}}
|
||||
<Option @value={{prop}} @selected={{contains prop @filter.searchproperties}}>{{prop}}</Option>
|
||||
{{/each}}
|
||||
{{/let}}
|
||||
</BlockSlot>
|
||||
</PopoverSelect>
|
||||
</FreetextFilter>
|
||||
</div>
|
||||
<div class="sort">
|
||||
{{#let (or @sort 'DestinationName:asc') as |sort|}}
|
||||
<PopoverSelect
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
## DataSource
|
||||
|
||||
```handlebars
|
||||
<DataCollection
|
||||
@search={{''}}
|
||||
@sort={{''}}
|
||||
@filter={{hash
|
||||
searchproperties=(array)
|
||||
}}
|
||||
@items={{array}}
|
||||
as |collection|>
|
||||
{{collection.items.length}}
|
||||
<collection.Collection>
|
||||
Has Results
|
||||
</collection.Collection>
|
||||
<collection.Empty>
|
||||
Is Empty
|
||||
</collection.Empty>
|
||||
</DataCollection>
|
||||
```
|
||||
|
||||
### Arguments
|
||||
|
||||
| Argument | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `items` | `Array` | [] | The collection of data to search/sort/filter |
|
||||
| `search` | `String` | '' | A search term to use for searching |
|
||||
| `sort` | `String` | '' | A sort term to use for sorting on ('Name:asc') |
|
||||
| `filter` | `Object` | | An object whose properties are the properties/values to filter on |
|
||||
|
||||
### Yields
|
||||
|
||||
The DataCollection yields an object containing the following:
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `items` | `Array` | The resulting collection of data after searching/sorting/filtering |
|
||||
| `search` | `Function` | An action used to perform a search - takes a single string argument that should be the search term |
|
||||
| `Collection` | `Component` | A slot-like component that only renders when the items in the collection is greater than 0 |
|
||||
| `Empty` | `Component` | A slot-like component that only renders when the items in the collection is 0 |
|
||||
|
||||
### See
|
||||
|
||||
- [Component Source Code](./index.js)
|
||||
- [Template Source Code](./index.hbs)
|
||||
|
||||
---
|
|
@ -1,4 +1,6 @@
|
|||
{{did-update (action (fn (set this 'term') '') @search)}}
|
||||
{{yield (hash
|
||||
search=(action this.search)
|
||||
items=this.items
|
||||
Collection=(if (gt this.items.length 0) (component 'anonymous') '')
|
||||
Empty=(if (eq this.items.length 0) (component 'anonymous') '')
|
||||
|
|
|
@ -1,18 +1,32 @@
|
|||
import Component from '@glimmer/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { computed } from '@ember/object';
|
||||
import { computed, get, action } from '@ember/object';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { sort } from '@ember/object/computed';
|
||||
import { defineProperty } from '@ember/object';
|
||||
|
||||
export default class DataCollectionComponent extends Component {
|
||||
@service('filter') filter;
|
||||
@service('sort') sort;
|
||||
@service('search') search;
|
||||
@service('search') searchService;
|
||||
|
||||
@tracked term = '';
|
||||
|
||||
get type() {
|
||||
return this.args.type;
|
||||
}
|
||||
|
||||
@computed('term', 'args.search')
|
||||
get searchTerm() {
|
||||
return this.term || this.args.search || '';
|
||||
}
|
||||
|
||||
@action
|
||||
search(term) {
|
||||
this.term = term;
|
||||
return this.items;
|
||||
}
|
||||
|
||||
@computed('args.items', 'args.items.content')
|
||||
get content() {
|
||||
// TODO: Temporary little hack to ensure we detect DataSource proxy
|
||||
|
@ -37,17 +51,17 @@ export default class DataCollectionComponent extends Component {
|
|||
return this.sorted;
|
||||
}
|
||||
|
||||
@computed('type', 'filtered', 'args.filters.searchproperties', 'args.search')
|
||||
@computed('type', 'filtered', 'args.filters.searchproperties', 'searchTerm')
|
||||
get searched() {
|
||||
if (typeof this.args.search === 'undefined') {
|
||||
if (typeof this.searchTerm === '') {
|
||||
return this.filtered;
|
||||
}
|
||||
const predicate = this.search.predicate(this.type);
|
||||
const predicate = this.searchService.predicate(this.type);
|
||||
const options = {};
|
||||
if (typeof this.args.filters.searchproperties !== 'undefined') {
|
||||
if (typeof get(this, 'args.filters.searchproperties') !== 'undefined') {
|
||||
options.properties = this.args.filters.searchproperties;
|
||||
}
|
||||
return this.filtered.filter(predicate(this.args.search, options));
|
||||
return this.filtered.filter(predicate(this.searchTerm, options));
|
||||
}
|
||||
|
||||
@computed('type', 'content', 'args.filters')
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
import Helper from '@ember/component/helper';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Helper.extend({
|
||||
sort: service('sort'),
|
||||
compute([type, key], hash) {
|
||||
return this.sort.comparator(type)(key);
|
||||
},
|
||||
});
|
|
@ -1,9 +0,0 @@
|
|||
import Helper from '@ember/component/helper';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Helper.extend({
|
||||
search: service('search'),
|
||||
compute([type, items], hash) {
|
||||
return this.search.searchable(type).add(items);
|
||||
},
|
||||
});
|
|
@ -1,46 +0,0 @@
|
|||
import { helper } from '@ember/component/helper';
|
||||
import { slugify } from 'consul-ui/helpers/slugify';
|
||||
export const selectableKeyValues = function(params = [], hash = {}) {
|
||||
let selected;
|
||||
|
||||
const items = params.map(function(item, i) {
|
||||
let key, value;
|
||||
switch (typeof item) {
|
||||
case 'string':
|
||||
key = slugify([item]);
|
||||
value = item;
|
||||
break;
|
||||
default:
|
||||
if (item.length > 1) {
|
||||
key = item[0];
|
||||
value = item[1];
|
||||
} else {
|
||||
key = slugify([item[0]]);
|
||||
value = item[0];
|
||||
}
|
||||
break;
|
||||
}
|
||||
const kv = {
|
||||
key: key,
|
||||
value: value,
|
||||
};
|
||||
switch (typeof hash.selected) {
|
||||
case 'string':
|
||||
if (hash.selected === item[0]) {
|
||||
selected = kv;
|
||||
}
|
||||
break;
|
||||
case 'number':
|
||||
if (hash.selected === i) {
|
||||
selected = kv;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return kv;
|
||||
});
|
||||
return {
|
||||
items: items,
|
||||
selected: typeof selected === 'undefined' ? items[0] : selected,
|
||||
};
|
||||
};
|
||||
export default helper(selectableKeyValues);
|
|
@ -7,6 +7,10 @@ export default class UpstreamsRoute extends Route {
|
|||
as: 'filter',
|
||||
replace: true,
|
||||
},
|
||||
searchproperty: {
|
||||
as: 'searchproperty',
|
||||
empty: [['DestinationName', 'LocalBindAddress', 'LocalBindPort']],
|
||||
},
|
||||
};
|
||||
|
||||
model() {
|
||||
|
@ -14,7 +18,10 @@ export default class UpstreamsRoute extends Route {
|
|||
.split('.')
|
||||
.slice(0, -1)
|
||||
.join('.');
|
||||
return this.modelFor(parent);
|
||||
return {
|
||||
...this.modelFor(parent),
|
||||
searchProperties: this.queryParams.searchproperty.empty[0],
|
||||
};
|
||||
}
|
||||
|
||||
setupController(controller, model) {
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import { get } from '@ember/object';
|
||||
export default function(filterable) {
|
||||
return filterable(function(item, { s = '' }) {
|
||||
const term = s.toLowerCase();
|
||||
return (
|
||||
get(item, 'Service')
|
||||
.toLowerCase()
|
||||
.indexOf(term) !== -1 ||
|
||||
get(item, 'ID')
|
||||
.toLowerCase()
|
||||
.indexOf(term) !== -1 ||
|
||||
(get(item, 'Tags') || []).some(function(item) {
|
||||
return item.toLowerCase().indexOf(term) !== -1;
|
||||
}) ||
|
||||
get(item, 'Port')
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.indexOf(term) !== -1
|
||||
);
|
||||
});
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
import { get } from '@ember/object';
|
||||
export default function(filterable) {
|
||||
return filterable(function(item, { s = '' }) {
|
||||
const sLower = s.toLowerCase();
|
||||
return (
|
||||
get(item, 'Name')
|
||||
.toLowerCase()
|
||||
.indexOf(sLower) !== -1 ||
|
||||
get(item, 'Description')
|
||||
.toLowerCase()
|
||||
.indexOf(sLower) !== -1
|
||||
);
|
||||
});
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import { get } from '@ember/object';
|
||||
export default function(filterable) {
|
||||
return filterable(function(item, { s = '' }) {
|
||||
const sLower = s.toLowerCase();
|
||||
return (
|
||||
get(item, 'Name')
|
||||
.toLowerCase()
|
||||
.indexOf(sLower) !== -1 ||
|
||||
get(item, 'Description')
|
||||
.toLowerCase()
|
||||
.indexOf(sLower) !== -1 ||
|
||||
(get(item, 'Policies') || []).some(function(item) {
|
||||
return item.Name.toLowerCase().indexOf(sLower) !== -1;
|
||||
}) ||
|
||||
(get(item, 'ServiceIdentities') || []).some(function(item) {
|
||||
return item.ServiceName.toLowerCase().indexOf(sLower) !== -1;
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import { get } from '@ember/object';
|
||||
export default function(filterable) {
|
||||
return filterable(function(item, { s = '' }) {
|
||||
const term = s.toLowerCase();
|
||||
return (
|
||||
get(item, 'Node.Node')
|
||||
.toLowerCase()
|
||||
.indexOf(term) !== -1 ||
|
||||
get(item, 'Service.ID')
|
||||
.toLowerCase()
|
||||
.indexOf(term) !== -1 ||
|
||||
`${get(item, 'Service.Address')}:${get(item, 'Service.Port')}`.indexOf(term) !== -1
|
||||
);
|
||||
});
|
||||
}
|
|
@ -1,12 +1,10 @@
|
|||
export default () => term => item => {
|
||||
const lowerTerm = term.toLowerCase();
|
||||
return Object.entries(item)
|
||||
.filter(([key, value]) => key !== 'DestinationType')
|
||||
.some(
|
||||
([key, value]) =>
|
||||
value
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.indexOf(lowerTerm) !== -1
|
||||
);
|
||||
export default {
|
||||
DestinationName: (item, value) =>
|
||||
item.DestinationName.toLowerCase().indexOf(value.toLowerCase()) !== -1,
|
||||
LocalBindAddress: (item, value) =>
|
||||
item.LocalBindAddress.toLowerCase().indexOf(value.toLowerCase()) !== -1,
|
||||
LocalBindPort: (item, value) =>
|
||||
item.LocalBindPort.toString()
|
||||
.toLowerCase()
|
||||
.indexOf(value.toLowerCase()) !== -1,
|
||||
};
|
||||
|
|
|
@ -14,20 +14,6 @@ import role from 'consul-ui/search/predicates/role';
|
|||
import policy from 'consul-ui/search/predicates/policy';
|
||||
import nspace from 'consul-ui/search/predicates/nspace';
|
||||
|
||||
import filteredRole from 'consul-ui/search/filters/role';
|
||||
import filteredPolicy from 'consul-ui/search/filters/policy';
|
||||
// service instance
|
||||
import nodeService from 'consul-ui/search/filters/node/service';
|
||||
import serviceNode from 'consul-ui/search/filters/service/node';
|
||||
|
||||
import filterableFactory from 'consul-ui/utils/search/filterable';
|
||||
const filterable = filterableFactory();
|
||||
const searchables = {
|
||||
serviceInstance: serviceNode(filterable),
|
||||
nodeservice: nodeService(filterable),
|
||||
role: filteredRole(filterable),
|
||||
policy: filteredPolicy(filterable),
|
||||
};
|
||||
export const search = spec => {
|
||||
let possible = Object.keys(spec);
|
||||
return (term, options = {}) => {
|
||||
|
@ -47,7 +33,7 @@ const predicates = {
|
|||
intention: search(intention),
|
||||
service: search(service),
|
||||
['service-instance']: search(serviceInstance),
|
||||
['upstream-instance']: upstreamInstance(),
|
||||
['upstream-instance']: search(upstreamInstance),
|
||||
['health-check']: search(healthCheck),
|
||||
node: search(node),
|
||||
kv: search(kv),
|
||||
|
@ -58,9 +44,6 @@ const predicates = {
|
|||
nspace: search(nspace),
|
||||
};
|
||||
export default class SearchService extends Service {
|
||||
searchable(name) {
|
||||
return searchables[name];
|
||||
}
|
||||
predicate(name) {
|
||||
return predicates[name];
|
||||
}
|
||||
|
|
|
@ -1,37 +1,53 @@
|
|||
<div class="tab-section">
|
||||
<div role="tabpanel">
|
||||
{{#let (or sortBy "DestinationName:asc") as |sort|}}
|
||||
{{#if (gt proxy.Service.Proxy.Upstreams.length 0)}}
|
||||
<input type="checkbox" id="toolbar-toggle" />
|
||||
<Consul::UpstreamInstance::SearchBar
|
||||
@search={{search}}
|
||||
@onsearch={{action (mut search) value="target.value"}}
|
||||
{{#let (hash
|
||||
searchproperties=(if (not-eq searchproperty undefined)
|
||||
(split searchproperty ',')
|
||||
searchProperties
|
||||
)
|
||||
) as |filters|}}
|
||||
{{#let (or sortBy "DestinationName:asc") as |sort|}}
|
||||
{{#if (gt proxy.Service.Proxy.Upstreams.length 0)}}
|
||||
<input type="checkbox" id="toolbar-toggle" />
|
||||
<Consul::UpstreamInstance::SearchBar
|
||||
@search={{search}}
|
||||
@onsearch={{action (mut search) value="target.value"}}
|
||||
@searchproperties={{searchProperties}}
|
||||
|
||||
@sort={{sort}}
|
||||
@onsort={{action (mut sortBy) value="target.selected"}}
|
||||
/>
|
||||
<Consul::UpstreamInstance::List
|
||||
@search={{search}}
|
||||
@sort={{sort}}
|
||||
@items={{proxy.Service.Proxy.Upstreams}}
|
||||
@dc={{dc}}
|
||||
@nspace={{nspace}}
|
||||
>
|
||||
<:empty>
|
||||
<EmptyState>
|
||||
<BlockSlot @name="body">
|
||||
<p>
|
||||
{{#if search.length}}
|
||||
No upstreams where found matching that search.
|
||||
{{else}}
|
||||
This service has no upstreams.
|
||||
{{/if}}
|
||||
</p>
|
||||
</BlockSlot>
|
||||
</EmptyState>
|
||||
</:empty>
|
||||
</Consul::UpstreamInstance::List>
|
||||
{{/if}}
|
||||
@sort={{sort}}
|
||||
@onsort={{action (mut sortBy) value="target.selected"}}
|
||||
|
||||
@filter={{filters}}
|
||||
@onfilter={{hash
|
||||
searchproperty=(action (mut searchproperty) value="target.selectedItems")
|
||||
}}
|
||||
/>
|
||||
<DataCollection
|
||||
@type="upstream-instance"
|
||||
@sort={{sort}}
|
||||
@filters={{filters}}
|
||||
@search={{search}}
|
||||
@items={{proxy.Service.Proxy.Upstreams}}
|
||||
as |collection|>
|
||||
<collection.Collection>
|
||||
<Consul::UpstreamInstance::List
|
||||
@items={{collection.items}}
|
||||
@dc={{dc}}
|
||||
@nspace={{nspace}}
|
||||
/>
|
||||
</collection.Collection>
|
||||
<collection.Empty>
|
||||
<EmptyState>
|
||||
<BlockSlot @name="body">
|
||||
<p>
|
||||
This service has no upstreams{{#if (gt proxy.Service.Proxy.Upstreams.length 0)}} matching that search{{/if}}.
|
||||
</p>
|
||||
</BlockSlot>
|
||||
</EmptyState>
|
||||
</collection.Empty>
|
||||
</DataCollection>
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
{{/let}}
|
||||
</div>
|
||||
</div>
|
|
@ -1,49 +0,0 @@
|
|||
import RSVP from 'rsvp';
|
||||
export default function(EventTarget = RSVP.EventTarget, P = Promise) {
|
||||
// TODO: Class-ify
|
||||
return function(filter) {
|
||||
return EventTarget.mixin({
|
||||
value: '',
|
||||
_data: [],
|
||||
add: function(data) {
|
||||
this.data = this._data = data;
|
||||
return this;
|
||||
},
|
||||
find: function(terms = []) {
|
||||
this.value = terms
|
||||
.filter(function(item) {
|
||||
return typeof item === 'string' && item !== '';
|
||||
})
|
||||
.map(function(term) {
|
||||
return term.trim();
|
||||
});
|
||||
return P.resolve(
|
||||
this.value.reduce(function(prev, term) {
|
||||
return prev.filter(item => {
|
||||
return filter(item, { s: term });
|
||||
});
|
||||
}, this._data)
|
||||
);
|
||||
},
|
||||
search: function(terms = []) {
|
||||
// specifically no return here we return `this` instead
|
||||
// right now filtering is sync but we introduce an async
|
||||
// flow now for later on
|
||||
this.find(Array.isArray(terms) ? terms : [terms]).then(data => {
|
||||
// TODO: For the moment, lets just fake a target
|
||||
this.data = data;
|
||||
this.trigger('change', {
|
||||
target: {
|
||||
value: this.value.join('\n'),
|
||||
// TODO: selectedOptions is what <select> uses, consider that
|
||||
data: data,
|
||||
},
|
||||
});
|
||||
// not returned
|
||||
return data;
|
||||
});
|
||||
return this;
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
import { module, skip } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
module('Integration | Component | changeable set', function(hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
skip('it renders', async function(assert) {
|
||||
// Set any properties with this.set('myProperty', 'value');
|
||||
// Handle any actions with this.on('myAction', function(val) { ... });
|
||||
|
||||
await render(hbs`{{changeable-set}}`);
|
||||
|
||||
assert.equal(this.element.textContent.trim(), '');
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
{{#changeable-set}}
|
||||
{{/changeable-set}}
|
||||
`);
|
||||
assert.equal(this.element.textContent.trim(), '');
|
||||
});
|
||||
});
|
|
@ -1,49 +0,0 @@
|
|||
import { selectableKeyValues } from 'consul-ui/helpers/selectable-key-values';
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
module('Unit | Helper | selectable-key-values', function() {
|
||||
test('it turns arrays into key values and selects the first item by default', function(assert) {
|
||||
const actual = selectableKeyValues([
|
||||
['key-1', 'value-1'],
|
||||
['key-2', 'value-2'],
|
||||
]);
|
||||
assert.equal(actual.items.length, 2);
|
||||
assert.deepEqual(actual.selected, { key: 'key-1', value: 'value-1' });
|
||||
});
|
||||
test('it turns arrays into key values and selects the defined key', function(assert) {
|
||||
const actual = selectableKeyValues(
|
||||
[
|
||||
['key-1', 'value-1'],
|
||||
['key-2', 'value-2'],
|
||||
],
|
||||
{
|
||||
selected: 'key-2',
|
||||
}
|
||||
);
|
||||
assert.equal(actual.items.length, 2);
|
||||
assert.deepEqual(actual.selected, { key: 'key-2', value: 'value-2' });
|
||||
});
|
||||
test('it turns arrays into key values and selects the defined index', function(assert) {
|
||||
const actual = selectableKeyValues(
|
||||
[
|
||||
['key-1', 'value-1'],
|
||||
['key-2', 'value-2'],
|
||||
],
|
||||
{
|
||||
selected: 1,
|
||||
}
|
||||
);
|
||||
assert.equal(actual.items.length, 2);
|
||||
assert.deepEqual(actual.selected, { key: 'key-2', value: 'value-2' });
|
||||
});
|
||||
test('it turns arrays with only one element into key values and selects the defined index', function(assert) {
|
||||
const actual = selectableKeyValues([['Value 1'], ['Value 2']], { selected: 1 });
|
||||
assert.equal(actual.items.length, 2);
|
||||
assert.deepEqual(actual.selected, { key: 'value-2', value: 'Value 2' });
|
||||
});
|
||||
test('it turns strings into key values and selects the defined index', function(assert) {
|
||||
const actual = selectableKeyValues(['Value 1', 'Value 2'], { selected: 1 });
|
||||
assert.equal(actual.items.length, 2);
|
||||
assert.deepEqual(actual.selected, { key: 'value-2', value: 'Value 2' });
|
||||
});
|
||||
});
|
|
@ -1,94 +0,0 @@
|
|||
import getFilter from 'consul-ui/search/filters/node/service';
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
module('Unit | Search | Filter | node/service', function() {
|
||||
const filter = getFilter(cb => cb);
|
||||
test('items are found by properties', function(assert) {
|
||||
[
|
||||
{
|
||||
Service: 'service-HIT',
|
||||
ID: 'id',
|
||||
Port: 8500,
|
||||
Tags: [],
|
||||
},
|
||||
{
|
||||
Service: 'service',
|
||||
ID: 'id-HiT',
|
||||
Port: 8500,
|
||||
Tags: [],
|
||||
},
|
||||
{
|
||||
Service: 'service',
|
||||
ID: 'id',
|
||||
Port: 8500,
|
||||
Tags: ['tag', 'tag-withHiT'],
|
||||
},
|
||||
].forEach(function(item) {
|
||||
const actual = filter(item, {
|
||||
s: 'hit',
|
||||
});
|
||||
assert.ok(actual);
|
||||
});
|
||||
});
|
||||
test('items are found by port (non-string)', function(assert) {
|
||||
[
|
||||
{
|
||||
Service: 'service',
|
||||
ID: 'id',
|
||||
Port: 8500,
|
||||
Tags: ['tag', 'tag'],
|
||||
},
|
||||
].forEach(function(item) {
|
||||
const actual = filter(item, {
|
||||
s: '8500',
|
||||
});
|
||||
assert.ok(actual);
|
||||
});
|
||||
});
|
||||
test('items are not found', function(assert) {
|
||||
[
|
||||
{
|
||||
Service: 'service',
|
||||
ID: 'id',
|
||||
Port: 8500,
|
||||
},
|
||||
{
|
||||
Service: 'service',
|
||||
ID: 'id',
|
||||
Port: 8500,
|
||||
Tags: ['one', 'two'],
|
||||
},
|
||||
].forEach(function(item) {
|
||||
const actual = filter(item, {
|
||||
s: 'hit',
|
||||
});
|
||||
assert.notOk(actual);
|
||||
});
|
||||
});
|
||||
test('tags can be empty', function(assert) {
|
||||
[
|
||||
{
|
||||
Service: 'service',
|
||||
ID: 'id',
|
||||
Port: 8500,
|
||||
},
|
||||
{
|
||||
Service: 'service',
|
||||
ID: 'id',
|
||||
Port: 8500,
|
||||
Tags: null,
|
||||
},
|
||||
{
|
||||
Service: 'service',
|
||||
ID: 'id',
|
||||
Port: 8500,
|
||||
Tags: [],
|
||||
},
|
||||
].forEach(function(item) {
|
||||
const actual = filter(item, {
|
||||
s: 'hit',
|
||||
});
|
||||
assert.notOk(actual);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,36 +0,0 @@
|
|||
import getFilter from 'consul-ui/search/filters/policy';
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
module('Unit | Search | Filter | policy', function() {
|
||||
const filter = getFilter(cb => cb);
|
||||
test('items are found by properties', function(assert) {
|
||||
[
|
||||
{
|
||||
Name: 'name-HIT',
|
||||
Description: 'description',
|
||||
},
|
||||
{
|
||||
Name: 'name',
|
||||
Description: 'desc-HIT-ription',
|
||||
},
|
||||
].forEach(function(item) {
|
||||
const actual = filter(item, {
|
||||
s: 'hit',
|
||||
});
|
||||
assert.ok(actual);
|
||||
});
|
||||
});
|
||||
test('items are not found', function(assert) {
|
||||
[
|
||||
{
|
||||
Name: 'name',
|
||||
Description: 'description',
|
||||
},
|
||||
].forEach(function(item) {
|
||||
const actual = filter(item, {
|
||||
s: 'hit',
|
||||
});
|
||||
assert.notOk(actual);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,74 +0,0 @@
|
|||
import getFilter from 'consul-ui/search/filters/service/node';
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
module('Unit | Search | Filter | service/node', function() {
|
||||
const filter = getFilter(cb => cb);
|
||||
test('items are found by properties', function(assert) {
|
||||
[
|
||||
{
|
||||
Service: {
|
||||
ID: 'hit',
|
||||
},
|
||||
Node: {
|
||||
Node: 'node',
|
||||
},
|
||||
},
|
||||
{
|
||||
Service: {
|
||||
ID: 'id',
|
||||
},
|
||||
Node: {
|
||||
Node: 'nodeHiT',
|
||||
},
|
||||
},
|
||||
].forEach(function(item) {
|
||||
const actual = filter(item, {
|
||||
s: 'hit',
|
||||
});
|
||||
assert.ok(actual);
|
||||
});
|
||||
});
|
||||
test('items are found by address:port', function(assert) {
|
||||
const instance = {
|
||||
Service: {
|
||||
ID: 'id',
|
||||
Address: '0.0.0.0',
|
||||
Port: 8000,
|
||||
},
|
||||
Node: {
|
||||
Node: 'node-0',
|
||||
},
|
||||
};
|
||||
['0.0.0.0', '8000', '0:8000', '0.0.0.0:8000'].forEach(function(item) {
|
||||
let actual = filter(instance, {
|
||||
s: item,
|
||||
});
|
||||
assert.ok(actual);
|
||||
});
|
||||
});
|
||||
test('items are not found', function(assert) {
|
||||
[
|
||||
{
|
||||
Service: {
|
||||
ID: 'ID',
|
||||
},
|
||||
Node: {
|
||||
Node: 'node',
|
||||
},
|
||||
},
|
||||
{
|
||||
Service: {
|
||||
ID: 'id',
|
||||
},
|
||||
Node: {
|
||||
Node: 'node',
|
||||
},
|
||||
},
|
||||
].forEach(function(item) {
|
||||
const actual = filter(item, {
|
||||
s: 'hit',
|
||||
});
|
||||
assert.notOk(actual);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
import predicates from 'consul-ui/search/predicates/policy';
|
||||
import { search as create } from 'consul-ui/services/search';
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
module('Unit | Search | Predicate | policy', function() {
|
||||
const search = create(predicates);
|
||||
test('items are found by properties', function(assert) {
|
||||
const actual = [
|
||||
{
|
||||
Name: 'name-HIT',
|
||||
Description: 'description',
|
||||
},
|
||||
{
|
||||
Name: 'name',
|
||||
Description: 'desc-HIT-ription',
|
||||
},
|
||||
].filter(search('hit'));
|
||||
assert.equal(actual.length, 2);
|
||||
});
|
||||
test('items are not found', function(assert) {
|
||||
const actual = [
|
||||
{
|
||||
Name: 'name',
|
||||
Description: 'description',
|
||||
},
|
||||
].filter(search('hit'));
|
||||
assert.equal(actual.length, 0);
|
||||
});
|
||||
});
|
|
@ -1,10 +1,11 @@
|
|||
import getFilter from 'consul-ui/search/filters/role';
|
||||
import predicates from 'consul-ui/search/predicates/role';
|
||||
import { search as create } from 'consul-ui/services/search';
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
module('Unit | Search | Filter | role', function() {
|
||||
const filter = getFilter(cb => cb);
|
||||
module('Unit | Search | Predicate | role', function() {
|
||||
const search = create(predicates);
|
||||
test('items are found by properties', function(assert) {
|
||||
[
|
||||
const actual = [
|
||||
{
|
||||
Name: 'name-HIT',
|
||||
Description: 'description',
|
||||
|
@ -28,15 +29,11 @@ module('Unit | Search | Filter | role', function() {
|
|||
{ ServiceName: 'service-identity-HIT' },
|
||||
],
|
||||
},
|
||||
].forEach(function(item) {
|
||||
const actual = filter(item, {
|
||||
s: 'hit',
|
||||
});
|
||||
assert.ok(actual);
|
||||
});
|
||||
].filter(search('hit'));
|
||||
assert.equal(actual.length, 4);
|
||||
});
|
||||
test('items are not found', function(assert) {
|
||||
[
|
||||
const actual = [
|
||||
{
|
||||
Name: 'name',
|
||||
Description: 'description',
|
||||
|
@ -53,15 +50,11 @@ module('Unit | Search | Filter | role', function() {
|
|||
Description: 'description',
|
||||
ServiceIdenitities: [{ ServiceName: 'si' }, { ServiceName: 'si-second' }],
|
||||
},
|
||||
].forEach(function(item) {
|
||||
const actual = filter(item, {
|
||||
s: 'hit',
|
||||
});
|
||||
assert.notOk(actual);
|
||||
});
|
||||
].filter(search('hit'));
|
||||
assert.equal(actual.length, 0);
|
||||
});
|
||||
test('arraylike things can be empty', function(assert) {
|
||||
[
|
||||
const actual = [
|
||||
{
|
||||
Name: 'name',
|
||||
Description: 'description',
|
||||
|
@ -79,11 +72,7 @@ module('Unit | Search | Filter | role', function() {
|
|||
Policies: [],
|
||||
ServiceIdentities: [],
|
||||
},
|
||||
].forEach(function(item) {
|
||||
const actual = filter(item, {
|
||||
s: 'hit',
|
||||
});
|
||||
assert.notOk(actual);
|
||||
});
|
||||
].filter(search('hit'));
|
||||
assert.equal(actual.length, 0);
|
||||
});
|
||||
});
|
|
@ -1,10 +0,0 @@
|
|||
import searchFilterable from 'consul-ui/utils/search/filterable';
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
module('Unit | Utility | search/filterable', function() {
|
||||
// Replace this with your real tests.
|
||||
test('it works', function(assert) {
|
||||
let result = searchFilterable();
|
||||
assert.ok(result);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue