mirror of https://github.com/status-im/consul.git
Merge pull request #14986 from hashicorp/ui/feature/filter-node-healthchecks-agentless
UI: filter node healthchecks on agentless service instances
This commit is contained in:
commit
0c8563f060
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:feature
|
||||||
|
ui: Filter out node health checks on agentless service instances
|
||||||
|
```
|
|
@ -1,11 +1,12 @@
|
||||||
export default (collection, text) =>
|
export default (collection, text) =>
|
||||||
(scope = '.consul-health-check-list') => {
|
(scope = '.consul-health-check-list') => {
|
||||||
return {
|
return collection({
|
||||||
scope,
|
scope,
|
||||||
item: collection('li', {
|
itemScope: 'li',
|
||||||
name: text('header h3'),
|
item: {
|
||||||
|
name: text('header h2'),
|
||||||
type: text('[data-health-check-type]'),
|
type: text('[data-health-check-type]'),
|
||||||
exposed: text('[data-test-exposed]'),
|
exposed: text('[data-test-exposed]'),
|
||||||
}),
|
},
|
||||||
};
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import Controller from '@ember/controller';
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
|
||||||
|
export default class HealthChecksController extends Controller {
|
||||||
|
@action
|
||||||
|
syntheticNodeSearchPropertyFilter(item, searchProperty) {
|
||||||
|
return !(item.Node.Meta?.['synthetic-node'] && searchProperty === 'Node');
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
syntheticNodeHealthCheckFilter(item, healthcheck, index, list) {
|
||||||
|
return !(item.Node.Meta?.['synthetic-node'] && healthcheck?.Kind === 'node');
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,93 +1,90 @@
|
||||||
<Route
|
<Route @name={{routeName}} as |route|>
|
||||||
@name={{routeName}}
|
|
||||||
as |route|>
|
|
||||||
{{#let
|
{{#let
|
||||||
|
(filter (action 'syntheticNodeSearchPropertyFilter' route.model.item) searchProperties)
|
||||||
(hash
|
as |filteredSearchProperties|
|
||||||
value=(or sortBy "Status:asc")
|
}}
|
||||||
change=(action (mut sortBy) value="target.selected")
|
{{#let
|
||||||
)
|
(hash value=(or sortBy 'Status:asc') change=(action (mut sortBy) value='target.selected'))
|
||||||
|
(hash
|
||||||
(hash
|
status=(hash
|
||||||
status=(hash
|
value=(if status (split status ',') undefined)
|
||||||
value=(if status (split status ',') undefined)
|
change=(action (mut status) value='target.selectedItems')
|
||||||
change=(action (mut status) value="target.selectedItems")
|
)
|
||||||
)
|
check=(hash
|
||||||
check=(hash
|
value=(if check (split check ',') undefined)
|
||||||
value=(if check (split check ',') undefined)
|
change=(action (mut check) value='target.selectedItems')
|
||||||
change=(action (mut check) value="target.selectedItems")
|
)
|
||||||
)
|
searchproperty=(hash
|
||||||
searchproperty=(hash
|
value=(if
|
||||||
value=(if (not-eq searchproperty undefined)
|
(not-eq searchproperty undefined) (split searchproperty ',') filteredSearchProperties
|
||||||
(split searchproperty ',')
|
)
|
||||||
searchProperties
|
change=(action (mut searchproperty) value='target.selectedItems')
|
||||||
|
default=filteredSearchProperties
|
||||||
)
|
)
|
||||||
change=(action (mut searchproperty) value="target.selectedItems")
|
|
||||||
default=searchProperties
|
|
||||||
)
|
)
|
||||||
)
|
(filter
|
||||||
|
(action 'syntheticNodeHealthCheckFilter' route.model.item)
|
||||||
|
(merge-checks
|
||||||
|
(array route.model.item.Checks route.model.proxy.Checks)
|
||||||
|
route.model.proxy.ServiceProxy.Expose.Checks
|
||||||
|
)
|
||||||
|
)
|
||||||
|
as |sort filters items|
|
||||||
|
}}
|
||||||
|
<div class='tab-section'>
|
||||||
|
|
||||||
(merge-checks (array route.model.item.Checks route.model.proxy.Checks) route.model.proxy.ServiceProxy.Expose.Checks)
|
{{#if (gt items.length 0)}}
|
||||||
|
<input type='checkbox' id='toolbar-toggle' />
|
||||||
as |sort filters items|}}
|
<Consul::HealthCheck::SearchBar
|
||||||
<div class="tab-section">
|
@search={{search}}
|
||||||
|
@onsearch={{action (mut search) value='target.value'}}
|
||||||
{{#if (gt items.length 0) }}
|
@sort={{sort}}
|
||||||
<input type="checkbox" id="toolbar-toggle" />
|
@filter={{filters}}
|
||||||
<Consul::HealthCheck::SearchBar
|
|
||||||
@search={{search}}
|
|
||||||
@onsearch={{action (mut search) value="target.value"}}
|
|
||||||
|
|
||||||
@sort={{sort}}
|
|
||||||
|
|
||||||
@filter={{filters}}
|
|
||||||
/>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#let (find-by "Type" "serf" items) as |serf|}}
|
|
||||||
{{#if (and serf (eq serf.Status "critical"))}}
|
|
||||||
<Notice
|
|
||||||
data-test-critical-serf-notice
|
|
||||||
@type="warning"
|
|
||||||
as |notice|>
|
|
||||||
<notice.Header>
|
|
||||||
<h2>
|
|
||||||
{{t "routes.dc.services.instance.healthchecks.critical-serf-notice.header"}}
|
|
||||||
</h2>
|
|
||||||
</notice.Header>
|
|
||||||
<notice.Body>
|
|
||||||
{{t
|
|
||||||
"routes.dc.services.instance.healthchecks.critical-serf-notice.body"
|
|
||||||
htmlSafe=true
|
|
||||||
}}
|
|
||||||
</notice.Body>
|
|
||||||
</Notice>
|
|
||||||
{{/if}}
|
|
||||||
{{/let}}
|
|
||||||
<DataCollection
|
|
||||||
@type="health-check"
|
|
||||||
@sort={{sort.value}}
|
|
||||||
@filters={{filters}}
|
|
||||||
@search={{search}}
|
|
||||||
@items={{items}}
|
|
||||||
as |collection|>
|
|
||||||
<collection.Collection>
|
|
||||||
<Consul::HealthCheck::List
|
|
||||||
@items={{collection.items}}
|
|
||||||
/>
|
/>
|
||||||
</collection.Collection>
|
{{/if}}
|
||||||
<collection.Empty>
|
|
||||||
<EmptyState>
|
{{#let (find-by 'Type' 'serf' items) as |serf|}}
|
||||||
<BlockSlot @name="body">
|
{{#if (and serf (eq serf.Status 'critical'))}}
|
||||||
{{t "routes.dc.services.instance.healthchecks.empty"
|
<Notice data-test-critical-serf-notice @type='warning' as |notice|>
|
||||||
items=items.length
|
<notice.Header>
|
||||||
htmlSafe=true
|
<h2>
|
||||||
}}
|
{{t 'routes.dc.services.instance.healthchecks.critical-serf-notice.header'}}
|
||||||
</BlockSlot>
|
</h2>
|
||||||
</EmptyState>
|
</notice.Header>
|
||||||
|
<notice.Body>
|
||||||
|
{{t
|
||||||
|
'routes.dc.services.instance.healthchecks.critical-serf-notice.body'
|
||||||
|
htmlSafe=true
|
||||||
|
}}
|
||||||
|
</notice.Body>
|
||||||
|
</Notice>
|
||||||
|
{{/if}}
|
||||||
|
{{/let}}
|
||||||
|
<DataCollection
|
||||||
|
@type='health-check'
|
||||||
|
@sort={{sort.value}}
|
||||||
|
@filters={{filters}}
|
||||||
|
@search={{search}}
|
||||||
|
@items={{items}}
|
||||||
|
as |collection|
|
||||||
|
>
|
||||||
|
<collection.Collection>
|
||||||
|
<Consul::HealthCheck::List @items={{collection.items}} />
|
||||||
|
</collection.Collection>
|
||||||
|
<collection.Empty>
|
||||||
|
<EmptyState>
|
||||||
|
<BlockSlot @name='body'>
|
||||||
|
{{t
|
||||||
|
'routes.dc.services.instance.healthchecks.empty'
|
||||||
|
items=items.length
|
||||||
|
htmlSafe=true
|
||||||
|
}}
|
||||||
|
</BlockSlot>
|
||||||
|
</EmptyState>
|
||||||
</collection.Empty>
|
</collection.Empty>
|
||||||
</DataCollection>
|
</DataCollection>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
{{/let}}
|
||||||
{{/let}}
|
{{/let}}
|
||||||
</Route>
|
</Route>
|
||||||
|
|
|
@ -64,3 +64,68 @@ Feature: dc / services / instances / health-checks
|
||||||
Then the url should be /dc1/services/service-0/instances/another-node/service-1-with-id/health-checks
|
Then the url should be /dc1/services/service-0/instances/another-node/service-1-with-id/health-checks
|
||||||
And I see healthChecksIsSelected on the tabs
|
And I see healthChecksIsSelected on the tabs
|
||||||
And I don't see criticalSerfNotice on the tabs.healthChecksTab
|
And I don't see criticalSerfNotice on the tabs.healthChecksTab
|
||||||
|
Scenario: Node health check should be hidden on agentless service instances
|
||||||
|
Given 1 instance model from yaml
|
||||||
|
---
|
||||||
|
- Service:
|
||||||
|
ID: service-0-with-id
|
||||||
|
Node:
|
||||||
|
Node: another-node
|
||||||
|
Meta:
|
||||||
|
synthetic-node: true
|
||||||
|
Checks:
|
||||||
|
- Type: ''
|
||||||
|
Name: Node Health Check
|
||||||
|
CheckID: serfHealth
|
||||||
|
ServiceID: ''
|
||||||
|
Status: passing
|
||||||
|
Output: Agent alive and reachable
|
||||||
|
- Type: ''
|
||||||
|
Name: Serf Health Status
|
||||||
|
CheckID: serfHealth
|
||||||
|
Status: critical
|
||||||
|
Output: ouch
|
||||||
|
---
|
||||||
|
When I visit the instance page for yaml
|
||||||
|
---
|
||||||
|
dc: dc1
|
||||||
|
service: service-0
|
||||||
|
node: another-node
|
||||||
|
id: service-0-with-id
|
||||||
|
---
|
||||||
|
Then the url should be /dc1/services/service-0/instances/another-node/service-0-with-id/health-checks
|
||||||
|
And I see healthChecksIsSelected on the tabs
|
||||||
|
And I see 1 healthCheck model on the tabs.healthChecksTab component
|
||||||
|
And I see 1 healthCheck model with the name "Serf Health Status"
|
||||||
|
Scenario: Node health checks should be visible on non-agentless service instances
|
||||||
|
Given 1 instance model from yaml
|
||||||
|
---
|
||||||
|
- Service:
|
||||||
|
ID: service-0-with-id
|
||||||
|
Node:
|
||||||
|
Node: another-node
|
||||||
|
Checks:
|
||||||
|
- Type: ''
|
||||||
|
Name: Node Health Check
|
||||||
|
CheckID: serfHealth
|
||||||
|
ServiceID: ''
|
||||||
|
Status: passing
|
||||||
|
Output: Agent alive and reachable
|
||||||
|
- Type: ''
|
||||||
|
Name: Serf Health Status
|
||||||
|
CheckID: serfHealth
|
||||||
|
Status: critical
|
||||||
|
Output: ouch
|
||||||
|
---
|
||||||
|
When I visit the instance page for yaml
|
||||||
|
---
|
||||||
|
dc: dc1
|
||||||
|
service: service-0
|
||||||
|
node: another-node
|
||||||
|
id: service-0-with-id
|
||||||
|
---
|
||||||
|
Then the url should be /dc1/services/service-0/instances/another-node/service-0-with-id/health-checks
|
||||||
|
And I see healthChecksIsSelected on the tabs
|
||||||
|
And I see 2 healthCheck models on the tabs.healthChecksTab component
|
||||||
|
And I see 1 healthCheck model with the name "Serf Health Status"
|
||||||
|
And I see 1 healthCheck model with the name "Node Health Check"
|
||||||
|
|
|
@ -75,7 +75,7 @@ Feature: dc / services / instances / show: Show Service Instance
|
||||||
|
|
||||||
And I don't see upstreams on the tabs
|
And I don't see upstreams on the tabs
|
||||||
And I see healthChecksIsSelected on the tabs
|
And I see healthChecksIsSelected on the tabs
|
||||||
And I see 6 of the checks object
|
And I see 6 healthCheck models
|
||||||
|
|
||||||
When I click tags&Meta on the tabs
|
When I click tags&Meta on the tabs
|
||||||
And I see tags&MetaIsSelected on the tabs
|
And I see tags&MetaIsSelected on the tabs
|
||||||
|
|
|
@ -1,8 +1,25 @@
|
||||||
export default function (scenario, assert, find, currentPage, pauseUntil, pluralize) {
|
export default function (scenario, assert, find, currentPage, pauseUntil, pluralize) {
|
||||||
|
function getModelItems(model, component) {
|
||||||
|
let obj;
|
||||||
|
if (component) {
|
||||||
|
obj = find(component);
|
||||||
|
} else {
|
||||||
|
obj = currentPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
let found = obj[pluralize(model)];
|
||||||
|
|
||||||
|
if (typeof found === 'function') {
|
||||||
|
found = found();
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
scenario
|
scenario
|
||||||
.then('pause until I see $number $model model[s]?', function (num, model) {
|
.then('pause until I see $number $model model[s]?', function (num, model) {
|
||||||
return pauseUntil(function (resolve, reject, retry) {
|
return pauseUntil(function (resolve, reject, retry) {
|
||||||
const len = currentPage()[pluralize(model)].filter(function (item) {
|
const len = getModelItems(model).filter(function (item) {
|
||||||
return item.isVisible;
|
return item.isVisible;
|
||||||
}).length;
|
}).length;
|
||||||
if (len === num) {
|
if (len === num) {
|
||||||
|
@ -15,8 +32,7 @@ export default function (scenario, assert, find, currentPage, pauseUntil, plural
|
||||||
'pause until I see $number $model model[s]? on the $component component',
|
'pause until I see $number $model model[s]? on the $component component',
|
||||||
function (num, model, component) {
|
function (num, model, component) {
|
||||||
return pauseUntil(function (resolve, reject, retry) {
|
return pauseUntil(function (resolve, reject, retry) {
|
||||||
const obj = find(component);
|
const len = getModelItems(model, component).filter(function (item) {
|
||||||
const len = obj[pluralize(model)].filter(function (item) {
|
|
||||||
return item.isVisible;
|
return item.isVisible;
|
||||||
}).length;
|
}).length;
|
||||||
if (len === num) {
|
if (len === num) {
|
||||||
|
@ -27,7 +43,7 @@ export default function (scenario, assert, find, currentPage, pauseUntil, plural
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then(['I see $num $model model[s]?'], function (num, model) {
|
.then(['I see $num $model model[s]?'], function (num, model) {
|
||||||
const len = currentPage()[pluralize(model)].filter(function (item) {
|
const len = getModelItems(model).filter(function (item) {
|
||||||
return item.isVisible;
|
return item.isVisible;
|
||||||
}).length;
|
}).length;
|
||||||
assert.equal(len, num, `Expected ${num} ${pluralize(model)}, saw ${len}`);
|
assert.equal(len, num, `Expected ${num} ${pluralize(model)}, saw ${len}`);
|
||||||
|
@ -35,15 +51,13 @@ export default function (scenario, assert, find, currentPage, pauseUntil, plural
|
||||||
.then(
|
.then(
|
||||||
['I see $num $model model[s]? on the $component component'],
|
['I see $num $model model[s]? on the $component component'],
|
||||||
function (num, model, component) {
|
function (num, model, component) {
|
||||||
const obj = find(component);
|
const len = getModelItems(model, component).filter(function (item) {
|
||||||
const len = obj[pluralize(model)].filter(function (item) {
|
|
||||||
return item.isVisible;
|
return item.isVisible;
|
||||||
}).length;
|
}).length;
|
||||||
|
|
||||||
assert.equal(len, num, `Expected ${num} ${pluralize(model)}, saw ${len}`);
|
assert.equal(len, num, `Expected ${num} ${pluralize(model)}, saw ${len}`);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
// TODO: I${ dont } see
|
|
||||||
.then(
|
.then(
|
||||||
[`I see $num $model model[s]? with the $property "$value"`],
|
[`I see $num $model model[s]? with the $property "$value"`],
|
||||||
function (
|
function (
|
||||||
|
@ -53,7 +67,7 @@ export default function (scenario, assert, find, currentPage, pauseUntil, plural
|
||||||
property,
|
property,
|
||||||
value
|
value
|
||||||
) {
|
) {
|
||||||
const len = currentPage()[pluralize(model)].filter(function (item) {
|
const len = getModelItems(model).filter(function (item) {
|
||||||
if (item.isVisible) {
|
if (item.isVisible) {
|
||||||
let prop = item[property];
|
let prop = item[property];
|
||||||
// cope with pageObjects that can have a multiple: true
|
// cope with pageObjects that can have a multiple: true
|
||||||
|
|
Loading…
Reference in New Issue