ui: Fix text search for upstream instances (#10151)

* ui: Fix text search for upstream instances

* Clean up predicates for other model types

* Add some docs around DataCollection and searching

* Enable UI Engineering Docs for our preview sites

* Use debug CSS in dev and staging
This commit is contained in:
John Cowen 2021-05-04 17:25:57 +01:00 committed by hc-github-team-consul-core
parent 2d6f0d1854
commit 9ebc9561d9
13 changed files with 141 additions and 73 deletions

3
.changelog/10151.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
ui: Fix text searching through upstream instances.
```

View File

@ -1,25 +1,62 @@
---
class: ember
---
# DataCollection
```hbs
<DataCollection
@search={{''}}
@sort={{''}}
@filter={{hash
searchproperties=(array)
}}
@items={{array 'hi'}}
as |collection|>
{{collection.items.length}}
<collection.Collection>
Has Results
</collection.Collection>
<collection.Empty>
Is Empty
</collection.Empty>
</DataCollection>
The DataCollection component is the component we use for searching, filtering and sorting. It does not dictate any rendering of your data, but it does provide two different states/contextual components for rendering: when there are some results, when there are no results and also (by not using the contextual components) when the amount of results does not matter to what you want to render (say a count of number of results).
The component should be configured with the `@type` attribute, the component will use this to lookup 'specifications' for searching, sorting and filtering in the respective `app/search/predicates`, `app/sort/comparators` and `app/filter/predicates` folders. These specifications define _what_ is searched when you search a list of objects (for example searching Name could mean searching `model.Name` whereas searching Role could mean searching through an array of `item.RoleDefaults`).
The component can also be further configured by specifications for _how_ to search, for example we currently only use `Exact` searching (pretty much an `indexOf` search), but we also have `fuzzy` and `regexp` searching methods available (but disabled until needed). These search 'methods' will use the above specifications again to define _what_ to search. Searching methods (the _how_) are in `utils/search/{exact,fuzzy,regexp}.js` and any new ones should implement at least a single `search` method.
Lastly, a `SearchService` in `services/search.js` configures what is available for _what_ to search and _how_ to search, so if you need to add new models or search methods, that is where to look.
```hbs preview-template
<figure>
<figcaption>Provide a widget to search with</figcaption>
<input
oninput={{action (mut this.term) value="target.value"}}
type="text"
/>
</figure>
<figure>
<figcaption>Get some data to search on</figcaption>
<DataSource @src="/nspace/dc-1/services" as |source|>
<figure>
<figcaption>and show the complete set of data</figcaption>
{{#each source.data as |item|}}
{{item.Name}},
{{/each}}
</figure>
<hr />
<figure>
<figcaption>Use the component</figcaption>
<DataCollection
@type="service"
@search={{this.term}}
@sort={{'Name:asc'}}
@filter={{hash
searchproperties=(array 'Name')
}}
@items={{source.data}}
as |collection|>
Has {{collection.items.length}} results:<br />
<collection.Collection>
{{#each collection.items as |item|}}
{{item.Name}},
{{/each}}
</collection.Collection>
<collection.Empty>
Is Empty
</collection.Empty>
</DataCollection>
</figure>
</DataSource>
</figure>
```
### Arguments
@ -30,6 +67,8 @@ as |collection|>
| `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 |
| `type` | `String` | '' | The name of the specification describing what to search, filter, sort |
| `searchable` | `String` | `exact` | The name of the method to use for searching |
### Yields

View File

@ -2,12 +2,12 @@ const asArray = function(arr) {
return Array.isArray(arr) ? arr : arr.toArray();
};
export default {
Name: (item, value) => item.Name,
Node: (item, value) => item.Node,
Service: (item, value) => item.ServiceName,
CheckID: (item, value) => item.CheckID || '',
ID: (item, value) => item.Service.ID || '',
Notes: (item, value) => item.Notes,
Output: (item, value) => item.Output,
ServiceTags: (item, value) => asArray(item.ServiceTags || []),
Name: item => item.Name,
Node: item => item.Node,
Service: item => item.ServiceName,
CheckID: item => item.CheckID || '',
ID: item => item.Service.ID || '',
Notes: item => item.Notes,
Output: item => item.Output,
ServiceTags: item => asArray(item.ServiceTags || []),
};

View File

@ -1,11 +1,11 @@
export default {
Name: (item, value) => item.Name,
Description: (item, value) => item.Description,
Role: (item, value) => {
Name: item => item.Name,
Description: item => item.Description,
Role: item => {
const acls = item.ACLs || {};
return (acls.RoleDefaults || []).map(item => item.Name);
},
Policy: (item, value) => {
Policy: item => {
const acls = item.ACLs || {};
return (acls.PolicyDefaults || []).map(item => item.Name);
},

View File

@ -1,7 +1,7 @@
export default {
Name: (item, value) => item.Name,
Description: (item, value) => item.Description,
Policy: (item, value) => {
Name: item => item.Name,
Description: item => item.Description,
Policy: item => {
return (item.Policies || [])
.map(item => item.Name)
.concat((item.ServiceIdentities || []).map(item => item.ServiceName))

View File

@ -1,7 +1,7 @@
export default {
Name: item => item.Name,
Tags: item => item.Service.Tags || [],
ID: (item, value) => item.Service.ID || '',
ID: item => item.Service.ID || '',
Address: item => item.Address || '',
Port: item => (item.Service.Port || '').toString(),
['Service.Meta']: item =>

View File

@ -1,9 +1,9 @@
export default {
Name: (item, value) => item.Name,
Description: (item, value) => item.Description,
AccessorID: (item, value) => item.AccessorID,
Role: (item, value) => (item.Roles || []).map(item => item.Name),
Policy: (item, value) => {
Name: item => item.Name,
Description: item => item.Description,
AccessorID: item => item.AccessorID,
Role: item => (item.Roles || []).map(item => item.Name),
Policy: item => {
return (item.Policies || [])
.map(item => item.Name)
.concat((item.ServiceIdentities || []).map(item => item.ServiceName))

View File

@ -1,10 +1,5 @@
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,
DestinationName: (item, value) => item.DestinationName,
LocalBindAddress: (item, value) => item.LocalBindAddress,
LocalBindPort: (item, value) => item.LocalBindPort.toString(),
};

View File

@ -59,6 +59,19 @@
ul {
margin-bottom: 0;
}
figure {
margin-bottom: .5rem;
}
figcaption {
margin-bottom: .5rem;
color: var(--gray-400);
font-style: italic;
}
figure > [type=text] {
border: 1px solid var(--gray-999);
width: 100%;
padding: .5rem;
}
}
// &__snippets__tabs__button {
// display: none;

View File

@ -1,3 +1,7 @@
// All of the configuration here is shared between buildtime and runtime and
// is therefore added to ember's <meta> tag in the actual app, if the
// configuration is for buildtime only you should probably just use
// ember-cli-build to prevent values being outputted in the meta tag
'use strict';
const path = require('path');
const utils = require('./utils');

View File

@ -1,42 +1,58 @@
'use strict';
const Funnel = require('broccoli-funnel');
const EmberApp = require('ember-cli/lib/broccoli/ember-app');
module.exports = function(defaults) {
// available environments
// ['production', 'development', 'staging', 'test'];
const env = EmberApp.env();
const prodlike = ['production', 'staging'];
const isProd = env === 'production';
const isProdLike = prodlike.indexOf(env) > -1;
const sourcemaps = !isProd;
const trees = {};
const addons = {};
const outputPaths = {};
if (isProdLike) {
// exclude any component/pageobject.js files from production-like environments
trees.app = new Funnel('app', {
exclude: [
'components/**/pageobject.js',
'components/**/*.test-support.js',
'components/**/*.test.js',
// exclude our debug initializer, route and template
'instance-initializers/debug.js',
'templates/debug.hbs',
'components/debug/**/*.*'
],
});
// exclude any debug like addons from production-like environments
let excludeFiles = [];
const sourcemaps = !['production'].includes(env);
// setup up different build configuration depending on environment
if(!['test'].includes(env)) {
// exclude any component/pageobject.js files from anything but test
excludeFiles = excludeFiles.concat([
'components/**/pageobject.js',
'components/**/*.test-support.js',
'components/**/*.test.js',
])
}
if(['test', 'production'].includes(env)) {
// exclude our debug initializer, route and template
excludeFiles = excludeFiles.concat([
'instance-initializers/debug.js',
'templates/debug.hbs',
'components/debug/**/*.*'
])
// exclude any debug like addons from production or test environments
addons.blacklist = [
// exclude docfy
'@docfy/ember'
];
} else {
// add debug css is we are not in prodlike environments
// add debug css is we are not in test or production environments
outputPaths.app = {
css: {
'debug': '/assets/debug.css'
}
}
}
let app = new EmberApp(
//
trees.app = new Funnel('app', {
exclude: excludeFiles
});
const app = new EmberApp(
Object.assign({}, defaults, {
productionEnvironments: prodlike,
}),
@ -138,6 +154,5 @@ module.exports = function(defaults) {
app.import('vendor/init.js', {
outputFile: 'assets/init.js',
});
let tree = app.toTree();
return tree;
return app.toTree();
};

View File

@ -42,8 +42,7 @@ module.exports = {
treeFor: function(name) {
const tree = this._super.treeFor.apply(this, arguments);
if (name === 'app') {
const prodlike = ['production', 'staging'];
if (prodlike.includes(process.env.EMBER_ENV)) {
if (['production', 'test'].includes(process.env.EMBER_ENV)) {
return mergeTrees([tree, writeFile('components/debug/navigation/index.hbs', '')]);
}
}

View File

@ -8,7 +8,7 @@ module.exports = ({ appName, environment, rootURL, config }) => `
<link rel="icon" type="image/png" href="${rootURL}assets/favicon-16x16.png" sizes="16x16">
<link integrity="" rel="stylesheet" href="${rootURL}assets/vendor.css">
<link integrity="" rel="stylesheet" href="${rootURL}assets/${
environment === 'development' ? 'debug' : appName
!['test', 'production'].includes(environment) ? 'debug' : appName
}.css">
${
environment === 'test' ? `<link rel="stylesheet" href="${rootURL}assets/test-support.css">` : ``