ui: Redesign - Node service instances tab (#8204)

* Upgrade consul-api-dobule to version 3.1.3

* Create ConsulInstaceChecks component with test

* Redesign: Service Instaces tab in for a Node

* Update Node tests to work with the ConsulServiceInstancesList

* Style fix to the copy button in the composite-row details

* Delete helper and move logic to ConsulInstanceChecks component

* Delete unused component consul-node-service-list
This commit is contained in:
Kenia 2020-07-01 10:27:29 -04:00 committed by GitHub
parent a4fe092e7a
commit b0ecfc4109
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 183 additions and 187 deletions

View File

@ -0,0 +1,32 @@
{{#if (gt items.length 0)}}
{{#if (eq healthCheck.check 'empty') }}
<dl class={{healthCheck.check}}>
<dt>
<Tooltip>
{{capitalize type}} Checks
</Tooltip>
</dt>
<dd>No {{type}} checks</dd>
</dl>
{{else}}
{{#if (eq healthCheck.count items.length)}}
<dl class={{healthCheck.check}}>
<dt>
<Tooltip>
{{capitalize type}} Checks
</Tooltip>
</dt>
<dd>All {{type}} checks {{healthCheck.status}}</dd>
</dl>
{{else}}
<dl class={{healthCheck.check}}>
<dt>
<Tooltip>
{{capitalize type}} Checks
</Tooltip>
</dt>
<dd>{{healthCheck.count}}/{{items.length}} {{type}} checks {{healthCheck.status}}</dd>
</dl>
{{/if}}
{{/if}}
{{/if}}

View File

@ -0,0 +1,52 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
export default Component.extend({
tagName: '',
healthCheck: computed('items.[]', function() {
let ChecksCritical = 0;
let ChecksWarning = 0;
let ChecksPassing = 0;
this.items.forEach(item => {
switch (item.Status) {
case 'critical':
ChecksCritical += 1;
break;
case 'warning':
ChecksWarning += 1;
break;
case 'passing':
ChecksPassing += 1;
break;
default:
break;
}
});
switch (true) {
case ChecksCritical !== 0:
return {
check: 'critical',
status: 'failing',
count: ChecksCritical,
};
case ChecksWarning !== 0:
return {
check: 'warning',
status: 'with warning',
count: ChecksWarning,
};
case ChecksPassing !== 0:
return {
check: 'passing',
status: 'passing',
count: ChecksPassing,
};
default:
return {
check: 'empty',
};
}
}),
});

View File

@ -1,80 +1,25 @@
{{#if (gt items.length 0)}}
<ListCollection @items={{items}} class="consul-service-instance-list" as |item index|>
<BlockSlot @name="header">
<a href={{href-to routeName item.Service.Service item.Node.Node (or item.Service.ID item.Service.Service)}}>
{{item.Service.ID}}
</a>
{{#if (eq routeName "dc.services.show")}}
<a data-test-service-name href={{href-to routeName item.Service}}>
{{item.ID}}
</a>
{{else}}
<a data-test-service-name href={{href-to routeName item.Service.Service item.Node.Node (or item.Service.ID item.Service.Service)}}>
{{item.Service.ID}}
</a>
{{/if}}
</BlockSlot>
<BlockSlot @name="details">
{{#if checks}}
<ConsulExternalSource @item={{item}} />
<ConsulInstanceChecks @type="service" @items={{get checks item.Service}} />
{{else}}
<ConsulExternalSource @item={{item.Service}} />
{{#let (reject-by 'ServiceID' '' item.Checks) as |checks|}}
{{#let (service/instance-checks checks) as |serviceCheck| }}
{{#if (eq serviceCheck.check 'empty') }}
<dl class={{serviceCheck.check}}>
<dt>
<Tooltip>
Service Checks
</Tooltip>
</dt>
<dd>No service checks</dd>
</dl>
{{else}}
{{#if (eq serviceCheck.count checks.length)}}
<dl class={{serviceCheck.check}}>
<dt>
<Tooltip>
Service Checks
</Tooltip>
</dt>
<dd>All service checks {{serviceCheck.status}}</dd>
</dl>
{{else}}
<dl class={{serviceCheck.check}}>
<dt>
<Tooltip>
Service Checks
</Tooltip>
</dt>
<dd>{{serviceCheck.count}}/{{checks.length}} service checks {{serviceCheck.status}}</dd>
</dl>
{{/if}}
<ConsulInstanceChecks @type="service" @items={{reject-by 'ServiceID' '' item.Checks}} />
<ConsulInstanceChecks @type="node" @items={{filter-by 'ServiceID' '' item.Checks}} />
{{/if}}
{{/let}}
{{/let}}
{{#let (filter-by 'ServiceID' '' item.Checks) as |checks|}}
{{#let (service/instance-checks checks) as |nodeCheck| }}
{{#if (eq nodeCheck.check 'empty') }}
<dl class={{nodeCheck.check}}>
<dt>
<Tooltip>
Node Checks
</Tooltip>
</dt>
<dd>No node checks</dd>
</dl>
{{else}}
{{#if (eq nodeCheck.count checks.length)}}
<dl class={{nodeCheck.check}}>
<dt>
<Tooltip>
Node Checks
</Tooltip>
</dt>
<dd>All node checks {{nodeCheck.status}}</dd>
</dl>
{{else}}
<dl class={{nodeCheck.check}}>
<dt>
<Tooltip>
Node Checks
</Tooltip>
</dt>
<dd>{{nodeCheck.count}}/{{checks.length}} node checks {{nodeCheck.status}}</dd>
</dl>
{{/if}}
{{/if}}
{{/let}}
{{/let}}
{{#if (get proxies item.Service.ID)}}
<dl class="proxy">
<dt>
@ -99,6 +44,7 @@
</dd>
</dl>
{{/if}}
{{#if item.Service.Port}}
<dl class="address" data-test-address>
<dt>
<Tooltip>
@ -113,7 +59,23 @@
{{/if}}
</dd>
</dl>
{{/if}}
{{#if (and checks item.Port)}}
<dl>
<dt>
<CopyButton
@value={{item.Port}}
@name="Port"
/>
</dt>
<dd data-test-service-port={{item.Port}}>:{{item.Port}}</dd>
</dl>
{{/if}}
{{#if checks}}
<TagList @item={{item}} />
{{else}}
<TagList @item={{item.Service}} />
{{/if}}
</BlockSlot>
</ListCollection>
{{/if}}

View File

@ -1,5 +1,6 @@
import Controller from '@ember/controller';
import { alias } from '@ember/object/computed';
import { get, computed } from '@ember/object';
export default Controller.extend({
items: alias('item.Services'),
@ -9,4 +10,19 @@ export default Controller.extend({
replace: true,
},
},
checks: computed('item.Checks.[]', function() {
const checks = {};
get(this, 'item.Checks')
.filter(item => {
return item.ServiceID !== '';
})
.forEach(item => {
if (typeof checks[item.ServiceID] === 'undefined') {
checks[item.ServiceID] = [];
}
checks[item.ServiceID].push(item);
});
return checks;
}),
});

View File

@ -1,50 +0,0 @@
import { helper } from '@ember/component/helper';
export function healthChecks([items], hash) {
let ChecksCritical = 0;
let ChecksWarning = 0;
let ChecksPassing = 0;
items.forEach(item => {
switch (item.Status) {
case 'critical':
ChecksCritical += 1;
break;
case 'warning':
ChecksWarning += 1;
break;
case 'passing':
ChecksPassing += 1;
break;
default:
break;
}
});
switch (true) {
case ChecksCritical !== 0:
return {
check: 'critical',
status: 'failing',
count: ChecksCritical,
};
case ChecksWarning !== 0:
return {
check: 'warning',
status: 'with warning',
count: ChecksWarning,
};
case ChecksPassing !== 0:
return {
check: 'passing',
status: 'passing',
count: ChecksPassing,
};
default:
return {
check: 'empty',
};
}
}
export default helper(healthChecks);

View File

@ -67,7 +67,7 @@ export const routes = {
_options: { path: '/health-checks' },
},
services: {
_options: { path: '/services' },
_options: { path: '/service-instances' },
},
rtt: {
_options: { path: '/round-trip-time' },

View File

@ -73,6 +73,7 @@
}
%composite-row-detail .copy-button {
margin-right: 4px;
margin-top: 2px;
}
%composite-row-header .copy-button {
margin-left: 4px;

View File

@ -21,7 +21,7 @@
compact
(array
(hash label="Health Checks" href=(href-to "dc.nodes.show.healthchecks") selected=(is-href "dc.nodes.show.healthchecks"))
(hash label="Services" href=(href-to "dc.nodes.show.services") selected=(is-href "dc.nodes.show.services"))
(hash label="Service Instances" href=(href-to "dc.nodes.show.services") selected=(is-href "dc.nodes.show.services"))
(if tomography.distances (hash label="Round Trip Time" href=(href-to "dc.nodes.show.rtt") selected=(is-href "dc.nodes.show.rtt")) '')
(hash label="Lock Sessions" href=(href-to "dc.nodes.show.sessions") selected=(is-href "dc.nodes.show.sessions"))
(hash label="Metadata" href=(href-to "dc.nodes.show.metadata") selected=(is-href "dc.nodes.show.metadata"))

View File

@ -8,38 +8,9 @@
@onsearch={{action (mut search) value="target.value"}}
/>
{{/if}}
<ChangeableSet @dispatcher={{searchable 'nodeservice' items}} @terms={{search}}>
<ChangeableSet @dispatcher={{searchable 'nodeservice' items}} @terms={{search}}>
<BlockSlot @name="set" as |filtered|>
<TabularCollection
data-test-services
@items={{filtered}} as |item index|
>
<BlockSlot @name="header">
<th>Service</th>
<th>Port</th>
<th>Tags</th>
</BlockSlot>
<BlockSlot @name="row">
<td data-test-service-name={{item.Service}}>
<a href={{href-to 'dc.services.show' item.Service}}>
{{#let (service/external-source item) as |externalSource| }}
{{#if externalSource }}
<span data-test-external-source={{externalSource}} style={{concat 'background-image: var(--' externalSource '-icon)'}}></span>
{{else}}
<span></span>
{{/if}}
{{/let}}
{{item.Service}}{{#if (not-eq item.ID item.Service) }}&nbsp;<em data-test-service-id="{{item.ID}}">({{item.ID}})</em>{{/if}}
</a>
</td>
<td data-test-service-port={{item.Port}} class="port">
{{item.Port}}
</td>
<td data-test-service-tags>
<TagList @item={{item}} />
</td>
</BlockSlot>
</TabularCollection>
<ConsulServiceInstanceList @routeName="dc.services.show" @items={{filtered}} @checks={{checks}}/>
</BlockSlot>
<BlockSlot @name="empty">
<p>

View File

@ -90,8 +90,8 @@ Feature: components / catalog-filter
node: node-0
---
# And I see 3 healthcheck model with the name "Disk Util"
When I click services on the tabs
And I see servicesIsSelected on the tabs
When I click serviceInstances on the tabs
And I see serviceInstancesIsSelected on the tabs
Then I fill in with yaml
---
@ -101,12 +101,6 @@ Feature: components / catalog-filter
And I see 1 [Model] model with the port "65535"
Then I fill in with yaml
---
s: service-0-with-id
---
And I see 1 [Model] model
And I see 1 [Model] model with the id "service-0-with-id"
Then I fill in with yaml
---
s: hard drive
---
And I see 1 [Model] model with the name "[Model]-1"

View File

@ -30,24 +30,18 @@ Feature: dc / nodes / services / list: Node > Services Listing
Tags: []
Meta:
external-source: kubernetes
- ID: 'service-4'
Port: 3
Service: 'service-4'
Tags: []
Meta: ~
---
When I visit the node page for yaml
---
dc: dc1
node: node-0
---
When I click services on the tabs
And I see servicesIsSelected on the tabs
When I click serviceInstances on the tabs
And I see serviceInstancesIsSelected on the tabs
And I see externalSource on the services like yaml
---
- consul
- nomad
- terraform
- kubernetes
- ~
---

View File

@ -11,8 +11,8 @@ Feature: dc / nodes / show: Show node
---
And I see healthChecksIsSelected on the tabs
When I click services on the tabs
And I see servicesIsSelected on the tabs
When I click serviceInstances on the tabs
And I see serviceInstancesIsSelected on the tabs
When I click roundTripTime on the tabs
And I see roundTripTimeIsSelected on the tabs
@ -34,14 +34,14 @@ Feature: dc / nodes / show: Show node
---
And I see healthChecksIsSelected on the tabs
When I click services on the tabs
And I see servicesIsSelected on the tabs
When I click serviceInstances on the tabs
And I see serviceInstancesIsSelected on the tabs
And I don't see roundTripTime on the tabs
When I click lockSessions on the tabs
And I see lockSessionsIsSelected on the tabs
Scenario: Given 1 node with no checks all the tabs are visible but the Services tab is selected
Scenario: Given 1 node with no checks all the tabs are visible but the serviceInstances tab is selected
Given 1 node models from yaml
---
ID: node-0
@ -53,10 +53,10 @@ Feature: dc / nodes / show: Show node
node: node-0
---
And I see healthChecks on the tabs
And I see services on the tabs
And I see serviceInstances on the tabs
And I don't see roundTripTime on the tabs
And I see lockSessions on the tabs
And I see servicesIsSelected on the tabs
And I see serviceInstancesIsSelected on the tabs
Scenario: A node warns when deregistered whilst blocking
Given 1 node model from yaml
---

View File

@ -0,0 +1,25 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | consul-instance-checks', function(hooks) {
setupRenderingTest(hooks);
test('it renders', async function(assert) {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.set('myAction', function(val) { ... });
await render(hbs`<ConsulInstanceChecks />`);
assert.equal(this.element.textContent.trim(), '');
// Template block usage:
await render(hbs`
<ConsulInstanceChecks>
</ConsulInstanceChecks>
`);
assert.equal(this.element.textContent.trim(), '');
});
});

View File

@ -138,7 +138,7 @@ export default {
),
instance: create(instance(visitable, attribute, collection, text, tabgroup)),
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, text)),
kvs: create(kvs(visitable, deletable, creatable, clickable, attribute, collection)),
kv: create(kv(visitable, attribute, submitable, deletable, cancelable, clickable)),
acls: create(acls(visitable, deletable, creatable, clickable, attribute, collection, aclFilter)),

View File

@ -1,9 +1,9 @@
export default function(visitable, deletable, clickable, attribute, collection, tabs) {
export default function(visitable, deletable, clickable, attribute, collection, tabs, text) {
return {
visit: visitable('/:dc/nodes/:node'),
tabs: tabs('tab', [
'health-checks',
'services',
'service-instances',
'round-trip-time',
'lock-sessions',
'metadata',
@ -11,11 +11,10 @@ export default function(visitable, deletable, clickable, attribute, collection,
healthchecks: collection('[data-test-node-healthcheck]', {
name: attribute('data-test-node-healthcheck'),
}),
services: collection('#services [data-test-tabular-row]', {
id: attribute('data-test-service-id', '[data-test-service-id]'),
name: attribute('data-test-service-name', '[data-test-service-name]'),
port: attribute('data-test-service-port', '.port'),
externalSource: attribute('data-test-external-source', 'a span'),
services: collection('.consul-service-instance-list > ul > li:not(:first-child)', {
name: text('[data-test-service-name]'),
port: attribute('data-test-service-port', '[data-test-service-port]'),
externalSource: attribute('data-test-external-source', '[data-test-external-source]'),
}),
sessions: collection(
'#lock-sessions [data-test-tabular-row]',

View File

@ -1211,9 +1211,9 @@
js-yaml "^3.13.1"
"@hashicorp/consul-api-double@^3.0.0":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-3.1.2.tgz#3c3b929ab0f8aff5f503728337caf1c1a41171fb"
integrity sha512-igs6f9fiA+z2Us1oLZ49/sEU0WsL+s7a1pnwFtED2xdI8tn5hz9G0doYfOxmi04IifNxv80NVifl3rZl2rn2tw==
version "3.1.3"
resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-3.1.3.tgz#62f8780c8513e9b37f29302543c29143b4024141"
integrity sha512-IZ90RK8g4/QPxQpRLnatwpBQh9Z3kQJjOGiUVz+CrSlXg4KRLhQCFFz/gI2vmhAXRACyTxIWuydPV6BcN4ptZA==
"@hashicorp/ember-cli-api-double@^3.1.0":
version "3.1.0"