ui: Redesign Node list page (#8567)

* Create ConsulNodeList component

* Implement ConsulNodeList and the new Search/Sort to Node List page

* Minor styling fix to align the first icons in composite row

* Fix-up and add tests for the redesigned Node List page

* Add Leader to composite row for Node List page

* Add test for node leader
This commit is contained in:
Kenia 2020-08-28 09:21:03 -04:00 committed by GitHub
parent 145bcdc2bb
commit d8c14b51a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 345 additions and 249 deletions

View File

@ -0,0 +1,41 @@
{{#if (gt items.length 0)}}
<ListCollection @items={{items}} class="consul-node-list" as |item index|>
<BlockSlot @name="header">
<dl class={{item.Status}}>
<dt>
Health
</dt>
<dd>
<Tooltip @position="top-start">
{{#if (eq 'critical' item.Status)}}
At least one health check on this node is failing.
{{else if (eq 'warning' item.Status)}}
At least one health check on this node has a warning.
{{else if (eq 'passing' item.Status)}}
All health checks are passing.
{{else}}
There are no health checks.
{{/if}}
</Tooltip>
</dd>
</dl>
<a data-test-node href={{href-to "dc.nodes.show" item.Node}}>
{{item.Node}}
</a>
</BlockSlot>
<BlockSlot @name="details">
{{#if (eq item.Address leader.Address)}}
<span class="leader" data-test-leader={{leader.Address}}>Leader</span>
{{/if}}
<dl>
<dt>
<CopyButton
@value={{item.Address}}
@name="Address"
/>
</dt>
<dd>{{item.Address}}</dd>
</dl>
</BlockSlot>
</ListCollection>
{{/if}}

View File

@ -0,0 +1,5 @@
import Component from '@ember/component';
export default Component.extend({
tagName: '',
});

View File

@ -2,26 +2,10 @@ import Controller from '@ember/controller';
export default Controller.extend({
queryParams: {
filterBy: {
as: 'status',
},
sortBy: 'sort',
search: {
as: 'filter',
replace: true,
},
},
actions: {
hasStatus: function(status, checks) {
if (status === '') {
return true;
}
return checks.some(item => item.Status === status);
},
isHealthy: function(checks) {
return !this.actions.isUnhealthy.apply(this, [checks]);
},
isUnhealthy: function(checks) {
return checks.some(item => item.Status === 'critical' || item.Status === 'warning');
},
},
});

View File

@ -23,8 +23,7 @@ export function initialize(application) {
policy: policy(filterable),
role: role(filterable),
kv: kv(filterable),
healthyNode: node(filterable),
unhealthyNode: node(filterable),
node: node(filterable),
serviceInstance: serviceNode(filterable),
nodeservice: nodeService(filterable),
service: service(filterable),

View File

@ -6,6 +6,7 @@ import token from 'consul-ui/sort/comparators/token';
import role from 'consul-ui/sort/comparators/role';
import policy from 'consul-ui/sort/comparators/policy';
import nspace from 'consul-ui/sort/comparators/nspace';
import node from 'consul-ui/sort/comparators/node';
export function initialize(container) {
// Service-less injection using private properties at a per-project level
@ -19,6 +20,7 @@ export function initialize(container) {
role: role(),
policy: policy(),
nspace: nspace(),
node: node(),
};
Sort.reopen({
comparator: function(type) {

View File

@ -1,5 +1,6 @@
import Model from 'ember-data/model';
import attr from 'ember-data/attr';
import { computed } from '@ember/object';
export const PRIMARY_KEY = 'uid';
export const SLUG_KEY = 'ID';
@ -20,4 +21,25 @@ export default Model.extend({
Coord: attr(),
SyncTime: attr('number'),
meta: attr(),
Status: computed('Checks.[]', 'ChecksCritical', 'ChecksPassing', 'ChecksWarning', function() {
switch (true) {
case this.ChecksCritical !== 0:
return 'critical';
case this.ChecksWarning !== 0:
return 'warning';
case this.ChecksPassing !== 0:
return 'passing';
default:
return 'empty';
}
}),
ChecksCritical: computed('Checks.[]', function() {
return this.Checks.filter(item => item.Status === 'critical').length;
}),
ChecksPassing: computed('Checks.[]', function() {
return this.Checks.filter(item => item.Status === 'passing').length;
}),
ChecksWarning: computed('Checks.[]', function() {
return this.Checks.filter(item => item.Status === 'warning').length;
}),
});

View File

@ -6,6 +6,7 @@ export default Route.extend({
repo: service('repository/node'),
data: service('data-source/service'),
queryParams: {
sortBy: 'sort',
search: {
as: 'filter',
replace: true,

View File

@ -0,0 +1,37 @@
export default () => key => {
if (key.startsWith('Status:')) {
return function(serviceA, serviceB) {
const [, dir] = key.split(':');
let a, b;
if (dir === 'asc') {
b = serviceA;
a = serviceB;
} else {
a = serviceA;
b = serviceB;
}
switch (true) {
case a.ChecksCritical > b.ChecksCritical:
return 1;
case a.ChecksCritical < b.ChecksCritical:
return -1;
default:
switch (true) {
case a.ChecksWarning > b.ChecksWarning:
return 1;
case a.ChecksWarning < b.ChecksWarning:
return -1;
default:
switch (true) {
case a.ChecksPassing < b.ChecksPassing:
return 1;
case a.ChecksPassing > b.ChecksPassing:
return -1;
}
}
return 0;
}
};
}
return key;
};

View File

@ -27,5 +27,9 @@
width: 14px;
height: 14px;
margin-right: 2px;
margin-top: 1px;
position: relative;
}
%reduced-pill.leader::before {
margin-right: 4px;
}

View File

@ -34,3 +34,7 @@
@extend %with-gateway-mask;
background-color: $gray-500;
}
%reduced-pill.leader::before {
@extend %with-star-outline-mask;
background-color: $gray-500;
}

View File

@ -156,7 +156,7 @@ $settings-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 24 24" fi
$source-file-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M13.714 9.143l3.572 3.571-3.572 3.572-.714-1.4 2.143-2.172L13 10.571l.714-1.428zm-3.571 1.4L8 12.714l2.143 2.143-.714 1.429-3.572-3.572L9.43 9.143l.714 1.4zm8.571 10.028H4.43V3.43h10l4.285 4.285v12.857zM15.143 2H4.429C3.643 2 3 2.643 3 3.429V20.57C3 21.357 3.643 22 4.429 22h14.285c.786 0 1.429-.643 1.429-1.429V7l-5-5z" fill="%23000"/></svg>');
$sort-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M0,10.0867585 L6,10.0867585 L6,8.40563206 L0,8.40563206 L0,10.0867585 L0,10.0867585 Z M3,12.4056321 L3,14.0867585 L0,14.0867585 L0,12.4056321 L3,12.4056321 Z M15.1301377,0 L15.1301377,1.68112641 L0,1.68112641 L0,0 L15.1301377,0 Z M13.8692929,4.62309763 L13.8692929,11.8384922 L16.8112641,8.89802258 L18,10.0867585 L13.0287297,15.0580288 L8.05745938,10.0867585 L9.24619526,8.89802258 L12.1881665,11.8393328 L12.1881665,4.62309763 L13.8692929,4.62309763 Z M10.0867585,4.20281603 L10.0867585,5.88394244 L0,5.88394244 L0,4.20281603 L10.0867585,4.20281603 Z" fill="%23000"/></svg>');
$star-fill-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27z" fill="%23000"/></svg>');
$star-outline-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M22 9.24l-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4l-3.76 2.27 1-4.28-3.32-2.88 4.38-.38L12 6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z" fill="%23000"/></svg>');
$star-outline-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 26.5 26" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 13C1 6.37258 6.37258 1 13 1V1C19.6274 1 25 6.37258 25 13V13C25 19.6274 19.6274 25 13 25V25C6.37258 25 1 19.6274 1 13V13Z" fill="none"/><path fill-rule="evenodd" clip-rule="evenodd" d="M13 16.0742L16.605 18.25L15.6483 14.1492L18.8333 11.39L14.6392 11.0342L13 7.16667L11.3608 11.0342L7.16667 11.39L10.3517 14.1492L9.39501 18.25L13 16.0742Z" fill="%23C62A71"/><path d="M13 24C6.92487 24 2 19.0751 2 13H0C0 20.1797 5.8203 26 13 26V24ZM24 13C24 19.0751 19.0751 24 13 24V26C20.1797 26 26 20.1797 26 13H24ZM13 2C19.0751 2 24 6.92487 24 13H26C26 5.8203 20.1797 0 13 0V2ZM13 0C5.8203 0 0 5.8203 0 13H2C2 6.92487 6.92487 2 13 2V0Z" fill="%23E1E4E7"/></svg>');
$star-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="10" height="9" viewBox="0 0 10 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M5 7.196L7.575 8.75l-.683-2.93 2.275-1.97-2.996-.254L5 .833 3.83 3.596.832 3.85l2.275 1.97-.683 2.93z"/></defs><use fill="%239E2159" xlink:href="%23a" fill-rule="evenodd"/></svg>');
$sub-left-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M11.42 9.42L7.83 13H18V4h2v11H7.83l3.59 3.58L10 20l-6-6 6-6 1.42 1.42z" fill="%23000"/></svg>');
$sub-right-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M14 20l-1.42-1.42L16.17 15H4V4h2v9h10.17l-3.59-3.58L14 8l6 6-6 6z" fill="%23000"/></svg>');

View File

@ -44,6 +44,7 @@
}
%composite-row-icon {
margin-right: 6px;
margin-left: -2px;
}
%composite-row-icon dt {
display: none;

View File

@ -6,7 +6,8 @@ td strong {
span.policy-service-identity,
span.policy-node-identity,
.consul-external-source,
.consul-kind {
.consul-kind,
.leader {
@extend %reduced-pill;
}
span.policy-service-identity::before,

View File

@ -1,15 +1,7 @@
{{title 'Nodes'}}
<EventSource @src={{items}} />
{{#let (selectable-key-values
(array "" "All (Any Status)")
(array "critical" "Critical Checks")
(array "warning" "Warning Checks")
(array "passing" "Passing Checks")
selected=filterBy
)
as |filter|
}}
<AppView @class="node list">
{{#let (or sortBy "Node:asc") as |sort|}}
<AppView @class="node list">
<BlockSlot @name="header">
<h1>
Nodes <em>{{format-number items.length}} total</em>
@ -17,121 +9,66 @@
<label for="toolbar-toggle"></label>
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0) }}
{{#if (gt items.length 0) }}
<SearchBar
data-test-catalog-filter
@value={{search}}
@onsearch={{action (mut search) value="target.value"}}
@selected={{filter.selected}}
@options={{filter.items}}
@onchange={{action (mut filterBy) value='target.value'}}
/>
{{/if}}
class="with-sort"
>
<BlockSlot @name="secondary">
<PopoverSelect
@position="right"
@onchange={{action (mut sortBy) value='target.selected'}}
@multiple={{false}}
as |components|>
<BlockSlot @name="selected">
<span>
{{#let (from-entries (array
(array "Node:asc" "A to Z")
(array "Node:desc" "Z to A")
(array "Status:asc" "Unhealthy to Healthy")
(array "Status:desc" "Healthy to Unhealthy")
))
as |selectable|
}}
{{get selectable sort}}
{{/let}}
</span>
</BlockSlot>
<BlockSlot @name="options">
{{#let components.Optgroup components.Option as |Optgroup Option|}}
<Optgroup @label="Name">
<Option @value="Node:asc" @selected={{eq "Node:asc" sort}}>A to Z</Option>
<Option @value="Node:desc" @selected={{eq "Node:desc" sort}}>Z to A</Option>
</Optgroup>
<Optgroup @label="Health Status">
<Option @value="Status:asc" @selected={{eq "Status:asc" sort}}>Unhealthy to Healthy</Option>
<Option @value="Status:desc" @selected={{eq "Status:desc" sort}}>Healthy to Unhealthy</Option>
</Optgroup>
{{/let}}
</BlockSlot>
</PopoverSelect>
</BlockSlot>
</SearchBar>
{{/if}}
</BlockSlot>
<BlockSlot @name="content">
{{#let (filter-by "Checks" (action "isUnhealthy") items) as |unhealthy|}}
{{#if (gt unhealthy.length 0) }}
<div class="unhealthy">
<h2>Unhealthy Nodes</h2>
<div>
{{! think about 2 differing views here }}
<ul>
<ChangeableSet
@dispatcher={{
searchable
'unhealthyNode'
(if (eq filter.selected.key "")
unhealthy
(filter-by "Checks" (action "hasStatus" filter.selected.key) unhealthy filter.selected.key)
)
}}
@terms={{search}}
>
<BlockSlot @name="set" as |unhealthy|>
{{#each unhealthy as |item|}}
<HealthcheckedResource @tagName="li" @data-test-node={{item.Node}} @href={{href-to "dc.nodes.show" item.Node}} @name={{item.Node}} @address={{item.Address}} @checks={{item.Checks}}>
<BlockSlot @name="icon">
{{#if (eq item.Address leader.Address)}}
<span data-test-leader={{leader.Address}} data-tooltip="Leader">Leader</span>
{{/if}}
</BlockSlot>
</HealthcheckedResource>
{{/each}}
</BlockSlot>
<BlockSlot @name="empty">
<li class="empty">
<EmptyState>
<BlockSlot @name="header">
<h2>No nodes found</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
There don't seem to be any nodes matching that search.
</p>
</BlockSlot>
</EmptyState>
</li>
</BlockSlot>
</ChangeableSet>
</ul>
</div>
</div>
{{/if}}
{{/let}}
{{#let (filter-by "Checks" (action "isHealthy") items) as |healthy|}}
{{#if (gt healthy.length 0) }}
<div class="healthy">
<h2>Healthy Nodes</h2>
<ChangeableSet
@dispatcher={{
searchable
'healthyNode'
(if (eq filter.selected.key "")
healthy
(filter-by "Checks" (action "hasStatus" filter.selected.key) healthy filter.selected.key)
)
}}
@terms={{search}}
>
<BlockSlot @name="set" as |healthy|>
<GridCollection @cellHeight={{92}} @items={{healthy}} as |item index|>
<HealthcheckedResource @data-test-node={{item.Node}} @href={{href-to "dc.nodes.show" item.Node}} @name={{item.Node}} @address={{item.Address}} @checks={{item.Checks}}>
<BlockSlot @name="icon">
{{#if (eq item.Address leader.Address)}}
<span data-test-leader={{leader.Address}} data-tooltip="Leader">Leader</span>
{{/if}}
</BlockSlot>
</HealthcheckedResource>
</GridCollection>
{{#let (sort-by (comparator 'node' sort) items) as |sorted|}}
<ChangeableSet @dispatcher={{searchable 'node' sorted}} @terms={{search}}>
<BlockSlot @name="set" as |filtered|>
<ConsulNodeList @items={{filtered}} @leader={{leader}} />
</BlockSlot>
<BlockSlot @name="empty">
<EmptyState>
<BlockSlot @name="header">
<h2>No nodes found</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
There don't seem to be any nodes matching that search.
There don't seem to be any registered nodes, or you may not have access to view nodes yet.
</p>
</BlockSlot>
</EmptyState>
</BlockSlot>
</ChangeableSet>
</div>
{{/if}}
{{/let}}
{{#if (eq items.length 0) }}
<EmptyState @allowLogin={{true}}>
<BlockSlot @name="header">
<h2>Welcome to Nodes</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
There don't seem to be any nodes, or you may not have access to view nodes yet.
</p>
</BlockSlot>
</EmptyState>
{{/if}}
</BlockSlot>
</AppView>
{{/let}}
</BlockSlot>
</AppView>
{{/let}}

View File

@ -3,68 +3,6 @@
# to use the name filter UI also, then they can stay together
@setupApplicationTest
Feature: components / catalog-filter
Scenario: Filtering [Model]
Given 1 datacenter model with the value "dc-1"
And 4 service models from yaml
---
- ChecksPassing: 1
ChecksWarning: 0
ChecksCritical: 0
- ChecksPassing: 0
ChecksWarning: 1
ChecksCritical: 0
- ChecksPassing: 0
ChecksWarning: 0
ChecksCritical: 1
- ChecksPassing: 1
ChecksWarning: 0
ChecksCritical: 0
---
And 4 node models from yaml
---
- Checks:
- Status: passing
- Checks:
- Status: warning
- Checks:
- Status: critical
- Checks:
- Status: passing
---
When I visit the [Page] page for yaml
---
dc: dc-1
---
Then the url should be [Url]
Then I see 4 [Model] models
And I see allIsSelected on the filter
When I click passing on the filter
And I see passingIsSelected on the filter
And I see 2 [Model] models
When I click warning on the filter
And I see warningIsSelected on the filter
And I see 1 [Model] model
When I click critical on the filter
And I see criticalIsSelected on the filter
And I see 1 [Model] model
When I click all on the filter
And I see allIsSelected on the filter
Then I fill in with yaml
---
s: [Model]-0
---
And I see 1 [Model] model with the name "[Model]-0"
Where:
-------------------------------------------------
| Model | Page | Url |
| node | nodes | /dc-1/nodes |
-------------------------------------------------
Scenario: Filtering [Model] in [Page]
Given 1 datacenter model with the value "dc1"
And 1 node model from yaml

View File

@ -20,14 +20,11 @@ Feature: dc / nodes / empty-ids: Hedge for if nodes come in over the API with no
dc: dc-1
---
Then the url should be /dc-1/nodes
Then I see name on the nodes like yaml
Then I see name on the nodes vertically like yaml
---
- name-1
- name-2
- name-3
- name-4
- name-5
@ignore
Scenario: Visually comparing
Then the ".unhealthy" element should look like the "/node_modules/@hashicorp/consul-testing-extras/fixtures/dc/nodes/empty-ids.png" image
---

View File

@ -16,7 +16,7 @@ Feature: dc / nodes / index
Then the url should be /dc-1/nodes
And the title should be "Nodes - Consul"
Then I see 3 node models
Scenario: Seeing the leader in unhealthy listing
Scenario: Seeing the leader in node listing
Given 3 node models from yaml
---
- Address: 211.245.86.75
@ -32,24 +32,7 @@ Feature: dc / nodes / index
---
Then the url should be /dc-1/nodes
Then I see 3 node models
And I see leader on the unHealthyNodes
Scenario: Seeing the leader in healthy listing
Given 3 node models from yaml
---
- Address: 211.245.86.75
Checks:
- Status: passing
Name: Passing check
- Address: 10.0.0.1
- Address: 10.0.0.3
---
When I visit the nodes page for yaml
---
dc: dc-1
---
Then the url should be /dc-1/nodes
Then I see 3 node models
And I see leader on the healthyNodes
And I see leader on the nodes.0
Scenario: Searching the nodes with name and IP address
Given 3 node models from yaml
---

View File

@ -0,0 +1,16 @@
@setupApplicationTest
Feature: dc / nodes / navigation
Scenario: Clicking a node in the listing and back again
Given 1 datacenter model with the value "dc-1"
And 3 node models
When I visit the nodes page for yaml
---
dc: dc-1
---
Then the url should be /dc-1/nodes
And the title should be "Nodes - Consul"
Then I see 3 node models
When I click node on the nodes
And I click "[data-test-back]"
Then the url should be /dc-1/nodes

View File

@ -0,0 +1,73 @@
@setupApplicationTest
Feature: dc / nodes / sorting
Scenario:
Given 1 datacenter model with the value "dc-1"
And 6 node models from yaml
---
- Node: Node-A
Checks:
- Status: critical
- Node: Node-B
Checks:
- Status: passing
- Node: Node-C
Checks:
- Status: warning
- Node: Node-D
Checks:
- Status: critical
- Node: Node-E
Checks:
- Status: critical
- Node: Node-F
Checks:
- Status: warning
---
When I visit the nodes page for yaml
---
dc: dc-1
---
When I click selected on the sort
When I click options.3.button on the sort
Then I see name on the nodes vertically like yaml
---
- Node-B
- Node-C
- Node-F
- Node-A
- Node-D
- Node-E
---
When I click selected on the sort
When I click options.2.button on the sort
Then I see name on the nodes vertically like yaml
---
- Node-A
- Node-D
- Node-E
- Node-C
- Node-F
- Node-B
---
When I click selected on the sort
When I click options.0.button on the sort
Then I see name on the nodes vertically like yaml
---
- Node-A
- Node-B
- Node-C
- Node-D
- Node-E
- Node-F
---
When I click selected on the sort
When I click options.1.button on the sort
Then I see name on the nodes vertically like yaml
---
- Node-F
- Node-E
- Node-D
- Node-C
- Node-B
- Node-A
---

View File

@ -43,7 +43,6 @@ Feature: page-navigation
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Item | Model | URL | Endpoint | Back |
| service | services | /dc-1/services/service-0/instances | /v1/discovery-chain/service-0?dc=dc-1&ns=@namespace | /dc-1/services |
| node | nodes | /dc-1/nodes/node-0/health-checks | /v1/session/node/node-0?dc=dc-1&ns=@namespace | /dc-1/nodes |
| kv | kvs | /dc-1/kv/0-key-value/edit | /v1/session/info/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=dc-1&ns=@namespace | /dc-1/kv |
# | acl | acls | /dc-1/acls/anonymous | /v1/acl/info/anonymous?dc=dc-1 | /dc-1/acls |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -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);
});
}

View File

@ -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);
});
}

View File

@ -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);
});
}

View File

@ -0,0 +1,26 @@
import { module, skip } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | consul-node-list', function(hooks) {
setupRenderingTest(hooks);
skip('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`<ConsulNodeList />`);
assert.equal(this.element.textContent.trim(), '');
// Template block usage:
await render(hbs`
<ConsulNodeList>
template block text
</ConsulNodeList>
`);
assert.equal(this.element.textContent.trim(), 'template block text');
});
});

View File

@ -79,9 +79,6 @@ const tokenList = tokenListFactory(clickable, attribute, collection, deletable);
const authForm = authFormFactory(submitable, clickable, attribute);
const freetextFilter = freetextFilterFactory(triggerable);
const catalogToolbar = searchBarFactory(freetextFilter);
const catalogFilter = searchBarFactory(freetextFilter, () =>
radiogroup('status', ['', 'passing', 'warning', 'critical'])
);
const aclFilter = searchBarFactory(freetextFilter, () =>
radiogroup('type', ['', 'management', 'client'])
);
@ -153,7 +150,7 @@ export default {
service(visitable, attribute, collection, text, consulIntentionList, catalogToolbar, tabgroup)
),
instance: create(instance(visitable, attribute, collection, text, tabgroup)),
nodes: create(nodes(visitable, clickable, attribute, collection, catalogFilter)),
nodes: create(nodes(visitable, text, clickable, attribute, collection, popoverSelect)),
node: create(node(visitable, deletable, clickable, attribute, collection, tabgroup, text)),
kvs: create(kvs(visitable, creatable, consulKvList)),
kv: create(kv(visitable, attribute, submitable, deletable, cancelable, clickable)),

View File

@ -1,14 +1,13 @@
export default function(visitable, clickable, attribute, collection, filter) {
export default function(visitable, text, clickable, attribute, collection, popoverSelect) {
const node = {
name: attribute('data-test-node'),
name: text('[data-test-node]'),
leader: attribute('data-test-leader', '[data-test-leader]'),
node: clickable('header a'),
node: clickable('a'),
};
return {
visit: visitable('/:dc/nodes'),
nodes: collection('[data-test-node]', node),
healthyNodes: collection('.healthy [data-test-node]', node),
unHealthyNodes: collection('.unhealthy [data-test-node]', node),
filter: filter('[data-test-catalog-filter]'),
nodes: collection('.consul-node-list [data-test-list-row]', node),
home: clickable('[data-test-home]'),
sort: popoverSelect(),
};
}