Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ui: Leader icon for node listing view #6265

Merged
merged 8 commits into from
Aug 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions ui-v2/app/adapters/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,38 @@ export default Adapter.extend({
}
return this.appendURL('internal/ui/node', [query.id], this.cleanQuery(query));
},
urlForRequest: function({ type, snapshot, requestType }) {
switch (requestType) {
case 'queryLeader':
return this.urlForQueryLeader(snapshot, type.modelName);
}
return this._super(...arguments);
},
urlForQueryLeader: function(query, modelName) {
// https://www.consul.io/api/status.html#get-raft-leader
return this.appendURL('status/leader', [], this.cleanQuery(query));
},
isQueryLeader: function(url, method) {
return url.pathname === this.parseURL(this.urlForQueryLeader({})).pathname;
},
queryLeader: function(store, modelClass, id, snapshot) {
const params = {
store: store,
type: modelClass,
id: id,
snapshot: snapshot,
requestType: 'queryLeader',
};
// _requestFor is private... but these methods aren't, until they disappear..
const request = {
method: this.methodForRequest(params),
url: this.urlForRequest(params),
headers: this.headersForRequest(params),
data: this.dataForRequest(params),
};
// TODO: private..
return this._makeRequest(request);
},
handleBatchResponse: function(url, response, primary, slug) {
const dc = url.searchParams.get(API_DATACENTER_KEY) || '';
return response.map((item, i, arr) => {
Expand All @@ -41,7 +73,21 @@ export default Adapter.extend({
const method = requestData.method;
if (status === HTTP_OK) {
const url = this.parseURL(requestData.url);
let temp, port, address;
switch (true) {
case this.isQueryLeader(url, method):
// This response is just an ip:port like `"10.0.0.1:8000"`
// split it and make it look like a `C`onsul.`R`esponse
// popping off the end for ports should cover us for IPv6 addresses
// as we should always get a `address:port` or `[a:dd:re:ss]:port` combo
temp = response.split(':');
port = temp.pop();
address = temp.join(':');
response = {
Address: address,
Port: port,
};
break;
case this.isQueryRecord(url, method):
response = this.handleSingleResponse(url, fillSlug(response), PRIMARY_KEY, SLUG_KEY);
break;
Expand Down
4 changes: 3 additions & 1 deletion ui-v2/app/routes/dc/nodes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ export default Route.extend({
},
},
model: function(params) {
const dc = this.modelFor('dc').dc.Name;
return hash({
items: get(this, 'repo').findAllByDatacenter(this.modelFor('dc').dc.Name),
items: get(this, 'repo').findAllByDatacenter(dc),
leader: get(this, 'repo').findByLeader(dc),
});
},
setupController: function(controller, model) {
Expand Down
8 changes: 8 additions & 0 deletions ui-v2/app/services/repository/node.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import RepositoryService from 'consul-ui/services/repository';
import { inject as service } from '@ember/service';
import { get } from '@ember/object';

const modelName = 'node';
export default RepositoryService.extend({
coordinates: service('repository/coordinate'),
getModelName: function() {
return modelName;
},
findByLeader: function(dc) {
const query = {
dc: dc,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
dc: dc,
dc,

Copy link
Contributor Author

@johncowen johncowen Aug 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @gregone ,

Thanks for looking at this! I was thinking of merging without doing this suggestion, reasons being:

Most of the Consul UI codebase is ES5-y (think ember 2.18 timescales here, 2-3 years back), we generally don't do this kind of assignment (I think the only ES6-y thing we use is the Object.assign-like {...{}, ...{}} which was only because we preferred that to having to use the ember provided assign)

Other examples specifically in Repositories are:

.self(this.getModelName(), {
secret: secret,
dc: dc,
})

const query = {
id: node,
dc: dc,
};

const query = {
id: slug,
dc: dc,
};

const query = {
id: key,
dc: dc,

..but there are a few more around and about.

I can completely see where you are coming from, right now it's one of those consistency/context things. Once we get past ember 3.1* we'll think about moving up a notch, and if we do we'll probably skim the entire codebase in one go so everything is consistent.

Anyway, let me know what you think, happy to add this before merging if you feel strongly about it.

Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @gregone

We are going to try and get this into our next release, so I'm going to merge this without the suggestion, hope that's ok, thanks for reviewing!

};
return get(this, 'store').queryLeader(this.getModelName(), query);
},
});
2 changes: 1 addition & 1 deletion ui-v2/app/services/repository/type/event-source.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const createProxy = function(repo, find, settings, cache, serialize = JSON.strin
type: 'message',
data: result,
};
const meta = get(event.data || {}, 'meta');
const meta = get(event.data || {}, 'meta') || {};
if (typeof meta.date !== 'undefined') {
// unload anything older than our current sync date/time
store.peekAll(repo.getModelName()).forEach(function(item) {
Expand Down
5 changes: 5 additions & 0 deletions ui-v2/app/services/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ export default Store.extend({
const adapter = this.adapterFor(modelName);
return adapter.self(this, { modelName: modelName }, token);
},
queryLeader: function(modelName, query) {
// TODO: no normalization, type it properly for the moment
const adapter = this.adapterFor(modelName);
return adapter.queryLeader(this, { modelName: modelName }, null, query);
},
});
5 changes: 2 additions & 3 deletions ui-v2/app/styles/components/healthchecked-resource.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
.healthchecked-resource > div {
@extend %stats-card;
}

%tooltip-below::after {
top: calc(100% - 8px);
bottom: auto;
Expand All @@ -12,6 +11,8 @@
%tooltip-below::before {
top: calc(100% + 4px);
bottom: auto;
/*TODO: This should probably go into base*/
line-height: 1em;
}
%tooltip-left::before {
right: 0;
Expand All @@ -21,8 +22,6 @@
}
%stats-card-icon {
@extend %tooltip-below;
/*TODO: This should probably go into base*/
line-height: 1em;
}
%stats-card-icon:first-child::before {
right: 0;
Expand Down
2 changes: 1 addition & 1 deletion ui-v2/app/templates/components/healthchecked-resource.hbs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{{#stats-card}}
{{#block-slot 'icon'}}{{#if false}}<span data-tooltip="Leader">Leader</span>{{/if}}{{/block-slot}}
{{#block-slot 'icon'}}{{yield}}{{/block-slot}}
{{#block-slot 'mini-stat'}}
{{#if (eq checks.length 0)}}
<span class="zero" data-tooltip="This node has no registered healthchecks">{{checks.length}}</span>
Expand Down
124 changes: 68 additions & 56 deletions ui-v2/app/templates/dc/nodes/index.hbs
Original file line number Diff line number Diff line change
@@ -1,72 +1,84 @@
{{#app-view class="node list"}}
{{#block-slot 'header'}}
<h1>
Nodes <em>{{format-number items.length}} total</em>
</h1>
<label for="toolbar-toggle"></label>
{{/block-slot}}
{{#block-slot 'toolbar'}}
{{#block-slot 'header'}}
<h1>
Nodes <em>{{format-number items.length}} total</em>
</h1>
<label for="toolbar-toggle"></label>
{{/block-slot}}
{{#block-slot 'toolbar'}}
{{#if (gt items.length 0) }}
{{catalog-filter searchable=(array searchableHealthy searchableUnhealthy) search=s status=filters.status onchange=(action 'filter')}}
{{catalog-filter searchable=(array searchableHealthy searchableUnhealthy) search=s status=filters.status onchange=(action 'filter')}}
{{/if}}
{{/block-slot}}
{{#block-slot 'content'}}
{{/block-slot}}
{{#block-slot 'content'}}
{{#if (gt unhealthy.length 0) }}
<div class="unhealthy">
<h2>Unhealthy Nodes</h2>
<div>
{{! think about 2 differing views here }}
<ul>
{{#changeable-set dispatcher=searchableUnhealthy}}
{{#block-slot 'set' as |unhealthy|}}
{{#each unhealthy as |item|}}
{{healthchecked-resource
tagName='li'
data-test-node=item.Node
href=(href-to 'dc.nodes.show' item.Node)
name=item.Node
address=item.Address
checks=item.Checks
}}
{{/each}}
<div class="unhealthy">
<h2>Unhealthy Nodes</h2>
<div>
{{! think about 2 differing views here }}
<ul>
{{#changeable-set dispatcher=searchableUnhealthy}}
{{#block-slot 'set' as |unhealthy|}}
{{#each unhealthy as |item|}}
{{#healthchecked-resource
tagName='li'
data-test-node=item.Node
href=(href-to 'dc.nodes.show' item.Node)
name=item.Node
address=item.Address
checks=item.Checks
}}
{{#block-slot 'icon'}}
{{#if (eq item.Address leader.Address)}}
<span data-test-leader={{leader.Address}} data-tooltip="Leader">Leader</span>
{{/if}}
{{/block-slot}}
{{#block-slot 'empty'}}
<p>
There are no unhealthy nodes for that search.
</p>
{{/block-slot}}
{{/changeable-set}}
</ul>
</div>
</div>
{{/if}}
{{#if (gt healthy.length 0) }}
<div class="healthy">
<h2>Healthy Nodes</h2>
{{#changeable-set dispatcher=searchableHealthy}}
{{#block-slot 'set' as |healthy|}}
{{#list-collection cellHeight=92 items=healthy as |item index|}}
{{healthchecked-resource
data-test-node=item.Node
href=(href-to 'dc.nodes.show' item.Node)
name=item.Node
address=item.Address
checks=item.Checks
}}
{{/list-collection}}
{{/healthchecked-resource}}
{{/each}}
{{/block-slot}}
{{#block-slot 'empty'}}
<p>
There are no healthy nodes for that search.
There are no unhealthy nodes for that search.
</p>
{{/block-slot}}
{{/changeable-set}}
</ul>
</div>
</div>
{{/if}}
{{#if (gt healthy.length 0) }}
<div class="healthy">
<h2>Healthy Nodes</h2>
{{#changeable-set dispatcher=searchableHealthy}}
{{#block-slot 'set' as |healthy|}}
{{#list-collection cellHeight=92 items=healthy as |item index|}}
{{#healthchecked-resource
data-test-node=item.Node
href=(href-to 'dc.nodes.show' item.Node)
name=item.Node
address=item.Address
checks=item.Checks
}}
{{#block-slot 'icon'}}
{{#if (eq item.Address leader.Address)}}
<span data-test-leader={{leader.Address}} data-tooltip="Leader">Leader</span>
{{/if}}
{{/block-slot}}
{{/healthchecked-resource}}
{{/list-collection}}
{{/block-slot}}
{{#block-slot 'empty'}}
<p>
There are no healthy nodes for that search.
</p>
{{/block-slot}}
{{/changeable-set}}
</div>
{{/if}}
{{#if (and (eq healthy.length 0) (eq unhealthy.length 0)) }}
<p>
There are no nodes.
</p>
<p>
There are no nodes.
</p>
{{/if}}
{{/block-slot}}
{{/block-slot}}
{{/app-view}}
4 changes: 2 additions & 2 deletions ui-v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"base64-js": "^1.3.0",
"broccoli-asset-rev": "^2.4.5",
"chalk": "^2.4.2",
"clipboard": "^2.0.4",
"dart-sass": "^1.14.1",
"ember-ajax": "^3.0.0",
"ember-auto-import": "^1.4.0",
Expand Down Expand Up @@ -103,8 +104,7 @@
"node-sass": "^4.9.3",
"prettier": "^1.10.2",
"svgo": "^1.0.5",
"text-encoding": "^0.6.4",
"clipboard": "^2.0.4"
"text-encoding": "^0.6.4"
},
"engines": {
"node": "^4.5 || 6.* || >= 7.*"
Expand Down
46 changes: 43 additions & 3 deletions ui-v2/tests/acceptance/dc/nodes/index.feature
Original file line number Diff line number Diff line change
@@ -1,11 +1,51 @@
@setupApplicationTest
Feature: Nodes
Scenario:
Feature: dc / nodes / index
Background:
Given 1 datacenter model with the value "dc-1"
And 3 node models
And the url "/v1/status/leader" responds with from yaml
---
body: |
"211.245.86.75:8500"
---
Scenario: Viewing nodes in the listing
Given 3 node models
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
Scenario: Seeing the leader in unhealthy listing
Given 3 node models from yaml
---
- Address: 211.245.86.75
Checks:
- Status: warning
Name: Warning 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 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
18 changes: 18 additions & 0 deletions ui-v2/tests/acceptance/dc/nodes/no-leader.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@setupApplicationTest
Feature: dc / nodes / no-leader
Scenario: Leader hasn't been elected
Given 1 datacenter model with the value "dc-1"
And 3 node models
And the url "/v1/status/leader" responds with from yaml
---
body: |
""
---
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 don't see leader on the nodes

2 changes: 1 addition & 1 deletion ui-v2/tests/acceptance/page-navigation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Feature: Page Navigation
Where:
-----------------------------------------------------------------------
| Link | URL | Endpoint |
| nodes | /dc-1/nodes | /v1/internal/ui/nodes?dc=dc-1 |
| nodes | /dc-1/nodes | /v1/status/leader?dc=dc-1 |
| kvs | /dc-1/kv | /v1/kv/?keys&dc=dc-1&separator=%2F |
| acls | /dc-1/acls/tokens | /v1/acl/tokens?dc=dc-1 |
| intentions | /dc-1/intentions | /v1/connect/intentions?dc=dc-1 |
Expand Down
10 changes: 10 additions & 0 deletions ui-v2/tests/acceptance/steps/dc/nodes/no-leader-steps.js
Original file line number Diff line number Diff line change
@@ -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);
});
}
Loading