ui: Ensure proxy instance health is taken into account in Service Instance Listings (#12279)

We noticed that the Service Instance listing on both Node and Service views where not taking into account proxy instance health. This fixes that up so that the small health check information in each Service Instance row includes the proxy instances health checks when displaying Service Instance health (afterall if the proxy instance is unhealthy then so is the service instance that it should be proxying)

* Refactor Consul::InstanceChecks with docs

* Add to-hash helper, which will return an object keyed by a prop

* Stop using/relying on ember-data type things, just use a hash lookup

* For the moment add an equivalent "just give me proxies" model prop

* Start stitching things together, this one requires an extra HTTP request

..previously we weren't even requesting proxies instances here

* Finish up the stitching

* Document Consul::ServiceInstance::List while I'm here

* Fix up navigation mocks Name > Service
This commit is contained in:
John Cowen 2022-02-10 15:28:26 +00:00 committed by GitHub
parent ed5204b6b5
commit d49ee8e355
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 335 additions and 89 deletions

3
.changelog/12279.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bugfix
ui: Ensure proxy instance health is taken into account in Service Instance Listings
```

View File

@ -0,0 +1,96 @@
# Consul::InstanceChecks
A presentational component to show an overview/summary of service or node
health.
```hbs preview-template
<figure>
<figcaption>With no checks</figcaption>
<Consul::InstanceChecks
@type="service"
@items={{array}}
/>
</figure>
<figure>
<figcaption>With all passing check</figcaption>
<Consul::InstanceChecks
@type="service"
@items={{array
(hash
Status="passing"
)
}}
/>
</figure>
<figure>
<figcaption>With a warning check</figcaption>
<Consul::InstanceChecks
@type="service"
@items={{array
(hash
Status="passing"
)
(hash
Status="passing"
)
(hash
Status="warning"
)
}}
/>
</figure>
<figure>
<figcaption>With a critical check</figcaption>
<Consul::InstanceChecks
@type="service"
@items={{array
(hash
Status="passing"
)
(hash
Status="warning"
)
(hash
Status="warning"
)
(hash
Status="critical"
)
}}
/>
</figure>
<figure>
<figcaption>Nodes with a critical check</figcaption>
<Consul::InstanceChecks
@type="node"
@items={{array
(hash
Status="passing"
)
(hash
Status="warning"
)
(hash
Status="warning"
)
(hash
Status="critical"
)
}}
/>
</figure>
```
## Arguments
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `type` | `(service | node )` | | A simple string to use for labelling |
| `items` | `Array` | | An array of Consul healthchecks |
## See
- [Template Source Code](./index.hbs)
---

View File

@ -1,30 +1,49 @@
{{#if (eq this.healthCheck.check 'empty') }}
<dl class={{this.healthCheck.check}}>
{{#let
(group-by "Status" (or @items (array)))
as |grouped|}}
{{! Check the status of each Status of checks in order }}
{{! First one to have more than one check wins }}
{{! Otherwise we are empty }}
{{#let
(or
(if (gt grouped.critical.length 0) grouped.critical)
(if (gt grouped.warning.length 0) grouped.warning)
(if (gt grouped.passing.length 0) grouped.passing)
(array)
)
as |checks|}}
{{#let
checks.firstObject.Status
as |status|}}
<dl
class={{class-map
'consul-instance-checks'
(array 'empty' (eq checks.length 0))
(array status (not-eq checks.length 0))
}}
...attributes
>
<dt>
<Tooltip>
{{capitalize @type}} Checks
</Tooltip>
</dt>
<dd>No {{@type}} checks</dd>
{{#let
(or
(if (eq status 'critical') 'failing')
(if (eq status 'warning') 'with a warning')
status
)
as |humanized|}}
<dd>
{{or
(if (eq checks.length 0) (concat 'No ' @type ' checks'))
(if (eq checks.length @items.length) (concat 'All ' @type ' checks ' humanized))
(concat checks.length '/' @items.length ' ' @type ' checks ' humanized)
}}
</dd>
{{/let}}
</dl>
{{else}}
{{#if (eq this.healthCheck.count @items.length)}}
<dl class={{this.healthCheck.check}}>
<dt>
<Tooltip>
{{capitalize @type}} Checks
</Tooltip>
</dt>
<dd>All {{@type}} checks {{this.healthCheck.status}}</dd>
</dl>
{{else}}
<dl class={{this.healthCheck.check}}>
<dt>
<Tooltip>
{{capitalize @type}} Checks
</Tooltip>
</dt>
<dd>{{this.healthCheck.count}}/{{@items.length}} {{@type}} checks {{this.healthCheck.status}}</dd>
</dl>
{{/if}}
{{/if}}
{{/let}}
{{/let}}
{{/let}}

View File

@ -1,50 +0,0 @@
import Component from '@glimmer/component';
export default class ConsulInstanceChecks extends Component {
get healthCheck() {
let ChecksCritical = 0;
let ChecksWarning = 0;
let ChecksPassing = 0;
this.args.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

@ -0,0 +1,24 @@
.consul-instance-checks {
& {
@extend %horizontal-kv-list;
}
dt::before {
@extend %as-pseudo;
}
&.passing dt::before {
@extend %with-check-circle-fill-mask;
color: rgb(var(--tone-green-500));
}
&.warning dt::before {
@extend %with-alert-triangle-mask;
color: rgb(var(--tone-orange-500));
}
&.critical dt::before {
@extend %with-cancel-square-fill-mask;
color: rgb(var(--tone-red-500));
}
&.empty dt::before {
@extend %with-minus-square-fill-mask;
color: rgb(var(--tone-gray-500));
}
}

View File

@ -0,0 +1,83 @@
# Consul::ServiceInstance::List
A presentational component to show a list of Service Instances. The component
will display slightly different information based on whether you want the list
in a `@node` view or not.
Please note: A nice refactor here would be to let the node information be
added from the outside via a slot. Once component is using the new row based
scrollpane this can probably be looked at.
```hbs preview-template
<figure>
<figcaption>With no node given (i.e. from within a Service)</figcaption>
<DataSource
@src={{uri '/${partition}/${nspace}/${dc}/service-instances/for-service/${name}'
(hash
partition=''
nspace=''
dc='dc-1'
name='service-name'
)
}}
@onchange={{action (mut this.items) value="data"}}
/>
<Consul::ServiceInstance::List
@routeName="dc.services.show"
@items={{this.items}}
@proxies={{array}}
/>
</figure>
```
Component configured to show a list from within a node page.
```hbs preview-template
<figure>
<figcaption>With a node given (i.e. from within a Node)</figcaption>
<DataSource
@src={{uri '/${partition}/${nspace}/${dc}/node/${name}'
(hash
partition=''
nspace=''
dc='dc-1'
name='node-0'
)
}}
@onchange={{action (mut this.node) value="data"}}
/>
<DataSource
@src={{uri '/${partition}/${nspace}/${dc}/service-instances/for-service/${name}'
(hash
partition=''
nspace=''
dc='dc-1'
name='service-name'
)
}}
@onchange={{action (mut this.items) value="data"}}
/>
<Consul::ServiceInstance::List
@routeName="dc.services.show"
@node={{this.node}}
@items={{this.items}}
@proxies={{array}}
/>
</figure>
```
## Arguments
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `items` | `Array` | | An array of Consul Service Instances |
| `proxies` | `Array` | | An array of Consul Proxy Service Instances from the same Service |
| `routeName` | `String` | | An Ember routeName for where clicking a row takes you |
| `node` | `(Object | boolean)` | | Whether to show a node like view |
## See
- [Template Source Code](./index.hbs)
---

View File

@ -1,3 +1,8 @@
{{#let
(to-hash @proxies "Service.Proxy.DestinationServiceID")
as |proxies|}}
<ListCollection
class="consul-service-instance-list"
...attributes
@ -15,15 +20,45 @@ as |item index|>
{{/if}}
</BlockSlot>
<BlockSlot @name="details">
{{#let
(get proxies item.Service.ID)
as |proxy|}}
{{#let
(merge-checks
(array
item.Checks
(or
proxy.Checks
(array)
)
)
)
as |checks|}}
{{#if @node}}
<Consul::ExternalSource @item={{item.Service}} />
<Consul::InstanceChecks @type="service" @items={{item.Checks}} />
<Consul::ExternalSource
@item={{item.Service}}
/>
<Consul::InstanceChecks
@type="service"
@items={{filter-by 'ServiceID' '' checks}}
/>
{{else}}
<Consul::ExternalSource @item={{item.Service}} />
<Consul::InstanceChecks @type="service" @items={{filter-by 'ServiceID' '' item.Checks}} />
<Consul::InstanceChecks @type="node" @items={{reject-by 'ServiceID' '' item.Checks}} />
<Consul::ExternalSource
@item={{item.Service}}
/>
<Consul::InstanceChecks
@type="service"
@items={{filter-by 'ServiceID' '' checks}}
/>
<Consul::InstanceChecks
@type="node"
@items={{reject-by 'ServiceID' '' checks}}
/>
{{/if}}
{{#if item.ProxyInstance}}
{{#if proxy}}
<dl class="mesh">
<dt>
<Tooltip>
@ -73,5 +108,10 @@ as |item index|>
</dl>
{{/if}}
<TagList @item={{item.Service}} />
{{/let}}
{{/let}}
</BlockSlot>
</ListCollection>
</ListCollection>
{{/let}}

View File

@ -0,0 +1,12 @@
import { helper } from '@ember/component/helper';
import { get } from '@ember/object';
export default helper(([arrayLike = [], prop], hash) => {
if (!Array.isArray(arrayLike)) {
arrayLike = arrayLike.toArray();
}
return arrayLike.reduce((prev, item, i) => {
prev[get(item, prop)] = item;
return prev;
}, {});
});

View File

@ -28,6 +28,8 @@ export default class Node extends Model {
// MeshServiceInstances are all instances that aren't connect-proxies this
// currently includes gateways as these need to show up in listings
@filter('Services', item => item.Service.Kind !== 'connect-proxy') MeshServiceInstances;
// ProxyServiceInstances are all instances that are connect-proxies
@filter('Services', item => item.Service.Kind === 'connect-proxy') ProxyServiceInstances;
@computed('Checks.[]', 'ChecksCritical', 'ChecksPassing', 'ChecksWarning')
get Status() {

View File

@ -80,6 +80,7 @@
@import 'consul-ui/components/consul/upstream/list';
@import 'consul-ui/components/consul/upstream-instance/list';
@import 'consul-ui/components/consul/health-check/list';
@import 'consul-ui/components/consul/instance-checks';
@import 'consul-ui/components/consul/exposed-path/list';
@import 'consul-ui/components/consul/external-source';
@import 'consul-ui/components/consul/kind';

View File

@ -28,8 +28,9 @@ as |route|>
)
route.model.item.MeshServiceInstances
route.model.item.ProxyServiceInstances
as |sort filters items|}}
as |sort filters items proxies|}}
<div class="tab-section">
{{#if (gt items.length 0) }}
<input type="checkbox" id="toolbar-toggle" />
@ -44,7 +45,6 @@ as |route|>
@filter={{filters}}
/>
{{/if}}
{{! filter out any sidecar proxies }}
<DataCollection
@type="service-instance"
@sort={{sort.value}}
@ -54,10 +54,10 @@ as |route|>
as |collection|>
<collection.Collection>
<Consul::ServiceInstance::List
@node={{item}}
@node={{route.model.item}}
@routeName="dc.services.show"
@items={{collection.items}}
@checks={{checks}}
@proxies={{proxies}}
/>
</collection.Collection>
<collection.Empty>

View File

@ -214,6 +214,7 @@ as |items item dc|}}
@name={{routeName}}
@model={{assign (hash
items=items
proxies=proxies
item=item
tabs=tabs
) route.model}}

View File

@ -29,8 +29,9 @@ as |route|>
)
route.model.items
route.model.proxies.firstObject
as |sort filters items|}}
as |sort filters items proxyMeta|}}
{{#if (gt items.length 0) }}
<input type="checkbox" id="toolbar-toggle" />
<Consul::ServiceInstance::SearchBar
@ -43,6 +44,19 @@ as |sort filters items|}}
@filter={{filters}}
/>
{{/if}}
{{#if proxyMeta.ServiceName}}
<DataSource
@src={{uri '/${partition}/${nspace}/${dc}/service-instances/for-service/${name}'
(hash
partition=route.params.partition
nspace=route.params.nspace
dc=route.params.dc
name=proxyMeta.ServiceName
)
}}
@onchange={{action (mut proxies) value="data"}}
/>
{{/if}}
{{! Service > Service Instance view doesn't require filtering of proxies }}
<DataCollection
@type="service-instance"
@ -55,6 +69,7 @@ as |sort filters items|}}
<Consul::ServiceInstance::List
@routeName="dc.services.instance"
@items={{collection.items}}
@proxies={{proxies}}
/>
</collection.Collection>
<collection.Empty>

View File

@ -11,14 +11,14 @@ Feature: dc / services / instances / navigation
And 3 instance models from yaml
---
- Service:
Name: service-0
Service: service-0
ID: service-a
Node:
Node: node-0
Checks:
- Status: critical
- Service:
Name: service-0
Service: service-0
ID: service-b
Node:
Node: node-0
@ -29,7 +29,7 @@ Feature: dc / services / instances / navigation
# proxy on request.0 from yaml', 'And 1 proxy on request.1 from yaml' or
# similar
- Service:
Name: service-0-proxy
Service: service-0-proxy
ID: service-a-proxy
Node:
Node: node-0