diff --git a/ui/packages/consul-ui/app/components/consul/intention/form/index.hbs b/ui/packages/consul-ui/app/components/consul/intention/form/index.hbs index f6fffd22f..e6907a9c7 100644 --- a/ui/packages/consul-ui/app/components/consul/intention/form/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/intention/form/index.hbs @@ -1,14 +1,14 @@ + @dc={{@dc}} + @nspace={{@nspace}} + @autofill={{@autofill}} + @item={{@item}} + @src={{@src}} + @onchange={{action this.change}} + @onsubmit={{action this.onsubmit}} +as |api|> +
- - + + {{#if (not api.isCreate)}} {{#if (not-eq item.ID 'anonymous') }} - + - + {{/if}} @@ -71,10 +97,31 @@
{{else}} - + {{#if item.IsManagedByCRD}} + + +

+ Intention Custom Resource +

+
+ +

+ This Intention is view only because it is managed through an Intention Custom Resource in your Kubernetes cluster. +

+

+ Learn more about CRDs +

+
+
+ {{/if}} + {{/if}} {{/let}} +
diff --git a/ui/packages/consul-ui/app/components/consul/intention/form/index.js b/ui/packages/consul-ui/app/components/consul/intention/form/index.js index faf8dd3b8..c2d8b2d06 100644 --- a/ui/packages/consul-ui/app/components/consul/intention/form/index.js +++ b/ui/packages/consul-ui/app/components/consul/intention/form/index.js @@ -1,112 +1,150 @@ -import Component from '@ember/component'; -import { setProperties, set, get } from '@ember/object'; +import Component from '@glimmer/component'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; +import { tracked } from '@glimmer/tracking'; -export default Component.extend({ - tagName: '', - ondelete: function() { - this.onsubmit(...arguments); - }, - oncancel: function() { - this.onsubmit(...arguments); - }, - onsubmit: function() {}, - actions: { - createServices: function(item, e) { - // Services in the menus should: - // 1. Be unique (they potentially could be duplicated due to services from different namespaces) - // 2. Only include services that shold have intentions - // 3. Include an 'All Services' option - // 4. Include the current Source and Destination incase they are virtual services/don't exist yet - let items = e.data - .uniqBy('Name') - .toArray() - .filter( - item => !['connect-proxy', 'mesh-gateway', 'terminating-gateway'].includes(item.Kind) - ) - .sort((a, b) => a.Name.localeCompare(b.Name)); - items = [{ Name: '*' }].concat(items); - let source = items.findBy('Name', item.SourceName); - if (!source) { - source = { Name: item.SourceName }; - items = [source].concat(items); - } - let destination = items.findBy('Name', item.DestinationName); - if (!destination) { - destination = { Name: item.DestinationName }; - items = [destination].concat(items); - } - setProperties(this, { - services: items, - SourceName: source, - DestinationName: destination, - }); - }, - createNspaces: function(item, e) { - // Nspaces in the menus should: - // 1. Include an 'All Namespaces' option - // 2. Include the current SourceNS and DestinationNS incase they don't exist yet - let items = e.data.toArray().sort((a, b) => a.Name.localeCompare(b.Name)); - items = [{ Name: '*' }].concat(items); - let source = items.findBy('Name', item.SourceNS); - if (!source) { - source = { Name: item.SourceNS }; - items = [source].concat(items); - } - let destination = items.findBy('Name', item.DestinationNS); - if (!destination) { - destination = { Name: item.DestinationNS }; - items = [destination].concat(items); - } - setProperties(this, { - nspaces: items, - SourceNS: source, - DestinationNS: destination, - }); - }, - change: function(e, form, item) { - const target = e.target; +export default class ConsulIntentionForm extends Component { - let name, selected, match; - switch (target.name) { - case 'SourceName': - case 'DestinationName': - case 'SourceNS': - case 'DestinationNS': - name = selected = target.value; - // Names can be selected Service EmberObjects or typed in strings - // if its not a string, use the `Name` from the Service EmberObject - if (typeof name !== 'string') { - name = get(target.value, 'Name'); + @tracked services; + @tracked SourceName; + @tracked DestinationName; + + @tracked nspaces; + @tracked SourceNS; + @tracked DestinationNS; + + @tracked isManagedByCRDs; + + @service('repository/intention') repo; + + constructor(owner, args) { + super(...arguments); + this.updateCRDManagement(); + } + + ondelete() { + if(this.args.ondelete) { + this.args.ondelete(...arguments); + } else { + this.onsubmit(...arguments); + } + } + + oncancel() { + if(this.args.oncancel) { + this.args.oncancel(...arguments); + } else { + this.onsubmit(...arguments); + } + } + + onsubmit() { + if(this.args.onsubmit) { + this.args.onsubmit(...arguments); + } + } + + @action + updateCRDManagement() { + this.isManagedByCRDs = this.repo.isManagedByCRDs(); + } + + @action + createServices (item, e) { + // Services in the menus should: + // 1. Be unique (they potentially could be duplicated due to services from different namespaces) + // 2. Only include services that shold have intentions + // 3. Include an 'All Services' option + // 4. Include the current Source and Destination incase they are virtual services/don't exist yet + let items = e.data + .uniqBy('Name') + .toArray() + .filter( + item => !['connect-proxy', 'mesh-gateway', 'terminating-gateway'].includes(item.Kind) + ) + .sort((a, b) => a.Name.localeCompare(b.Name)); + items = [{ Name: '*' }].concat(items); + let source = items.findBy('Name', item.SourceName); + if (!source) { + source = { Name: item.SourceName }; + items = [source].concat(items); + } + let destination = items.findBy('Name', item.DestinationName); + if (!destination) { + destination = { Name: item.DestinationName }; + items = [destination].concat(items); + } + this.services = items; + this.SourceName = source; + this.DestinationName = destination; + } + + @action + createNspaces (item, e) { + // Nspaces in the menus should: + // 1. Include an 'All Namespaces' option + // 2. Include the current SourceNS and DestinationNS incase they don't exist yet + let items = e.data.toArray().sort((a, b) => a.Name.localeCompare(b.Name)); + items = [{ Name: '*' }].concat(items); + let source = items.findBy('Name', item.SourceNS); + if (!source) { + source = { Name: item.SourceNS }; + items = [source].concat(items); + } + let destination = items.findBy('Name', item.DestinationNS); + if (!destination) { + destination = { Name: item.DestinationNS }; + items = [destination].concat(items); + } + this.nspaces = items; + this.SourceNS = source; + this.DestinationNS = destination; + } + + @action + change(e, form, item) { + const target = e.target; + + let name, selected, match; + switch (target.name) { + case 'SourceName': + case 'DestinationName': + case 'SourceNS': + case 'DestinationNS': + name = selected = target.value; + // Names can be selected Service EmberObjects or typed in strings + // if its not a string, use the `Name` from the Service EmberObject + if (typeof name !== 'string') { + name = target.value.Name; + } + // mutate the value with the string name + // which will be handled by the form + target.value = name; + // these are 'non-form' variables so not on `item` + // these variables also exist in the template so we know + // the current selection + // basically the difference between + // `item.DestinationName` and just `DestinationName` + // see if the name is already in the list + match = this.services.filterBy('Name', name); + if (match.length === 0) { + // if its not make a new 'fake' Service that doesn't exist yet + // and add it to the possible services to make an intention between + selected = { Name: name }; + switch (target.name) { + case 'SourceName': + case 'DestinationName': + this.services = [selected].concat(this.services.toArray()); + break; + case 'SourceNS': + case 'DestinationNS': + this.nspaces = [selected].concat(this.nspaces.toArray()); + break; } - // mutate the value with the string name - // which will be handled by the form - target.value = name; - // these are 'non-form' variables so not on `item` - // these variables also exist in the template so we know - // the current selection - // basically the difference between - // `item.DestinationName` and just `DestinationName` - // see if the name is already in the list - match = this.services.filterBy('Name', name); - if (match.length === 0) { - // if its not make a new 'fake' Service that doesn't exist yet - // and add it to the possible services to make an intention between - selected = { Name: name }; - switch (target.name) { - case 'SourceName': - case 'DestinationName': - set(this, 'services', [selected].concat(this.services.toArray())); - break; - case 'SourceNS': - case 'DestinationNS': - set(this, 'nspaces', [selected].concat(this.nspaces.toArray())); - break; - } - } - set(this, target.name, selected); - break; - } - form.handleEvent(e); - }, - }, -}); + } + this[target.name] = selected; + break; + } + form.handleEvent(e); + } +} diff --git a/ui/packages/consul-ui/app/components/consul/intention/list/index.hbs b/ui/packages/consul-ui/app/components/consul/intention/list/index.hbs index 8ace0370b..92a43a603 100644 --- a/ui/packages/consul-ui/app/components/consul/intention/list/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/intention/list/index.hbs @@ -1,6 +1,7 @@
{{#let (hash - Check=(component 'consul/intention/list/check') - Table=(component 'consul/intention/list/table' delete=writer.delete items=@items) + Table=(component 'consul/intention/list/table' delete=writer.delete items=this.items) + CheckNotice=(if this.checkedItem + (component 'consul/intention/list/check' item=this.checkedItem) + '' + ) + CustomResourceNotice=(if this.isManagedByCRDs + (component 'consul/intention/notice/custom-resource') + '' + ) ) as |api|}} - {{#if (gt @items.length 0)}} + {{#if (gt this.items.length 0)}} {{yield api to="idle"}} {{else}} {{yield api to="empty"}} {{/if}} - {{/let}} diff --git a/ui/packages/consul-ui/app/components/consul/intention/list/index.js b/ui/packages/consul-ui/app/components/consul/intention/list/index.js new file mode 100644 index 000000000..167ea1730 --- /dev/null +++ b/ui/packages/consul-ui/app/components/consul/intention/list/index.js @@ -0,0 +1,49 @@ +import Component from '@glimmer/component'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; +import { tracked } from '@glimmer/tracking'; +import { sort } from '@ember/object/computed'; + +export default class ConsulIntentionList extends Component { + + @service('filter') filter; + @service('sort') sort; + @service('search') search; + @service('repository/intention') repo; + + @sort('searched', 'comparator') sorted; + + @tracked isManagedByCRDs; + + constructor(owner, args) { + super(...arguments); + this.updateCRDManagement(args.items); + } + get items() { + return this.sorted; + } + get filtered() { + const predicate = this.filter.predicate('intention'); + return this.args.items.filter(predicate(this.args.filters)) + } + get searched() { + if(typeof this.args.search === 'undefined') { + return this.filtered; + } + const predicate = this.search.predicate('intention'); + return this.filtered.filter(predicate(this.args.search)); + } + get comparator() { + return [this.args.sort]; + } + get checkedItem() { + if(this.searched.length === 1) { + return this.searched[0].SourceName === this.args.search ? this.searched[0] : null; + } + return null; + } + @action + updateCRDManagement() { + this.isManagedByCRDs = this.repo.isManagedByCRDs(); + } +} diff --git a/ui/packages/consul-ui/app/components/consul/intention/list/pageobject.js b/ui/packages/consul-ui/app/components/consul/intention/list/pageobject.js index 59844bcee..3159a09f5 100644 --- a/ui/packages/consul-ui/app/components/consul/intention/list/pageobject.js +++ b/ui/packages/consul-ui/app/components/consul/intention/list/pageobject.js @@ -1,10 +1,15 @@ -export default (collection, clickable, attribute, deletable) => () => { - return collection('.consul-intention-list [data-test-tabular-row]', { +export default (collection, clickable, attribute, isPresent, deletable) => (scope = '.consul-intention-list') => { + const row = { source: attribute('data-test-intention-source', '[data-test-intention-source]'), destination: attribute('data-test-intention-destination', '[data-test-intention-destination]'), action: attribute('data-test-intention-action', '[data-test-intention-action]'), intention: clickable('a'), actions: clickable('label'), ...deletable(), - }); + }; + return { + scope: scope, + customResourceNotice: isPresent('.consul-intention-notice-custom-resource'), + intentions: collection('[data-test-tabular-row]', row) + } }; diff --git a/ui/packages/consul-ui/app/components/consul/intention/notice/custom-resource/index.hbs b/ui/packages/consul-ui/app/components/consul/intention/notice/custom-resource/index.hbs new file mode 100644 index 000000000..a79a57cce --- /dev/null +++ b/ui/packages/consul-ui/app/components/consul/intention/notice/custom-resource/index.hbs @@ -0,0 +1,19 @@ + + +

+ Intention Custom Resource +

+
+ +

+ Some of your intentions are being managed through an Intention Custom Resource in your Kubernetes cluster. Those managed intentions will be view only in the UI. Any intentions created in the UI will work but will not be synced to the Custom Resource Definition (CRD) datastore. +

+

+ Learn more about CRDs +

+
+
\ No newline at end of file diff --git a/ui/packages/consul-ui/app/initializers/search.js b/ui/packages/consul-ui/app/initializers/search.js deleted file mode 100644 index e6ed797ab..000000000 --- a/ui/packages/consul-ui/app/initializers/search.js +++ /dev/null @@ -1,41 +0,0 @@ -import intention from 'consul-ui/search/filters/intention'; -import token from 'consul-ui/search/filters/token'; -import policy from 'consul-ui/search/filters/policy'; -import role from 'consul-ui/search/filters/role'; -import kv from 'consul-ui/search/filters/kv'; -import acl from 'consul-ui/search/filters/acl'; -import node from 'consul-ui/search/filters/node'; -// service instance -import nodeService from 'consul-ui/search/filters/node/service'; -import serviceNode from 'consul-ui/search/filters/service/node'; -import service from 'consul-ui/search/filters/service'; -import nspace from 'consul-ui/search/filters/nspace'; - -import filterableFactory from 'consul-ui/utils/search/filterable'; -const filterable = filterableFactory(); -export function initialize(application) { - // Service-less injection using private properties at a per-project level - const Builder = application.resolveRegistration('service:search'); - const searchables = { - intention: intention(filterable), - token: token(filterable), - acl: acl(filterable), - policy: policy(filterable), - role: role(filterable), - kv: kv(filterable), - node: node(filterable), - serviceInstance: serviceNode(filterable), - nodeservice: nodeService(filterable), - service: service(filterable), - nspace: nspace(filterable), - }; - Builder.reopen({ - searchable: function(name) { - return searchables[name]; - }, - }); -} - -export default { - initialize, -}; diff --git a/ui/packages/consul-ui/app/search/filters/intention.js b/ui/packages/consul-ui/app/search/filters/intention.js deleted file mode 100644 index cf71a6d86..000000000 --- a/ui/packages/consul-ui/app/search/filters/intention.js +++ /dev/null @@ -1,15 +0,0 @@ -import { get } from '@ember/object'; -export default function(filterable) { - return filterable(function(item, { s = '' }) { - const source = get(item, 'SourceName').toLowerCase(); - const destination = get(item, 'DestinationName').toLowerCase(); - const sLower = s.toLowerCase(); - const allLabel = 'All Services (*)'.toLowerCase(); - return ( - source.indexOf(sLower) !== -1 || - destination.indexOf(sLower) !== -1 || - (source === '*' && allLabel.indexOf(sLower) !== -1) || - (destination === '*' && allLabel.indexOf(sLower) !== -1) - ); - }); -} diff --git a/ui/packages/consul-ui/app/search/predicates/intention.js b/ui/packages/consul-ui/app/search/predicates/intention.js new file mode 100644 index 000000000..43fa80528 --- /dev/null +++ b/ui/packages/consul-ui/app/search/predicates/intention.js @@ -0,0 +1,12 @@ +export default () => (term) => (item) => { + const source = item.SourceName.toLowerCase(); + const destination = item.DestinationName.toLowerCase(); + const allLabel = 'All Services (*)'.toLowerCase(); + const lowerTerm = term.toLowerCase(); + return ( + source.indexOf(lowerTerm) !== -1 || + destination.indexOf(lowerTerm) !== -1 || + (source === '*' && allLabel.indexOf(lowerTerm) !== -1) || + (destination === '*' && allLabel.indexOf(lowerTerm) !== -1) + ); +} diff --git a/ui/packages/consul-ui/app/services/repository/intention.js b/ui/packages/consul-ui/app/services/repository/intention.js index 3e1033571..1c4d627dd 100644 --- a/ui/packages/consul-ui/app/services/repository/intention.js +++ b/ui/packages/consul-ui/app/services/repository/intention.js @@ -1,46 +1,59 @@ import { set, get } from '@ember/object'; import RepositoryService from 'consul-ui/services/repository'; import { PRIMARY_KEY } from 'consul-ui/models/intention'; + const modelName = 'intention'; -export default RepositoryService.extend({ - getModelName: function() { +export default class IntentionRepository extends RepositoryService { + + managedByCRDs = false; + + getModelName() { return modelName; - }, - getPrimaryKey: function() { + } + + getPrimaryKey() { return PRIMARY_KEY; - }, - create: function(obj) { + } + + create(obj) { delete obj.Namespace; - return this._super({ + return super.create({ Action: 'allow', ...obj, }); - }, - persist: function(obj) { - return this._super(...arguments).then(res => { - // if Action is set it means we are an l4 type intention - // we don't delete these at a UI level incase the user - // would like to switch backwards and forwards between - // allow/deny/l7 in the forms, but once its been saved - // to the backend we then delete them - if (get(res, 'Action.length')) { - set(res, 'Permissions', []); - } - return res; - }); - }, - findByService: function(slug, dc, nspace, configuration = {}) { + } + + isManagedByCRDs() { + if(!this.managedByCRDs) { + this.managedByCRDs = this.store.peekAll(this.getModelName()) + .toArray().some(item => item.IsManagedByCRD); + } + return this.managedByCRDs; + } + + async persist(obj) { + const res = await super.persist(...arguments); + // if Action is set it means we are an l4 type intention + // we don't delete these at a UI level incase the user + // would like to switch backwards and forwards between + // allow/deny/l7 in the forms, but once its been saved + // to the backend we then delete them + if (get(res, 'Action.length')) { + set(res, 'Permissions', []); + } + return res; + } + + async findByService(slug, dc, nspace, configuration = {}) { const query = { - dc: dc, - nspace: nspace, + dc, + nspace, filter: `SourceName == "${slug}" or DestinationName == "${slug}" or SourceName == "*" or DestinationName == "*"`, }; if (typeof configuration.cursor !== 'undefined') { query.index = configuration.cursor; query.uri = configuration.uri; } - return this.store.query(this.getModelName(), { - ...query, - }); - }, -}); + return this.store.query(this.getModelName(), query); + } +} diff --git a/ui/packages/consul-ui/app/services/search.js b/ui/packages/consul-ui/app/services/search.js index 5962a6085..1f807725b 100644 --- a/ui/packages/consul-ui/app/services/search.js +++ b/ui/packages/consul-ui/app/services/search.js @@ -1,9 +1,40 @@ import Service from '@ember/service'; -export default Service.extend({ - searchable: function() { - return { - addEventListener: function() {}, - removeEventListener: function() {}, - }; - }, -}); + +import intention from 'consul-ui/search/predicates/intention'; +import token from 'consul-ui/search/filters/token'; +import policy from 'consul-ui/search/filters/policy'; +import role from 'consul-ui/search/filters/role'; +import kv from 'consul-ui/search/filters/kv'; +import acl from 'consul-ui/search/filters/acl'; +import node from 'consul-ui/search/filters/node'; +// service instance +import nodeService from 'consul-ui/search/filters/node/service'; +import serviceNode from 'consul-ui/search/filters/service/node'; +import service from 'consul-ui/search/filters/service'; +import nspace from 'consul-ui/search/filters/nspace'; + +import filterableFactory from 'consul-ui/utils/search/filterable'; +const filterable = filterableFactory(); +const searchables = { + token: token(filterable), + acl: acl(filterable), + policy: policy(filterable), + role: role(filterable), + kv: kv(filterable), + node: node(filterable), + serviceInstance: serviceNode(filterable), + nodeservice: nodeService(filterable), + service: service(filterable), + nspace: nspace(filterable), +}; +const predicates = { + intention: intention(), +}; +export default class SearchService extends Service { + searchable(name) { + return searchables[name]; + } + predicate(name) { + return predicates[name]; + } +} diff --git a/ui/packages/consul-ui/app/styles/base/components/notice/skin.scss b/ui/packages/consul-ui/app/styles/base/components/notice/skin.scss index fbde9161a..049f42556 100644 --- a/ui/packages/consul-ui/app/styles/base/components/notice/skin.scss +++ b/ui/packages/consul-ui/app/styles/base/components/notice/skin.scss @@ -1,6 +1,7 @@ %notice { border-radius: $decor-radius-100; border: 1px solid; + color: $black; } %notice p:last-child a:only-child { @extend %p3; @@ -22,7 +23,6 @@ %notice-info { border-color: $blue-100; background-color: $gray-010; - color: $black; } %notice-info header * { color: $blue-700; @@ -31,7 +31,11 @@ @extend %frame-gray-800; } %notice-warning { - @extend %frame-yellow-500; + border-color: $yellow-100; + background-color: $yellow-050; +} +%notice-warning header * { + color: $yellow-800; } %notice-error { @extend %frame-red-500; diff --git a/ui/packages/consul-ui/app/templates/dc/intentions/index.hbs b/ui/packages/consul-ui/app/templates/dc/intentions/index.hbs index fefd58c64..b3657f6a6 100644 --- a/ui/packages/consul-ui/app/templates/dc/intentions/index.hbs +++ b/ui/packages/consul-ui/app/templates/dc/intentions/index.hbs @@ -22,6 +22,7 @@ Create + {{#if (gt items.length 0) }} + /> {{/if}} + - {{#let (filter (filter-predicate 'intention' filters) items) as |filtered|}} - {{#let (sort-by (comparator 'intention' sort) filtered) as |sorted|}} - - - - <:idle as |list|> - - - <:empty as |list|> - - -

- {{#if (gt items.length 0)}} - No intentions found - {{else}} - Welcome to Intentions - {{/if}} -

-
- -

- {{#if (gt items.length 0)}} - No intentions where found matching that search, or you may not have access to view the intentions you are searching for. - {{else}} - There don't seem to be any intentions, or you may not have access to view intentions yet. - {{/if}} -

-
- - - - -
- -
-
-
- {{/let}} - {{/let}} -
- + + <:idle as |list|> + + + + <:empty as |list|> + + +

+ {{#if (gt items.length 0)}} + No intentions found + {{else}} + Welcome to Intentions + {{/if}} +

+
+ +

+ {{#if (gt items.length 0)}} + No intentions where found matching that search, or you may not have access to view the intentions you are searching for. + {{else}} + There don't seem to be any intentions, or you may not have access to view intentions yet. + {{/if}} +

+
+ + + + +
+ +
+ + {{/let}} {{/let}} {{/let}} diff --git a/ui/packages/consul-ui/app/templates/dc/services/show/intentions/index.hbs b/ui/packages/consul-ui/app/templates/dc/services/show/intentions/index.hbs index ff3e2fcce..68408ffe0 100644 --- a/ui/packages/consul-ui/app/templates/dc/services/show/intentions/index.hbs +++ b/ui/packages/consul-ui/app/templates/dc/services/show/intentions/index.hbs @@ -27,24 +27,17 @@ }} /> {{/if}} -{{#let (filter (filter-predicate 'intention' filters) items) as |filtered|}} - {{#let (sort-by (comparator 'intention' sort) filtered) as |sorted|}} - - <:idle as |list|> - {{#if (eq searched.length 1)}} - {{#let searched.firstObject as |item|}} - {{#if (eq search item.SourceName)}} - - {{/if}} - {{/let}} - {{/if}} + + <:empty as |list|> @@ -58,10 +51,6 @@ - - - {{/let}} -{{/let}}
{{/let}} diff --git a/ui/packages/consul-ui/tests/acceptance/dc/intentions/delete.feature b/ui/packages/consul-ui/tests/acceptance/dc/intentions/delete.feature index 58202d686..9b5f18646 100644 --- a/ui/packages/consul-ui/tests/acceptance/dc/intentions/delete.feature +++ b/ui/packages/consul-ui/tests/acceptance/dc/intentions/delete.feature @@ -16,9 +16,9 @@ Feature: dc / intentions / deleting: Deleting items with confirmations, success --- dc: datacenter --- - And I click actions on the intentions - And I click delete on the intentions - And I click confirmDelete on the intentions + And I click actions on the intentionList.intentions + And I click delete on the intentionList.intentions + And I click confirmDelete on the intentionList.intentions Then a DELETE request was made to "/v1/connect/intentions/exact?source=default%2Fname&destination=default%2Fdestination&dc=datacenter" And "[data-notification]" has the "notification-delete" class And "[data-notification]" has the "success" class diff --git a/ui/packages/consul-ui/tests/acceptance/dc/intentions/index.feature b/ui/packages/consul-ui/tests/acceptance/dc/intentions/index.feature index 5e1aa58b9..014fde220 100644 --- a/ui/packages/consul-ui/tests/acceptance/dc/intentions/index.feature +++ b/ui/packages/consul-ui/tests/acceptance/dc/intentions/index.feature @@ -9,4 +9,46 @@ Feature: dc / intentions / index --- Then the url should be /dc-1/intentions And the title should be "Intentions - Consul" - Then I see 3 intention models + Then I see 3 intention models on the intentionList component + Scenario: Viewing intentions in the listing live updates + Given 1 datacenter model with the value "dc-1" + Given 3 intention models + And a network latency of 100 + When I visit the intentions page for yaml + --- + dc: dc-1 + --- + Then the url should be /dc-1/intentions + And pause until I see 3 intention models on the intentionList component + And an external edit results in 5 intention models + And pause until I see 5 intention models on the intentionList component + And an external edit results in 1 intention model + And pause until I see 1 intention models on the intentionList component + And an external edit results in 0 intention models + And pause until I see 0 intention models on the intentionList component + Scenario: Viewing intentions in the listing with CRDs + Given 1 datacenter model with the value "dc-1" + And 1 intention models from yaml + --- + Meta: + external-source: kubernetes + --- + When I visit the intentions page for yaml + --- + dc: dc-1 + --- + Then the url should be /dc-1/intentions + Then I see customResourceNotice on the intentionList + Scenario: Viewing intentions in the listing without CRDs + Given 1 datacenter model with the value "dc-1" + And 1 intention models from yaml + --- + Meta: + external-source: consul + --- + When I visit the intentions page for yaml + --- + dc: dc-1 + --- + Then the url should be /dc-1/intentions + Then I don't see customResourceNotice on the intentionList diff --git a/ui/packages/consul-ui/tests/acceptance/dc/intentions/navigation.feature b/ui/packages/consul-ui/tests/acceptance/dc/intentions/navigation.feature index 38979f1a6..4b1bd2318 100644 --- a/ui/packages/consul-ui/tests/acceptance/dc/intentions/navigation.feature +++ b/ui/packages/consul-ui/tests/acceptance/dc/intentions/navigation.feature @@ -21,12 +21,12 @@ Feature: dc / intentions / navigation --- Then the url should be /dc-1/intentions And the title should be "Intentions - Consul" - Then I see 3 intention models + Then I see 3 intention models on the intentionList component Given 1 intention model from yaml --- ID: 755b72bd-f5ab-4c92-90cc-bed0e7d8e9f0 --- - When I click intention on the intentions + When I click intention on the intentionList.intentions component Then a GET request was made to "/v1/internal/ui/services?dc=dc-1&ns=*" And I click "[data-test-back]" Then the url should be /dc-1/intentions @@ -37,7 +37,7 @@ Feature: dc / intentions / navigation --- Then the url should be /dc-1/intentions And the title should be "Intentions - Consul" - Then I see 3 intention models + Then I see 3 intention models on the intentionList component When I click create Then the url should be /dc-1/intentions/create And I click "[data-test-back]" diff --git a/ui/packages/consul-ui/tests/acceptance/dc/intentions/sorting.feature b/ui/packages/consul-ui/tests/acceptance/dc/intentions/sorting.feature index 8575c6044..2c4c7ef96 100644 --- a/ui/packages/consul-ui/tests/acceptance/dc/intentions/sorting.feature +++ b/ui/packages/consul-ui/tests/acceptance/dc/intentions/sorting.feature @@ -1,42 +1,42 @@ -@setupApplicationTest -@notNamespaceable -Feature: dc / intentions / sorting - Scenario: Sorting Intentions - Given 1 datacenter model with the value "dc-1" - And 6 intention models from yaml - --- - - Action: "allow" - - Action: "allow" - - Action: "deny" - - Action: "deny" - - Action: "allow" - - Action: "deny" - --- - When I visit the intentions page for yaml - --- - dc: dc-1 - --- - Then I see 6 intention models - When I click selected on the sort - When I click options.1.button on the sort - Then I see action on the intentions vertically like yaml - --- - - "deny" - - "deny" - - "deny" - - "allow" - - "allow" - - "allow" - --- - When I click selected on the sort - When I click options.0.button on the sort - Then I see action on the intentions vertically like yaml - --- - - "allow" - - "allow" - - "allow" - - "deny" - - "deny" - - "deny" - --- - +@setupApplicationTest +@notNamespaceable +Feature: dc / intentions / sorting + Scenario: Sorting Intentions + Given 1 datacenter model with the value "dc-1" + And 6 intention models from yaml + --- + - Action: "allow" + - Action: "allow" + - Action: "deny" + - Action: "deny" + - Action: "allow" + - Action: "deny" + --- + When I visit the intentions page for yaml + --- + dc: dc-1 + --- + Then I see 6 intention models on the intentionList component + When I click selected on the sort + When I click options.1.button on the sort + Then I see action on the intentionList.intentions vertically like yaml + --- + - "deny" + - "deny" + - "deny" + - "allow" + - "allow" + - "allow" + --- + When I click selected on the sort + When I click options.0.button on the sort + Then I see action on the intentionList.intentions vertically like yaml + --- + - "allow" + - "allow" + - "allow" + - "deny" + - "deny" + - "deny" + --- + diff --git a/ui/packages/consul-ui/tests/acceptance/dc/list-blocking.feature b/ui/packages/consul-ui/tests/acceptance/dc/list-blocking.feature index 2cbdfadc2..0194be140 100644 --- a/ui/packages/consul-ui/tests/acceptance/dc/list-blocking.feature +++ b/ui/packages/consul-ui/tests/acceptance/dc/list-blocking.feature @@ -24,7 +24,6 @@ Feature: dc / list-blocking ------------------------------------------------ | Page | Model | Url | | nodes | node | nodes | - | intentions | intention | intentions | ------------------------------------------------ Scenario: Viewing detail pages with a listing for [Page] Given 3 [Model] models diff --git a/ui/packages/consul-ui/tests/acceptance/dc/services/show/intentions.feature b/ui/packages/consul-ui/tests/acceptance/dc/services/show/intentions.feature index 58706574e..dd7b4fc65 100644 --- a/ui/packages/consul-ui/tests/acceptance/dc/services/show/intentions.feature +++ b/ui/packages/consul-ui/tests/acceptance/dc/services/show/intentions.feature @@ -37,11 +37,11 @@ Feature: dc / services / show / intentions: Intentions per service When I click intentions on the tabs And I see intentionsIsSelected on the tabs Scenario: I can see intentions - And I see 3 intention models + And I see 3 intention models on the intentionList component Scenario: I can delete intentions - And I click actions on the intentions - And I click delete on the intentions - And I click confirmDelete on the intentions + And I click actions on the intentionList.intentions component + And I click delete on the intentionList.intentions component + And I click confirmDelete on the intentionList.intentions Then a DELETE request was made to "/v1/connect/intentions/exact?source=default%2Fname&destination=default%2Fdestination&dc=dc1" And "[data-notification]" has the "notification-delete" class And "[data-notification]" has the "success" class diff --git a/ui/packages/consul-ui/tests/pages.js b/ui/packages/consul-ui/tests/pages.js index b0012fbb7..121dfef1e 100644 --- a/ui/packages/consul-ui/tests/pages.js +++ b/ui/packages/consul-ui/tests/pages.js @@ -94,7 +94,7 @@ const morePopoverMenu = morePopoverMenuFactory(clickable); const popoverSelect = popoverSelectFactory(clickable, collection); const emptyState = emptyStateFactory(isPresent); -const consulIntentionList = consulIntentionListFactory(collection, clickable, attribute, deletable); +const consulIntentionList = consulIntentionListFactory(collection, clickable, attribute, isPresent, deletable); const consulNspaceList = consulNspaceListFactory( collection, clickable, diff --git a/ui/packages/consul-ui/tests/pages/dc/intentions/index.js b/ui/packages/consul-ui/tests/pages/dc/intentions/index.js index f554d1964..65b3b8bda 100644 --- a/ui/packages/consul-ui/tests/pages/dc/intentions/index.js +++ b/ui/packages/consul-ui/tests/pages/dc/intentions/index.js @@ -1,8 +1,8 @@ export default function(visitable, creatable, clickable, intentions, popoverSelect) { - return creatable({ + return { visit: visitable('/:dc/intentions'), - intentions: intentions(), + intentionList: intentions(), sort: popoverSelect('[data-test-sort-control]'), - create: clickable('[data-test-create]'), - }); + ...creatable({}) + } } diff --git a/ui/packages/consul-ui/tests/pages/dc/services/show.js b/ui/packages/consul-ui/tests/pages/dc/services/show.js index 6e5313c00..84fc79677 100644 --- a/ui/packages/consul-ui/tests/pages/dc/services/show.js +++ b/ui/packages/consul-ui/tests/pages/dc/services/show.js @@ -21,7 +21,7 @@ export default function(visitable, attribute, collection, text, intentions, filt instances: collection('.consul-service-instance-list > ul > li:not(:first-child)', { address: text('[data-test-address]'), }), - intentions: intentions(), + intentionList: intentions(), }; page.tabs.upstreamsTab = { services: collection('.consul-upstream-list > ul > li:not(:first-child)', { diff --git a/ui/packages/consul-ui/tests/steps/assertions/model.js b/ui/packages/consul-ui/tests/steps/assertions/model.js index bcacb8c0a..227f39ed2 100644 --- a/ui/packages/consul-ui/tests/steps/assertions/model.js +++ b/ui/packages/consul-ui/tests/steps/assertions/model.js @@ -11,6 +11,18 @@ export default function(scenario, assert, find, currentPage, pauseUntil, plurali return retry(); }, `Expected ${num} ${model}s`); }) + .then('pause until I see $number $model model[s]? on the $component component', function(num, model, component) { + return pauseUntil(function(resolve, reject, retry) { + const obj = find(component); + const len = obj[pluralize(model)].filter(function(item) { + return item.isVisible; + }).length; + if (len === num) { + return resolve(); + } + return retry(); + }, `Expected ${num} ${model}s`); + }) .then(['I see $num $model model[s]?'], function(num, model) { const len = currentPage()[pluralize(model)].filter(function(item) { return item.isVisible; diff --git a/ui/packages/consul-ui/tests/steps/assertions/page.js b/ui/packages/consul-ui/tests/steps/assertions/page.js index efef064d2..18baba76c 100644 --- a/ui/packages/consul-ui/tests/steps/assertions/page.js +++ b/ui/packages/consul-ui/tests/steps/assertions/page.js @@ -101,7 +101,7 @@ export default function(scenario, assert, find, currentPage, $) { component, yaml ) { - const _component = currentPage()[component]; + const _component = find(component); const iterator = new Array(_component.length).fill(true); assert.ok(iterator.length > 0); diff --git a/ui/packages/consul-ui/tests/steps/interactions/click.js b/ui/packages/consul-ui/tests/steps/interactions/click.js index f1a287725..65a93f8ec 100644 --- a/ui/packages/consul-ui/tests/steps/interactions/click.js +++ b/ui/packages/consul-ui/tests/steps/interactions/click.js @@ -4,7 +4,11 @@ export default function(scenario, find, click) { return click(selector); }) // TODO: Probably nicer to think of better vocab than having the 'without " rule' - .when(['I click (?!")$property(?!")', 'I click $property on the $component'], function( + .when([ + 'I click (?!")$property(?!")', + 'I click $property on the $component', + 'I click $property on the $component component' + ], function( property, component, next diff --git a/ui/packages/consul-ui/tests/unit/search/filters/intention-test.js b/ui/packages/consul-ui/tests/unit/search/predicates/intention-test.js similarity index 75% rename from ui/packages/consul-ui/tests/unit/search/filters/intention-test.js rename to ui/packages/consul-ui/tests/unit/search/predicates/intention-test.js index a3357ef6d..a7c39bb4d 100644 --- a/ui/packages/consul-ui/tests/unit/search/filters/intention-test.js +++ b/ui/packages/consul-ui/tests/unit/search/predicates/intention-test.js @@ -1,8 +1,8 @@ -import getFilter from 'consul-ui/search/filters/intention'; +import getPredicate from 'consul-ui/search/predicates/intention'; import { module, test } from 'qunit'; -module('Unit | Search | Filter | intention', function() { - const filter = getFilter(cb => cb); +module('Unit | Search | Predicate | intention', function() { + const predicate = getPredicate(); test('items are found by properties', function(assert) { [ { @@ -14,9 +14,7 @@ module('Unit | Search | Filter | intention', function() { DestinationName: 'hiT', }, ].forEach(function(item) { - const actual = filter(item, { - s: 'hit', - }); + const actual = predicate('hit')(item); assert.ok(actual); }); }); @@ -27,9 +25,7 @@ module('Unit | Search | Filter | intention', function() { DestinationName: 'destination', }, ].forEach(function(item) { - const actual = filter(item, { - s: '*', - }); + const actual = predicate('*')(item); assert.notOk(actual); }); }); @@ -44,9 +40,7 @@ module('Unit | Search | Filter | intention', function() { DestinationName: '*', }, ].forEach(function(item) { - const actual = filter(item, { - s: '*', - }); + const actual = predicate('*')(item); assert.ok(actual); }); }); @@ -62,9 +56,7 @@ module('Unit | Search | Filter | intention', function() { }, ].forEach(function(item) { ['All Services (*)', 'SerVices', '(*)', '*', 'vIces', 'lL Ser'].forEach(function(term) { - const actual = filter(item, { - s: term, - }); + const actual = predicate(term)(item); assert.ok(actual); }); });