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 f6fffd22f1..e6907a9c78 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 faf8dd3b81..c2d8b2d06f 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 8ace0370b4..92a43a6038 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 0000000000..167ea1730e --- /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 59844bcee1..3159a09f57 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 0000000000..a79a57cceb --- /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 e6ed797ab7..0000000000 --- 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 cf71a6d86c..0000000000 --- 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 0000000000..43fa80528a --- /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 3e1033571b..1c4d627dd3 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 5962a60852..1f807725b3 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 fbde9161a7..049f425565 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 fefd58c64c..b3657f6a68 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 ff3e2fccec..68408ffe0c 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 58202d686e..9b5f186464 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 5e1aa58b9f..014fde2205 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 38979f1a6c..4b1bd2318e 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 8575c60443..2c4c7ef96a 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 2cbdfadc25..0194be140e 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 58706574e4..dd7b4fc652 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 b0012fbb77..121dfef1e4 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 f554d19640..65b3b8bdaf 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 6e5313c00d..84fc79677d 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 bcacb8c0ae..227f39ed2b 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 efef064d2d..18baba76c2 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 f1a2877256..65a93f8ec1 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 a3357ef6dc..a7c39bb4d5 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); }); });