ui: Per Service Intentions Tab (#7615)
* Add model layer support for filtering intentions by service * Add Route, Controller and template for services.show.intentions tab We are still loading the intentions themselves in the parent Route for the moment * Load the intentions in in the parent route for the moment * Temporarily add support for returning to history -1 Once we have an intention form underneath the service/intention tab this will no longer be needed * Add the new tab and enable blocking queries for it * Add some further acceptance testing around intention listings
This commit is contained in:
parent
b43ecb8fa7
commit
455dfe0a1b
|
@ -6,11 +6,14 @@ import { SLUG_KEY } from 'consul-ui/models/intention';
|
||||||
|
|
||||||
// TODO: Update to use this.formatDatacenter()
|
// TODO: Update to use this.formatDatacenter()
|
||||||
export default Adapter.extend({
|
export default Adapter.extend({
|
||||||
requestForQuery: function(request, { dc, index, id }) {
|
requestForQuery: function(request, { dc, filter, index }) {
|
||||||
return request`
|
return request`
|
||||||
GET /v1/connect/intentions?${{ dc }}
|
GET /v1/connect/intentions?${{ dc }}
|
||||||
|
|
||||||
${{ index }}
|
${{
|
||||||
|
index,
|
||||||
|
filter,
|
||||||
|
}}
|
||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
requestForQueryRecord: function(request, { dc, index, id }) {
|
requestForQueryRecord: function(request, { dc, index, id }) {
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import Controller from '@ember/controller';
|
||||||
|
import { get, computed } from '@ember/object';
|
||||||
|
import WithSearching from 'consul-ui/mixins/with-searching';
|
||||||
|
|
||||||
|
export default Controller.extend(WithSearching, {
|
||||||
|
queryParams: {
|
||||||
|
s: {
|
||||||
|
as: 'filter',
|
||||||
|
replace: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
init: function() {
|
||||||
|
this.searchParams = {
|
||||||
|
intention: 's',
|
||||||
|
};
|
||||||
|
this._super(...arguments);
|
||||||
|
},
|
||||||
|
searchable: computed('intentions', function() {
|
||||||
|
return get(this, 'searchables.intention')
|
||||||
|
.add(this.intentions)
|
||||||
|
.search(get(this, this.searchParams.intention));
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
route: function() {
|
||||||
|
this.send(...arguments);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -61,6 +61,7 @@ export function initialize(container) {
|
||||||
services: {
|
services: {
|
||||||
repo: 'repository/service/event-source',
|
repo: 'repository/service/event-source',
|
||||||
chainRepo: 'repository/discovery-chain/event-source',
|
chainRepo: 'repository/discovery-chain/event-source',
|
||||||
|
intentionRepo: 'repository/intention/event-source',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Mixin from '@ember/object/mixin';
|
import Mixin from '@ember/object/mixin';
|
||||||
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
||||||
|
import { get } from '@ember/object';
|
||||||
|
|
||||||
import { INTERNAL_SERVER_ERROR as HTTP_INTERNAL_SERVER_ERROR } from 'consul-ui/utils/http/status';
|
import { INTERNAL_SERVER_ERROR as HTTP_INTERNAL_SERVER_ERROR } from 'consul-ui/utils/http/status';
|
||||||
export default Mixin.create(WithBlockingActions, {
|
export default Mixin.create(WithBlockingActions, {
|
||||||
|
@ -14,4 +15,25 @@ export default Mixin.create(WithBlockingActions, {
|
||||||
}
|
}
|
||||||
return type;
|
return type;
|
||||||
},
|
},
|
||||||
|
afterUpdate: function(item) {
|
||||||
|
if (get(this, 'history.length') > 0) {
|
||||||
|
return this.transitionTo(this.history[0].key, this.history[0].value);
|
||||||
|
}
|
||||||
|
return this._super(...arguments);
|
||||||
|
},
|
||||||
|
afterCreate: function(item) {
|
||||||
|
if (get(this, 'history.length') > 0) {
|
||||||
|
return this.transitionTo(this.history[0].key, this.history[0].value);
|
||||||
|
}
|
||||||
|
return this._super(...arguments);
|
||||||
|
},
|
||||||
|
afterDelete: function(item) {
|
||||||
|
if (get(this, 'history.length') > 0) {
|
||||||
|
return this.transitionTo(this.history[0].key, this.history[0].value);
|
||||||
|
}
|
||||||
|
if (this.routeName === 'dc.services.show') {
|
||||||
|
return this.transitionTo(this.routeName, this._router.currentRoute.params.name);
|
||||||
|
}
|
||||||
|
return this._super(...arguments);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,7 +10,19 @@ export default Route.extend(WithIntentionActions, {
|
||||||
repo: service('repository/intention'),
|
repo: service('repository/intention'),
|
||||||
servicesRepo: service('repository/service'),
|
servicesRepo: service('repository/service'),
|
||||||
nspacesRepo: service('repository/nspace/disabled'),
|
nspacesRepo: service('repository/nspace/disabled'),
|
||||||
model: function(params) {
|
buildRouteInfoMetadata: function() {
|
||||||
|
return { history: this.history };
|
||||||
|
},
|
||||||
|
model: function(params, transition) {
|
||||||
|
const from = get(transition, 'from');
|
||||||
|
this.history = [];
|
||||||
|
if (from && get(from, 'name') === 'dc.services.show.intentions') {
|
||||||
|
this.history.push({
|
||||||
|
key: get(from, 'name'),
|
||||||
|
value: get(from, 'parent.params.name'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const dc = this.modelFor('dc').dc.Name;
|
const dc = this.modelFor('dc').dc.Name;
|
||||||
// We load all of your services that you are able to see here
|
// We load all of your services that you are able to see here
|
||||||
// as even if it doesn't exist in the namespace you are targetting
|
// as even if it doesn't exist in the namespace you are targetting
|
||||||
|
@ -21,6 +33,7 @@ export default Route.extend(WithIntentionActions, {
|
||||||
item: this.repo.findBySlug(params.id, dc, nspace),
|
item: this.repo.findBySlug(params.id, dc, nspace),
|
||||||
services: this.servicesRepo.findAllByDatacenter(dc, nspace),
|
services: this.servicesRepo.findAllByDatacenter(dc, nspace),
|
||||||
nspaces: this.nspacesRepo.findAll(),
|
nspaces: this.nspacesRepo.findAll(),
|
||||||
|
history: this.history,
|
||||||
}).then(function(model) {
|
}).then(function(model) {
|
||||||
return {
|
return {
|
||||||
...model,
|
...model,
|
||||||
|
|
|
@ -5,15 +5,18 @@ import { get } from '@ember/object';
|
||||||
|
|
||||||
export default Route.extend({
|
export default Route.extend({
|
||||||
repo: service('repository/service'),
|
repo: service('repository/service'),
|
||||||
|
intentionRepo: service('repository/intention'),
|
||||||
chainRepo: service('repository/discovery-chain'),
|
chainRepo: service('repository/discovery-chain'),
|
||||||
settings: service('settings'),
|
settings: service('settings'),
|
||||||
model: function(params) {
|
model: function(params, transition = {}) {
|
||||||
const dc = this.modelFor('dc').dc.Name;
|
const dc = this.modelFor('dc').dc.Name;
|
||||||
const nspace = this.modelFor('nspace').nspace.substr(1);
|
const nspace = this.modelFor('nspace').nspace.substr(1);
|
||||||
return hash({
|
return hash({
|
||||||
item: this.repo.findBySlug(params.name, dc, nspace),
|
item: this.repo.findBySlug(params.name, dc, nspace),
|
||||||
|
intentions: this.intentionRepo.findByService(params.name, dc, nspace),
|
||||||
urls: this.settings.findBySlug('urls'),
|
urls: this.settings.findBySlug('urls'),
|
||||||
dc: dc,
|
dc: dc,
|
||||||
|
nspace: nspace,
|
||||||
}).then(model => {
|
}).then(model => {
|
||||||
return hash({
|
return hash({
|
||||||
chain: ['connect-proxy', 'mesh-gateway'].includes(get(model, 'item.Service.Kind'))
|
chain: ['connect-proxy', 'mesh-gateway'].includes(get(model, 'item.Service.Kind'))
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import Route from '@ember/routing/route';
|
import Route from '@ember/routing/route';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
import WithIntentionActions from 'consul-ui/mixins/intention/with-actions';
|
||||||
|
|
||||||
export default Route.extend({
|
export default Route.extend(WithIntentionActions, {
|
||||||
|
repo: service('repository/intention'),
|
||||||
model: function() {
|
model: function() {
|
||||||
const parent = this.routeName
|
const parent = this.routeName
|
||||||
.split('.')
|
.split('.')
|
||||||
|
@ -11,4 +14,8 @@ export default Route.extend({
|
||||||
setupController: function(controller, model) {
|
setupController: function(controller, model) {
|
||||||
controller.setProperties(model);
|
controller.setProperties(model);
|
||||||
},
|
},
|
||||||
|
// Overwrite default afterDelete action to just refresh
|
||||||
|
afterDelete: function() {
|
||||||
|
return this.refresh();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,4 +8,17 @@ export default RepositoryService.extend({
|
||||||
getPrimaryKey: function() {
|
getPrimaryKey: function() {
|
||||||
return PRIMARY_KEY;
|
return PRIMARY_KEY;
|
||||||
},
|
},
|
||||||
|
findByService: function(slug, dc, nspace, configuration = {}) {
|
||||||
|
const query = {
|
||||||
|
dc: dc,
|
||||||
|
nspace: nspace,
|
||||||
|
filter: `SourceName == ${slug} or DestinationName == ${slug}`,
|
||||||
|
};
|
||||||
|
if (typeof configuration.cursor !== 'undefined') {
|
||||||
|
query.index = configuration.cursor;
|
||||||
|
}
|
||||||
|
return this.store.query(this.getModelName(), {
|
||||||
|
...query,
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,7 +10,14 @@
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
<BlockSlot @name="breadcrumbs">
|
<BlockSlot @name="breadcrumbs">
|
||||||
<ol>
|
<ol>
|
||||||
<li><a data-test-back href={{href-to 'dc.intentions'}}>All Intentions</a></li>
|
{{#if (gt history.length 0)}}
|
||||||
|
<li><a href={{href-to 'dc.services'}}>All Services</a></li>
|
||||||
|
{{#let history.firstObject as |back|}}
|
||||||
|
<li><a data-test-back href={{href-to back.key back.value}}>{{concat 'Service (' back.value ')'}}</a></li>
|
||||||
|
{{/let}}
|
||||||
|
{{else}}
|
||||||
|
<li><a data-test-back href={{href-to 'dc.intentions'}}>All Intentions</a></li>
|
||||||
|
{{/if}}
|
||||||
</ol>
|
</ol>
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
<BlockSlot @name="header">
|
<BlockSlot @name="header">
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<AppView @class="service show">
|
<AppView @class="service show">
|
||||||
<BlockSlot @name="notification" as |status type|>
|
<BlockSlot @name="notification" as |status type|>
|
||||||
{{partial 'dc/services/notifications'}}
|
{{partial 'dc/services/notifications'}}
|
||||||
|
{{partial 'dc/intentions/notifications'}}
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
<BlockSlot @name="breadcrumbs">
|
<BlockSlot @name="breadcrumbs">
|
||||||
<ol>
|
<ol>
|
||||||
|
@ -27,6 +28,7 @@
|
||||||
compact
|
compact
|
||||||
(array
|
(array
|
||||||
(hash label="Instances" href=(href-to "dc.services.show.instances") selected=(is-href "dc.services.show.instances"))
|
(hash label="Instances" href=(href-to "dc.services.show.instances") selected=(is-href "dc.services.show.instances"))
|
||||||
|
(hash label="Intentions" href=(href-to "dc.services.show.intentions") selected=(is-href "dc.services.show.intentions"))
|
||||||
(if (not-eq chain) (hash label="Routing" href=(href-to "dc.services.show.routing") selected=(is-href "dc.services.show.routing")) '')
|
(if (not-eq chain) (hash label="Routing" href=(href-to "dc.services.show.routing") selected=(is-href "dc.services.show.routing")) '')
|
||||||
(hash label="Tags" href=(href-to "dc.services.show.tags") selected=(is-href "dc.services.show.tags"))
|
(hash label="Tags" href=(href-to "dc.services.show.tags") selected=(is-href "dc.services.show.tags"))
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<div id="intentions" class="tab-section">
|
||||||
|
<div role="tabpanel">
|
||||||
|
{{#if (gt intentions.length 0) }}
|
||||||
|
<input type="checkbox" id="toolbar-toggle" />
|
||||||
|
<form class="filter-bar">
|
||||||
|
<FreetextFilter @searchable={{searchable}} @value={{s}} @placeholder="Search" />
|
||||||
|
</form>
|
||||||
|
{{/if}}
|
||||||
|
<ChangeableSet @dispatcher={{searchable}}>
|
||||||
|
<BlockSlot @name="set" as |filtered|>
|
||||||
|
<ConsulIntentionList
|
||||||
|
@items={{filtered}}
|
||||||
|
@ondelete={{action "route" "delete"}}
|
||||||
|
/>
|
||||||
|
</BlockSlot>
|
||||||
|
<BlockSlot @name="empty">
|
||||||
|
<p>
|
||||||
|
There are no intentions for this service.
|
||||||
|
</p>
|
||||||
|
</BlockSlot>
|
||||||
|
</ChangeableSet>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,12 @@
|
||||||
|
@setupApplicationTest
|
||||||
|
Feature: dc / intentions / index
|
||||||
|
Scenario: Viewing intentions in the listing
|
||||||
|
Given 1 datacenter model with the value "dc-1"
|
||||||
|
And 3 intention models
|
||||||
|
When I visit the intentions page for yaml
|
||||||
|
---
|
||||||
|
dc: dc-1
|
||||||
|
---
|
||||||
|
Then the url should be /dc-1/intentions
|
||||||
|
And the title should be "Intentions - Consul"
|
||||||
|
Then I see 3 intention models
|
|
@ -0,0 +1,31 @@
|
||||||
|
@setupApplicationTest
|
||||||
|
Feature: dc / services / intentions: Intentions per service
|
||||||
|
Background:
|
||||||
|
Given 1 datacenter model with the value "dc1"
|
||||||
|
And 1 node models
|
||||||
|
And 1 service model from yaml
|
||||||
|
---
|
||||||
|
- Service:
|
||||||
|
Kind: consul
|
||||||
|
Name: service-0
|
||||||
|
ID: service-0-with-id
|
||||||
|
---
|
||||||
|
And 3 intention models
|
||||||
|
When I visit the service page for yaml
|
||||||
|
---
|
||||||
|
dc: dc1
|
||||||
|
service: service-0
|
||||||
|
---
|
||||||
|
And the title should be "service-0 - Consul"
|
||||||
|
And I see intentions on the tabs
|
||||||
|
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
|
||||||
|
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
|
||||||
|
Then a DELETE request was made to "/v1/connect/intentions/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=dc1"
|
||||||
|
And "[data-notification]" has the "notification-delete" class
|
||||||
|
And "[data-notification]" has the "success" class
|
|
@ -0,0 +1,10 @@
|
||||||
|
import steps from '../../steps';
|
||||||
|
|
||||||
|
// step definitions that are shared between features should be moved to the
|
||||||
|
// tests/acceptance/steps/steps.js file
|
||||||
|
|
||||||
|
export default function(assert) {
|
||||||
|
return steps(assert).then('I should find a file', function() {
|
||||||
|
assert.ok(true, this.step);
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
import steps from '../../../steps';
|
||||||
|
|
||||||
|
// step definitions that are shared between features should be moved to the
|
||||||
|
// tests/acceptance/steps/steps.js file
|
||||||
|
|
||||||
|
export default function(assert) {
|
||||||
|
return steps(assert).then('I should find a file', function() {
|
||||||
|
assert.ok(true, this.step);
|
||||||
|
});
|
||||||
|
}
|
|
@ -14,6 +14,8 @@ import createSubmitable from 'consul-ui/tests/lib/page-object/createSubmitable';
|
||||||
import createCreatable from 'consul-ui/tests/lib/page-object/createCreatable';
|
import createCreatable from 'consul-ui/tests/lib/page-object/createCreatable';
|
||||||
import createCancelable from 'consul-ui/tests/lib/page-object/createCancelable';
|
import createCancelable from 'consul-ui/tests/lib/page-object/createCancelable';
|
||||||
|
|
||||||
|
// TODO: All component-like page objects should be moved into the component folder
|
||||||
|
// along with all of its other dependencies once we can mae ember-cli ignore them
|
||||||
import page from 'consul-ui/tests/pages/components/page';
|
import page from 'consul-ui/tests/pages/components/page';
|
||||||
import radiogroup from 'consul-ui/tests/lib/page-object/radiogroup';
|
import radiogroup from 'consul-ui/tests/lib/page-object/radiogroup';
|
||||||
import tabgroup from 'consul-ui/tests/lib/page-object/tabgroup';
|
import tabgroup from 'consul-ui/tests/lib/page-object/tabgroup';
|
||||||
|
@ -26,6 +28,8 @@ import policyFormFactory from 'consul-ui/tests/pages/components/policy-form';
|
||||||
import policySelectorFactory from 'consul-ui/tests/pages/components/policy-selector';
|
import policySelectorFactory from 'consul-ui/tests/pages/components/policy-selector';
|
||||||
import roleFormFactory from 'consul-ui/tests/pages/components/role-form';
|
import roleFormFactory from 'consul-ui/tests/pages/components/role-form';
|
||||||
import roleSelectorFactory from 'consul-ui/tests/pages/components/role-selector';
|
import roleSelectorFactory from 'consul-ui/tests/pages/components/role-selector';
|
||||||
|
import consulIntentionListFactory from 'consul-ui/tests/pages/components/consul-intention-list';
|
||||||
|
|
||||||
// TODO: should this specifically be modal or form?
|
// TODO: should this specifically be modal or form?
|
||||||
// should all forms be forms?
|
// should all forms be forms?
|
||||||
|
|
||||||
|
@ -65,13 +69,26 @@ const policySelector = policySelectorFactory(clickable, deletable, collection, a
|
||||||
const roleForm = roleFormFactory(submitable, cancelable, policySelector);
|
const roleForm = roleFormFactory(submitable, cancelable, policySelector);
|
||||||
const roleSelector = roleSelectorFactory(clickable, deletable, collection, alias, roleForm);
|
const roleSelector = roleSelectorFactory(clickable, deletable, collection, alias, roleForm);
|
||||||
|
|
||||||
|
const consulIntentionList = consulIntentionListFactory(collection, clickable, attribute, deletable);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
index: create(index(visitable, collection)),
|
index: create(index(visitable, collection)),
|
||||||
dcs: create(dcs(visitable, clickable, attribute, collection)),
|
dcs: create(dcs(visitable, clickable, attribute, collection)),
|
||||||
services: create(
|
services: create(
|
||||||
services(visitable, clickable, text, attribute, collection, page, catalogFilter, radiogroup)
|
services(visitable, clickable, text, attribute, collection, page, catalogFilter, radiogroup)
|
||||||
),
|
),
|
||||||
service: create(service(visitable, attribute, collection, text, catalogFilter, tabgroup)),
|
service: create(
|
||||||
|
service(
|
||||||
|
visitable,
|
||||||
|
clickable,
|
||||||
|
attribute,
|
||||||
|
collection,
|
||||||
|
text,
|
||||||
|
consulIntentionList,
|
||||||
|
catalogFilter,
|
||||||
|
tabgroup
|
||||||
|
)
|
||||||
|
),
|
||||||
instance: create(instance(visitable, attribute, collection, text, tabgroup)),
|
instance: create(instance(visitable, attribute, collection, text, tabgroup)),
|
||||||
nodes: create(nodes(visitable, clickable, attribute, collection, catalogFilter)),
|
nodes: create(nodes(visitable, clickable, attribute, collection, catalogFilter)),
|
||||||
node: create(node(visitable, deletable, clickable, attribute, collection, tabgroup)),
|
node: create(node(visitable, deletable, clickable, attribute, collection, tabgroup)),
|
||||||
|
@ -113,9 +130,7 @@ export default {
|
||||||
token: create(
|
token: create(
|
||||||
token(visitable, submitable, deletable, cancelable, clickable, policySelector, roleSelector)
|
token(visitable, submitable, deletable, cancelable, clickable, policySelector, roleSelector)
|
||||||
),
|
),
|
||||||
intentions: create(
|
intentions: create(intentions(visitable, creatable, consulIntentionList, intentionFilter)),
|
||||||
intentions(visitable, deletable, creatable, clickable, attribute, collection, intentionFilter)
|
|
||||||
),
|
|
||||||
intention: create(intention(visitable, submitable, deletable, cancelable)),
|
intention: create(intention(visitable, submitable, deletable, cancelable)),
|
||||||
nspaces: create(
|
nspaces: create(
|
||||||
nspaces(visitable, deletable, creatable, clickable, attribute, collection, text, freetextFilter)
|
nspaces(visitable, deletable, creatable, clickable, attribute, collection, text, freetextFilter)
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
export default (collection, clickable, attribute, deletable) => () => {
|
||||||
|
return collection('.consul-intention-list [data-test-tabular-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(),
|
||||||
|
});
|
||||||
|
};
|
|
@ -1,19 +1,7 @@
|
||||||
export default function(visitable, deletable, creatable, clickable, attribute, collection, filter) {
|
export default function(visitable, creatable, intentions, filter) {
|
||||||
return creatable({
|
return creatable({
|
||||||
visit: visitable('/:dc/intentions'),
|
visit: visitable('/:dc/intentions'),
|
||||||
intentions: collection(
|
intentions: intentions(),
|
||||||
'[data-test-tabular-row]',
|
|
||||||
deletable({
|
|
||||||
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'),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
filter: filter,
|
filter: filter,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,26 @@
|
||||||
export default function(visitable, attribute, collection, text, filter, tabs) {
|
export default function(
|
||||||
|
visitable,
|
||||||
|
clickable,
|
||||||
|
attribute,
|
||||||
|
collection,
|
||||||
|
text,
|
||||||
|
intentions,
|
||||||
|
filter,
|
||||||
|
tabs
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
visit: visitable('/:dc/services/:service'),
|
visit: visitable('/:dc/services/:service'),
|
||||||
externalSource: attribute('data-test-external-source', 'h1 span'),
|
externalSource: attribute('data-test-external-source', 'h1 span'),
|
||||||
instances: collection('#instances [data-test-tabular-row]', {
|
|
||||||
address: text('[data-test-address]'),
|
|
||||||
}),
|
|
||||||
dashboardAnchor: {
|
dashboardAnchor: {
|
||||||
href: attribute('href', '[data-test-dashboard-anchor]'),
|
href: attribute('href', '[data-test-dashboard-anchor]'),
|
||||||
},
|
},
|
||||||
tabs: tabs('tab', ['instances', 'routing', 'tags']),
|
tabs: tabs('tab', ['instances', 'intentions', 'routing', 'tags']),
|
||||||
filter: filter,
|
filter: filter,
|
||||||
|
|
||||||
|
// TODO: These need to somehow move to subpages
|
||||||
|
instances: collection('#instances [data-test-tabular-row]', {
|
||||||
|
address: text('[data-test-address]'),
|
||||||
|
}),
|
||||||
|
intentions: intentions(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue