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 <FreetextFilter
@onsearch={{action onsearch}} @onsearch={{action onsearch}}
@value={{search}} @value={{search}}
@ -19,6 +22,7 @@
{{#let components.Optgroup components.Option as |Optgroup Option|}} {{#let components.Optgroup components.Option as |Optgroup Option|}}
<Option class="value-allow" @value="allow" @selected={{contains 'allow' filter.accesses}}>Allow</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-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}} {{/let}}
</BlockSlot> </BlockSlot>
</PopoverSelect> </PopoverSelect>

View File

@ -1,13 +1,13 @@
{{#if (gt items.length 0)}} {{#if (gt items.length 0)}}
<ListCollection @items={{items}} @linkable={{action "isLinkable"}} class="consul-nspace-list" as |item|> <ListCollection @items={{items}} @linkable={{action "isLinkable"}} class="consul-nspace-list" as |item|>
<BlockSlot @name="header"> <BlockSlot @name="header">
{{#if item.DeletedAt}} {{#if item.DeletedAt}}
<p> <p>
Deleting {{item.Name}}... Deleting {{item.Name}}...
</p> </p>
{{else}} {{else}}
<a data-test-nspace={{item.Name}} href={{href-to 'dc.nspaces.edit' item.Name}}>{{item.Name}}</a> <a data-test-nspace={{item.Name}} href={{href-to 'dc.nspaces.edit' item.Name}}>{{item.Name}}</a>
{{/if}} {{/if}}
</BlockSlot> </BlockSlot>
<BlockSlot @name="details"> <BlockSlot @name="details">
<dl> <dl>
@ -21,7 +21,7 @@
{{/if}} {{/if}}
</BlockSlot> </BlockSlot>
<BlockSlot @name="actions" as |Actions|> <BlockSlot @name="actions" as |Actions|>
{{#if (not item.DeletedAt)}} {{#if (not item.DeletedAt)}}
<Actions as |Action|> <Actions as |Action|>
<Action data-test-edit-action @href={{href-to 'dc.nspaces.edit' item.Name}}> <Action data-test-edit-action @href={{href-to 'dc.nspaces.edit' item.Name}}>
<BlockSlot @name="label"> <BlockSlot @name="label">
@ -51,7 +51,9 @@
</Action> </Action>
{{/if}} {{/if}}
</Actions> </Actions>
{{/if}} {{/if}}
</BlockSlot> </BlockSlot>
</ListCollection> </ListCollection>
{{else}}
{{yield to="empty"}}
{{/if}} {{/if}}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,10 @@
export default () => ({ statuses = [] }) => { import setHelpers from 'mnemonist/set';
return item => { import { andOr } from 'consul-ui/utils/filter';
if (statuses.length > 0 && !statuses.includes(item.Status)) {
return false; export default andOr({
} statuses: {
return true; 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'; import setHelpers from 'mnemonist/set';
export default () => ({ dcs = [], types = [] }) => { import { andOr } from 'consul-ui/utils/filter';
const typeIncludes = ['global-management', 'standard'].reduce((prev, item) => {
prev[item] = types.includes(item); export default andOr({
return prev; kinds: {
}, {}); 'global-management': (item, value) => item.isGlobalManagement,
const selectedDcs = new Set(dcs); standard: (item, value) => !item.isGlobalManagement,
return item => { },
let type = true; dcs: (item, values) => {
let dc = true; return (
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' || typeof item.Datacenters === 'undefined' ||
setHelpers.intersectionSize(selectedDcs, new Set(item.Datacenters)) > 0; setHelpers.intersectionSize(values, new Set(item.Datacenters)) > 0
} );
return type && dc; },
}; });
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -32,11 +32,12 @@
<BlockSlot @name="content"> <BlockSlot @name="content">
{{#let (sort-by (comparator 'nspace' sort) items) as |sorted|}} {{#let (sort-by (comparator 'nspace' sort) items) as |sorted|}}
<ChangeableSet @dispatcher={{searchable 'nspace' sorted}} @terms={{search}}> <ChangeableSet @dispatcher={{searchable 'nspace' sorted}} @terms={{search}}>
<BlockSlot @name="set" as |filtered|> <BlockSlot @name="content" as |filtered|>
<Consul::Nspace::List <Consul::Nspace::List
@items={{filtered}} @items={{filtered}}
@ondelete={{queue (action send 'delete')}} @ondelete={{queue (action send 'delete')}}
> >
<:empty>
<EmptyState @allowLogin={{true}}> <EmptyState @allowLogin={{true}}>
<BlockSlot @name="header"> <BlockSlot @name="header">
<h2> <h2>
@ -65,6 +66,7 @@
</li> </li>
</BlockSlot> </BlockSlot>
</EmptyState> </EmptyState>
</:empty>
</Consul::Nspace::List> </Consul::Nspace::List>
</BlockSlot> </BlockSlot>
</ChangeableSet> </ChangeableSet>

View File

@ -2,7 +2,7 @@
<EventSource @src={{items}} /> <EventSource @src={{items}} />
{{#let (hash {{#let (hash
statuses=(if status (split status ',') undefined) statuses=(if status (split status ',') undefined)
types=(if type (split type ',') undefined) kinds=(if kind (split kind ',') undefined)
sources=(if source (split source ',') undefined) sources=(if source (split source ',') undefined)
) as |filters|}} ) as |filters|}}
{{#let (or sortBy "Name:asc") as |sort|}} {{#let (or sortBy "Name:asc") as |sort|}}
@ -32,7 +32,7 @@
@filter={{filters}} @filter={{filters}}
@onfilter={{hash @onfilter={{hash
status=(action (mut status) value="target.selectedItems") 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") 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]]; expected = [items[0]];
actual = items.filter( actual = items.filter(
predicate({ predicate({
types: ['ingress-gateway'], kinds: ['ingress-gateway'],
}) })
); );
assert.deepEqual(actual, expected); assert.deepEqual(actual, expected);
@ -105,7 +105,7 @@ module('Unit | Filter | Predicates | service', function() {
expected = [items[1]]; expected = [items[1]];
actual = items.filter( actual = items.filter(
predicate({ predicate({
types: ['mesh-gateway'], kinds: ['mesh-gateway'],
}) })
); );
assert.deepEqual(actual, expected); assert.deepEqual(actual, expected);
@ -113,7 +113,7 @@ module('Unit | Filter | Predicates | service', function() {
expected = items; expected = items;
actual = items.filter( actual = items.filter(
predicate({ predicate({
types: ['ingress-gateway', 'mesh-gateway', 'service'], kinds: ['ingress-gateway', 'mesh-gateway', 'service'],
}) })
); );
assert.deepEqual(actual, expected); assert.deepEqual(actual, expected);
@ -141,7 +141,7 @@ module('Unit | Filter | Predicates | service', function() {
expected = [items[0]]; expected = [items[0]];
actual = items.filter( actual = items.filter(
predicate({ predicate({
types: ['ingress-gateway'], kinds: ['ingress-gateway'],
statuses: ['passing'], statuses: ['passing'],
instances: ['registered'], instances: ['registered'],
}) })
@ -151,7 +151,7 @@ module('Unit | Filter | Predicates | service', function() {
expected = [items[1]]; expected = [items[1]];
actual = items.filter( actual = items.filter(
predicate({ predicate({
types: ['mesh-gateway'], kinds: ['mesh-gateway'],
statuses: ['warning'], statuses: ['warning'],
instances: ['registered'], instances: ['registered'],
}) })
@ -161,7 +161,7 @@ module('Unit | Filter | Predicates | service', function() {
expected = items; expected = items;
actual = items.filter( actual = items.filter(
predicate({ predicate({
types: ['ingress-gateway', 'mesh-gateway', 'service'], kinds: ['ingress-gateway', 'mesh-gateway', 'service'],
statuses: ['passing', 'warning', 'critical'], statuses: ['passing', 'warning', 'critical'],
instances: ['registered', 'not-registered'], instances: ['registered', 'not-registered'],
}) })