ui: Search/sort improvements (#9183)

This commit is contained in:
John Cowen 2020-11-13 15:55:40 +00:00 committed by hashicorp-ci
parent a2e0246805
commit 8067b229e7
32 changed files with 287 additions and 290 deletions

View File

@ -1,4 +1,7 @@
<form class="consul-intention-search-bar filter-bar">
<form
class="consul-intention-search-bar filter-bar"
...attributes
>
<FreetextFilter
@onsearch={{action onsearch}}
@value={{search}}
@ -19,6 +22,7 @@
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option class="value-allow" @value="allow" @selected={{contains 'allow' filter.accesses}}>Allow</Option>
<Option class="value-deny" @value="deny" @selected={{contains 'deny' filter.accesses}}>Deny</Option>
<Option class="value-" @value="app-aware" @selected={{contains 'app-aware' filter.accesses}}>App aware</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>

View File

@ -1,57 +1,59 @@
{{#if (gt items.length 0)}}
<ListCollection @items={{items}} @linkable={{action "isLinkable"}} class="consul-nspace-list" as |item|>
<BlockSlot @name="header">
{{#if item.DeletedAt}}
<p>
Deleting {{item.Name}}...
</p>
{{else}}
<a data-test-nspace={{item.Name}} href={{href-to 'dc.nspaces.edit' item.Name}}>{{item.Name}}</a>
{{/if}}
</BlockSlot>
<BlockSlot @name="details">
<dl>
<dt>Description</dt>
<dd data-test-description>
{{item.Description}}
</dd>
</dl>
{{#if (env 'CONSUL_ACLS_ENABLED')}}
<Consul::Token::Ruleset::List @item={{item}} />
<ListCollection @items={{items}} @linkable={{action "isLinkable"}} class="consul-nspace-list" as |item|>
<BlockSlot @name="header">
{{#if item.DeletedAt}}
<p>
Deleting {{item.Name}}...
</p>
{{else}}
<a data-test-nspace={{item.Name}} href={{href-to 'dc.nspaces.edit' item.Name}}>{{item.Name}}</a>
{{/if}}
</BlockSlot>
<BlockSlot @name="actions" as |Actions|>
{{#if (not item.DeletedAt)}}
<Actions as |Action|>
<Action data-test-edit-action @href={{href-to 'dc.nspaces.edit' item.Name}}>
<BlockSlot @name="label">
Edit
</BlockSlot>
</Action>
{{#if (not-eq item.Name 'default') }}
<Action data-test-delete-action @onclick={{action ondelete item}} class="dangerous">
<BlockSlot @name="label">
Delete
</BlockSlot>
<BlockSlot @name="confirmation" as |Confirmation|>
<Confirmation class="warning">
<BlockSlot @name="header">
Confirm delete
</BlockSlot>
<BlockSlot @name="body">
<p>
Are you sure you want to delete this namespace?
</p>
</BlockSlot>
<BlockSlot @name="confirm" as |Confirm|>
<Confirm>Delete</Confirm>
</BlockSlot>
</Confirmation>
</BlockSlot>
</Action>
{{/if}}
</Actions>
{{/if}}
</BlockSlot>
</ListCollection>
</BlockSlot>
<BlockSlot @name="details">
<dl>
<dt>Description</dt>
<dd data-test-description>
{{item.Description}}
</dd>
</dl>
{{#if (env 'CONSUL_ACLS_ENABLED')}}
<Consul::Token::Ruleset::List @item={{item}} />
{{/if}}
</BlockSlot>
<BlockSlot @name="actions" as |Actions|>
{{#if (not item.DeletedAt)}}
<Actions as |Action|>
<Action data-test-edit-action @href={{href-to 'dc.nspaces.edit' item.Name}}>
<BlockSlot @name="label">
Edit
</BlockSlot>
</Action>
{{#if (not-eq item.Name 'default') }}
<Action data-test-delete-action @onclick={{action ondelete item}} class="dangerous">
<BlockSlot @name="label">
Delete
</BlockSlot>
<BlockSlot @name="confirmation" as |Confirmation|>
<Confirmation class="warning">
<BlockSlot @name="header">
Confirm delete
</BlockSlot>
<BlockSlot @name="body">
<p>
Are you sure you want to delete this namespace?
</p>
</BlockSlot>
<BlockSlot @name="confirm" as |Confirm|>
<Confirm>Delete</Confirm>
</BlockSlot>
</Confirmation>
</BlockSlot>
</Action>
{{/if}}
</Actions>
{{/if}}
</BlockSlot>
</ListCollection>
{{else}}
{{yield to="empty"}}
{{/if}}

View File

@ -28,7 +28,7 @@
<PopoverSelect
class="select-type"
@position="left"
@onchange={{action onfilter.type}}
@onchange={{action onfilter.kind}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
@ -38,8 +38,8 @@
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="global-management" @selected={{contains 'global-management' filter.types}}>Global Management</Option>
<Option @value="standard" @selected={{contains 'standard' filter.types}}>Standard</Option>
<Option @value="global-management" @selected={{contains 'global-management' filter.kinds}}>Global Management</Option>
<Option @value="standard" @selected={{contains 'standard' filter.kinds}}>Standard</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>

View File

@ -27,7 +27,7 @@
</PopoverSelect>
<PopoverSelect
@position="left"
@onchange={{action onfilter.type}}
@onchange={{action onfilter.kind}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
@ -37,15 +37,15 @@
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="service" @selected={{contains 'service' filter.types}}>Service</Option>
<Option @value="service" @selected={{contains 'service' filter.kinds}}>Service</Option>
<Optgroup @label="Gateway">
<Option @value="ingress-gateway" @selected={{contains 'ingress-gateway' filter.types}}>Ingress Gateway</Option>
<Option @value="terminating-gateway" @selected={{contains 'terminating-gateway' filter.types}}>Terminating Gateway</Option>
<Option @value="mesh-gateway" @selected={{contains 'mesh-gateway' filter.types}}>Mesh Gateway</Option>
<Option @value="ingress-gateway" @selected={{contains 'ingress-gateway' filter.kinds}}>Ingress Gateway</Option>
<Option @value="terminating-gateway" @selected={{contains 'terminating-gateway' filter.kinds}}>Terminating Gateway</Option>
<Option @value="mesh-gateway" @selected={{contains 'mesh-gateway' filter.kinds}}>Mesh Gateway</Option>
</Optgroup>
<Optgroup @label="Mesh">
<Option @value="in-mesh" @selected={{contains 'in-mesh' filter.types}}>In service mesh</Option>
<Option @value="not-in-mesh" @selected={{contains 'not-in-mesh' filter.types}}>Not in service mesh</Option>
<Option @value="in-mesh" @selected={{contains 'in-mesh' filter.kinds}}>In service mesh</Option>
<Option @value="not-in-mesh" @selected={{contains 'not-in-mesh' filter.kinds}}>Not in service mesh</Option>
</Optgroup>
{{/let}}
</BlockSlot>

View File

@ -7,7 +7,7 @@
<div class="filters">
<PopoverSelect
@position="left"
@onchange={{action onfilter.type}}
@onchange={{action onfilter.kind}}
@multiple={{true}}
as |components|>
<BlockSlot @name="selected">
@ -17,9 +17,9 @@
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option @value="global-management" @selected={{contains 'global-management' filter.types}}>Global Management</Option>
<Option @value="global" @selected={{contains 'global' filter.types}}>Global</Option>
<Option @value="local" @selected={{contains 'local' filter.types}}>Local</Option>
<Option @value="global-management" @selected={{contains 'global-management' filter.kinds}}>Global Management</Option>
<Option @value="global" @selected={{contains 'global' filter.kinds}}>Global</Option>
<Option @value="local" @selected={{contains 'local' filter.kinds}}>Local</Option>
{{/let}}
</BlockSlot>
</PopoverSelect>

View File

@ -1,9 +1,10 @@
import Controller from '@ember/controller';
export default class IndexController extends Controller {
queryParams = {
sortBy: 'sort',
dc: 'dc',
type: 'type',
kind: 'kind',
search: {
as: 'filter',
replace: true,

View File

@ -1,7 +1,9 @@
import Controller from '@ember/controller';
export default class IndexController extends Controller {
queryParams = {
sortBy: 'sort',
kind: 'kind',
search: {
as: 'filter',
replace: true,

View File

@ -1,6 +1,7 @@
import Controller from '@ember/controller';
export default class IndexController extends Controller {
queryParams = {
sortBy: 'sort',
search: {
as: 'filter',
replace: true,

View File

@ -6,9 +6,10 @@ export default class IndexController extends Controller {
sortBy: 'sort',
status: 'status',
source: 'source',
type: 'type',
kind: 'kind',
search: {
as: 'filter',
replace: true,
},
};

View File

@ -2,6 +2,7 @@ import Controller from '@ember/controller';
export default class IndexController extends Controller {
queryParams = {
sortBy: 'sort',
access: 'access',
search: {
as: 'filter',
replace: true,

View File

@ -1,9 +1,9 @@
export default () => ({ accesses = [] }) => item => {
if (accesses.length > 0) {
if (accesses.includes(item.Action)) {
return true;
}
return false;
}
return true;
};
import { andOr } from 'consul-ui/utils/filter';
export default andOr({
accesses: {
allow: (item, value) => item.Action === value,
deny: (item, value) => item.Action === value,
'app-aware': (item, value) => typeof item.Action === 'undefined',
},
});

View File

@ -1,8 +1,10 @@
export default () => ({ statuses = [] }) => {
return item => {
if (statuses.length > 0 && !statuses.includes(item.Status)) {
return false;
}
return true;
};
};
import setHelpers from 'mnemonist/set';
import { andOr } from 'consul-ui/utils/filter';
export default andOr({
statuses: {
passing: (item, value) => item.Status === value,
warning: (item, value) => item.Status === value,
critical: (item, value) => item.Status === value,
},
});

View File

@ -1,28 +1,15 @@
import setHelpers from 'mnemonist/set';
export default () => ({ dcs = [], types = [] }) => {
const typeIncludes = ['global-management', 'standard'].reduce((prev, item) => {
prev[item] = types.includes(item);
return prev;
}, {});
const selectedDcs = new Set(dcs);
return item => {
let type = true;
let dc = true;
if (types.length > 0) {
type = false;
if (typeIncludes['global-management'] && item.isGlobalManagement) {
type = true;
}
if (typeIncludes['standard'] && !item.isGlobalManagement) {
type = true;
}
}
if (dcs.length > 0) {
// if datacenters is undefined it means the policy is applicable to all datacenters
dc =
typeof item.Datacenters === 'undefined' ||
setHelpers.intersectionSize(selectedDcs, new Set(item.Datacenters)) > 0;
}
return type && dc;
};
};
import { andOr } from 'consul-ui/utils/filter';
export default andOr({
kinds: {
'global-management': (item, value) => item.isGlobalManagement,
standard: (item, value) => !item.isGlobalManagement,
},
dcs: (item, values) => {
return (
typeof item.Datacenters === 'undefined' ||
setHelpers.intersectionSize(values, new Set(item.Datacenters)) > 0
);
},
});

View File

@ -1,19 +1,13 @@
import setHelpers from 'mnemonist/set';
export default () => ({ sources = [], statuses = [] }) => {
const uniqueSources = new Set(sources);
return item => {
if (statuses.length > 0) {
if (statuses.includes(item.Status)) {
return true;
}
return false;
}
if (sources.length > 0) {
if (setHelpers.intersectionSize(uniqueSources, new Set(item.ExternalSources || [])) !== 0) {
return true;
}
return false;
}
return true;
};
};
import { andOr } from 'consul-ui/utils/filter';
export default andOr({
statuses: {
passing: (item, value) => item.Status === value,
warning: (item, value) => item.Status === value,
critical: (item, value) => item.Status === value,
},
sources: (item, values) => {
return setHelpers.intersectionSize(values, new Set(item.ExternalSources || [])) !== 0;
},
});

View File

@ -1,71 +1,25 @@
import setHelpers from 'mnemonist/set';
export default () => ({ instances = [], sources = [], statuses = [], types = [] }) => {
const uniqueSources = new Set(sources);
const typeIncludes = [
'ingress-gateway',
'terminating-gateway',
'mesh-gateway',
'service',
'in-mesh',
'not-in-mesh',
].reduce((prev, item) => {
prev[item] = types.includes(item);
return prev;
}, {});
const instanceIncludes = ['registered', 'not-registered'].reduce((prev, item) => {
prev[item] = instances.includes(item);
return prev;
}, {});
return item => {
if (statuses.length > 0) {
if (statuses.includes(item.MeshStatus)) {
return true;
}
return false;
}
if (instances.length > 0) {
if (item.InstanceCount > 0) {
if (instanceIncludes['registered']) {
return true;
}
} else {
if (instanceIncludes['not-registered']) {
return true;
}
}
return false;
}
if (types.length > 0) {
if (typeIncludes['ingress-gateway'] && item.Kind === 'ingress-gateway') {
return true;
}
if (typeIncludes['terminating-gateway'] && item.Kind === 'terminating-gateway') {
return true;
}
if (typeIncludes['mesh-gateway'] && item.Kind === 'mesh-gateway') {
return true;
}
if (typeIncludes['service'] && typeof item.Kind === 'undefined') {
return true;
}
if (typeIncludes['in-mesh']) {
if (item.InMesh) {
return true;
}
}
if (typeIncludes['not-in-mesh']) {
if (!item.InMesh) {
return true;
}
}
return false;
}
if (sources.length > 0) {
if (setHelpers.intersectionSize(uniqueSources, new Set(item.ExternalSources || [])) !== 0) {
return true;
}
return false;
}
return true;
};
};
import { andOr } from 'consul-ui/utils/filter';
export default andOr({
kinds: {
'ingress-gateway': (item, value) => item.Kind === value,
'terminating-gateway': (item, value) => item.Kind === value,
'mesh-gateway': (item, value) => item.Kind === value,
service: (item, value) => !item.Kind,
'in-mesh': (item, value) => item.InMesh,
'not-in-mesh': (item, value) => !item.InMesh,
},
statuses: {
passing: (item, value) => item.MeshStatus === value,
warning: (item, value) => item.MeshStatus === value,
critical: (item, value) => item.MeshStatus === value,
},
instances: {
registered: (item, value) => item.InstanceCount > 0,
'not-registered': (item, value) => item.InstanceCount === 0,
},
sources: (item, values) => {
return setHelpers.intersectionSize(values, new Set(item.ExternalSources || [])) !== 0;
},
});

View File

@ -1,21 +1,10 @@
export default () => ({ types = [] }) => {
const typeIncludes = ['global-management', 'global', 'local'].reduce((prev, item) => {
prev[item] = types.includes(item);
return prev;
}, {});
return item => {
if (types.length > 0) {
if (typeIncludes['global-management'] && item.isGlobalManagement) {
return true;
}
if (typeIncludes['global'] && !item.Local) {
return true;
}
if (typeIncludes['local'] && item.Local) {
return true;
}
return false;
}
return true;
};
};
import setHelpers from 'mnemonist/set';
import { andOr } from 'consul-ui/utils/filter';
export default andOr({
kinds: {
'global-management': (item, value) => item.isGlobalManagement,
global: (item, value) => !item.Local,
local: (item, value) => item.Local,
},
});

View File

@ -5,11 +5,12 @@ import { hash } from 'rsvp';
import WithPolicyActions from 'consul-ui/mixins/policy/with-actions';
export default class IndexRoute extends Route.extend(WithPolicyActions) {
@service('repository/policy')
repo;
@service('repository/policy') repo;
queryParams = {
sortBy: 'sort',
dc: 'dc',
kind: 'kind',
search: {
as: 'filter',
replace: true,

View File

@ -5,8 +5,7 @@ import { hash } from 'rsvp';
import WithRoleActions from 'consul-ui/mixins/role/with-actions';
export default class IndexRoute extends Route.extend(WithRoleActions) {
@service('repository/role')
repo;
@service('repository/role') repo;
queryParams = {
sortBy: 'sort',

View File

@ -3,15 +3,14 @@ import Route from 'consul-ui/routing/route';
import { hash } from 'rsvp';
import { get } from '@ember/object';
import WithTokenActions from 'consul-ui/mixins/token/with-actions';
export default class IndexRoute extends Route.extend(WithTokenActions) {
@service('repository/token')
repo;
@service('settings')
settings;
export default class IndexRoute extends Route.extend(WithTokenActions) {
@service('repository/token') repo;
@service('settings') settings;
queryParams = {
sortBy: 'sort',
kind: 'kind',
search: {
as: 'filter',
replace: true,

View File

@ -3,6 +3,7 @@ import Route from 'consul-ui/routing/route';
export default class IndexRoute extends Route {
queryParams = {
sortBy: 'sort',
access: 'access',
search: {
as: 'filter',
replace: true,

View File

@ -5,16 +5,16 @@ import { get, action } from '@ember/object';
import isFolder from 'consul-ui/utils/isFolder';
export default class IndexRoute extends Route {
@service('repository/kv') repo;
queryParams = {
sortBy: 'sort',
search: {
as: 'filter',
replace: true,
},
};
@service('repository/kv')
repo;
beforeModel() {
// we are index or folder, so if the key doesn't have a trailing slash
// add one to force a fake findBySlug

View File

@ -3,11 +3,11 @@ import Route from 'consul-ui/routing/route';
import { hash } from 'rsvp';
export default class IndexRoute extends Route {
@service('data-source/service')
data;
@service('data-source/service') data;
queryParams = {
sortBy: 'sort',
status: 'status',
search: {
as: 'filter',
replace: true,

View File

@ -4,11 +4,8 @@ import { hash } from 'rsvp';
import WithNspaceActions from 'consul-ui/mixins/nspace/with-actions';
export default class IndexRoute extends Route.extend(WithNspaceActions) {
@service('data-source/service')
data;
@service('repository/nspace')
repo;
@service('data-source/service') data;
@service('repository/nspace') repo;
queryParams = {
sortBy: 'sort',

View File

@ -3,18 +3,17 @@ import Route from 'consul-ui/routing/route';
import { hash } from 'rsvp';
export default class IndexRoute extends Route {
@service('data-source/service')
data;
@service('data-source/service') data;
queryParams = {
sortBy: 'sort',
status: 'status',
source: 'source',
kind: 'kind',
search: {
as: 'filter',
replace: true,
},
// temporary support of old style status
status: {
as: 'status',
},
};
model(params) {

View File

@ -2,6 +2,9 @@ import Route from 'consul-ui/routing/route';
export default class InstancesRoute extends Route {
queryParams = {
sortBy: 'sort',
status: 'status',
source: 'source',
search: {
as: 'filter',
replace: true,

View File

@ -2,6 +2,8 @@ import Route from 'consul-ui/routing/route';
export default class IndexRoute extends Route {
queryParams = {
sortBy: 'sort',
access: 'access',
search: {
as: 'filter',
replace: true,

View File

@ -4,7 +4,7 @@
{{title 'Access Controls'}}
{{/if}}
{{#let (hash
types=(if type (split type ',') undefined)
kinds=(if kind (split kind ',') undefined)
dcs=(if dc (split dc ',') undefined)
) as |filters|}}
{{#let (or sortBy "Name:asc") as |sort|}}
@ -45,7 +45,7 @@
@filter={{filters}}
@onfilter={{hash
dc=(action (mut dc) value="target.selectedItems")
type=(action (mut type) value="target.selectedItems")
kind=(action (mut kind) value="target.selectedItems")
}}
/>
{{/if}}

View File

@ -5,7 +5,7 @@
{{/if}}
{{#let (hash
types=(if type (split type ',') undefined)
kinds=(if kind (split kind ',') undefined)
) as |filters|}}
{{#let (or sortBy "CreateTime:desc") as |sort|}}
<AppView
@ -44,7 +44,7 @@
@filter={{filters}}
@onfilter={{hash
type=(action (mut type) value="target.selectedItems")
kind=(action (mut kind) value="target.selectedItems")
}}
/>
{{/if}}

View File

@ -32,39 +32,41 @@
<BlockSlot @name="content">
{{#let (sort-by (comparator 'nspace' sort) items) as |sorted|}}
<ChangeableSet @dispatcher={{searchable 'nspace' sorted}} @terms={{search}}>
<BlockSlot @name="set" as |filtered|>
<BlockSlot @name="content" as |filtered|>
<Consul::Nspace::List
@items={{filtered}}
@ondelete={{queue (action send 'delete')}}
>
<EmptyState @allowLogin={{true}}>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No namespaces found
{{else}}
Welcome to Namespaces
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#if (gt items.length 0)}}
No namespaces where found matching that search, or you may not have access to view the namespaces you are searching for.
{{else}}
There don't seem to be any namespaces, or you may not have access to view namespaces yet.
{{/if}}
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/commands/namespace" rel="noopener noreferrer" target="_blank">Documentation on namespaces</a>
</li>
<li class="learn-link">
<a href="{{env 'CONSUL_DOCS_LEARN_URL'}}/consul/namespaces/secure-namespaces" rel="noopener noreferrer" target="_blank">Read the guide</a>
</li>
</BlockSlot>
</EmptyState>
<:empty>
<EmptyState @allowLogin={{true}}>
<BlockSlot @name="header">
<h2>
{{#if (gt items.length 0)}}
No namespaces found
{{else}}
Welcome to Namespaces
{{/if}}
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
{{#if (gt items.length 0)}}
No namespaces where found matching that search, or you may not have access to view the namespaces you are searching for.
{{else}}
There don't seem to be any namespaces, or you may not have access to view namespaces yet.
{{/if}}
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/commands/namespace" rel="noopener noreferrer" target="_blank">Documentation on namespaces</a>
</li>
<li class="learn-link">
<a href="{{env 'CONSUL_DOCS_LEARN_URL'}}/consul/namespaces/secure-namespaces" rel="noopener noreferrer" target="_blank">Read the guide</a>
</li>
</BlockSlot>
</EmptyState>
</:empty>
</Consul::Nspace::List>
</BlockSlot>
</ChangeableSet>

View File

@ -2,7 +2,7 @@
<EventSource @src={{items}} />
{{#let (hash
statuses=(if status (split status ',') undefined)
types=(if type (split type ',') undefined)
kinds=(if kind (split kind ',') undefined)
sources=(if source (split source ',') undefined)
) as |filters|}}
{{#let (or sortBy "Name:asc") as |sort|}}
@ -32,7 +32,7 @@
@filter={{filters}}
@onfilter={{hash
status=(action (mut status) value="target.selectedItems")
type=(action (mut type) value="target.selectedItems")
kind=(action (mut kind) value="target.selectedItems")
source=(action (mut source) value="target.selectedItems")
}}
/>

View File

@ -0,0 +1,56 @@
import setHelpers from 'mnemonist/set';
const createPossibles = function(predicates) {
// create arrays of allowed values
return Object.entries(predicates).reduce((prev, [key, value]) => {
if (typeof value !== 'function') {
prev[key] = new Set(Object.keys(value));
} else {
prev[key] = null;
}
return prev;
}, {});
};
const sanitize = function(values, possibles) {
return Object.keys(possibles).reduce((prev, key) => {
// only set the value if the value has a length of > 0
const value = typeof values[key] === 'undefined' ? [] : values[key];
if (value.length > 0) {
if (possibles[key] !== null) {
// only include possible values
prev[key] = [...setHelpers.intersection(possibles[key], new Set(value))];
} else {
// only unique values
prev[key] = [...new Set(value)];
}
}
return prev;
}, {});
};
const execute = function(item, values, predicates) {
// every/and the top level values
return Object.entries(values).every(([key, values]) => {
let predicate = predicates[key];
if (typeof predicate === 'function') {
return predicate(item, values);
} else {
// if the top level values can have multiple values some/or them
return values.some(val => predicate[val](item, val));
}
});
};
// exports a function that requires a hash of predicates passed in
export const andOr = predicates => {
// figure out all possible values from the hash of predicates
const possibles = createPossibles(predicates);
return () => values => {
// this is what is called post injection
// the actual user values are passed in here so 'sanitize' them which is
// basically checking against the possibles
values = sanitize(values, possibles);
// this is your actual filter predicate
return item => {
return execute(item, values, predicates);
};
};
};

View File

@ -97,7 +97,7 @@ module('Unit | Filter | Predicates | service', function() {
expected = [items[0]];
actual = items.filter(
predicate({
types: ['ingress-gateway'],
kinds: ['ingress-gateway'],
})
);
assert.deepEqual(actual, expected);
@ -105,7 +105,7 @@ module('Unit | Filter | Predicates | service', function() {
expected = [items[1]];
actual = items.filter(
predicate({
types: ['mesh-gateway'],
kinds: ['mesh-gateway'],
})
);
assert.deepEqual(actual, expected);
@ -113,7 +113,7 @@ module('Unit | Filter | Predicates | service', function() {
expected = items;
actual = items.filter(
predicate({
types: ['ingress-gateway', 'mesh-gateway', 'service'],
kinds: ['ingress-gateway', 'mesh-gateway', 'service'],
})
);
assert.deepEqual(actual, expected);
@ -141,7 +141,7 @@ module('Unit | Filter | Predicates | service', function() {
expected = [items[0]];
actual = items.filter(
predicate({
types: ['ingress-gateway'],
kinds: ['ingress-gateway'],
statuses: ['passing'],
instances: ['registered'],
})
@ -151,7 +151,7 @@ module('Unit | Filter | Predicates | service', function() {
expected = [items[1]];
actual = items.filter(
predicate({
types: ['mesh-gateway'],
kinds: ['mesh-gateway'],
statuses: ['warning'],
instances: ['registered'],
})
@ -161,7 +161,7 @@ module('Unit | Filter | Predicates | service', function() {
expected = items;
actual = items.filter(
predicate({
types: ['ingress-gateway', 'mesh-gateway', 'service'],
kinds: ['ingress-gateway', 'mesh-gateway', 'service'],
statuses: ['passing', 'warning', 'critical'],
instances: ['registered', 'not-registered'],
})