Merge pull request #7235 from hashicorp/ui-staging
ui: UI Release Merge (ui-staging merge)
This commit is contained in:
commit
cabc189b50
|
@ -497,7 +497,6 @@ jobs:
|
|||
environment:
|
||||
EMBER_TEST_PARALLEL: true #enables test parallelization with ember-exam
|
||||
EMBER_TEST_REPORT: test-results/report.xml #outputs test report for CircleCI test summary
|
||||
parallelism: 4
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
|
@ -506,7 +505,7 @@ jobs:
|
|||
at: ui-v2
|
||||
- run:
|
||||
working_directory: ui-v2
|
||||
command: node_modules/ember-cli/bin/ember exam --split=$CIRCLE_NODE_TOTAL --partition=`expr $CIRCLE_NODE_INDEX + 1` --path dist --silent -r xunit
|
||||
command: make test-ci
|
||||
- store_test_results:
|
||||
path: ui-v2/test-results
|
||||
|
||||
|
|
|
@ -38,6 +38,9 @@ start-api: deps
|
|||
test: deps test-node
|
||||
yarn run test
|
||||
|
||||
test-ci: deps test-node
|
||||
yarn run test:ci
|
||||
|
||||
test-view: deps test-node
|
||||
yarn run test:view
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ export default Adapter.extend({
|
|||
requestForUpdateRecord: function(request, serialized, data) {
|
||||
const params = {
|
||||
...this.formatDatacenter(data[DATACENTER_KEY]),
|
||||
flags: data.Flags,
|
||||
...this.formatNspace(data[NSPACE_KEY]),
|
||||
};
|
||||
return request`
|
||||
|
|
|
@ -39,6 +39,7 @@ const MENU_ITEMS = '[role^="menuitem"]';
|
|||
export default Component.extend({
|
||||
tagName: '',
|
||||
dom: service('dom'),
|
||||
router: service('router'),
|
||||
guid: '',
|
||||
expanded: false,
|
||||
orientation: 'vertical',
|
||||
|
@ -47,6 +48,7 @@ export default Component.extend({
|
|||
this._super(...arguments);
|
||||
set(this, 'guid', this.dom.guid(this));
|
||||
this._listeners = this.dom.listeners();
|
||||
this._routelisteners = this.dom.listeners();
|
||||
},
|
||||
didInsertElement: function() {
|
||||
// TODO: How do you detect whether the children have changed?
|
||||
|
@ -54,10 +56,14 @@ export default Component.extend({
|
|||
this.$menu = this.dom.element(`#${COMPONENT_ID}menu-${this.guid}`);
|
||||
const labelledBy = this.$menu.getAttribute('aria-labelledby');
|
||||
this.$trigger = this.dom.element(`#${labelledBy}`);
|
||||
this._routelisteners.add(this.router, {
|
||||
routeWillChange: () => this.actions.close.apply(this, [{}]),
|
||||
});
|
||||
},
|
||||
willDestroyElement: function() {
|
||||
this._super(...arguments);
|
||||
this._listeners.remove();
|
||||
this._routelisteners.remove();
|
||||
},
|
||||
actions: {
|
||||
keypressClick: function(e) {
|
||||
|
|
|
@ -31,11 +31,13 @@ export default Component.extend({
|
|||
this._super(...arguments);
|
||||
this._viewportlistener.add(
|
||||
this.dom.isInViewport(this.element, bool => {
|
||||
set(this, 'isDisplayed', bool);
|
||||
if (this.isDisplayed) {
|
||||
this.addPathListeners();
|
||||
} else {
|
||||
this.ticker.destroy(this);
|
||||
if (get(this, 'isDisplayed') !== bool) {
|
||||
set(this, 'isDisplayed', bool);
|
||||
if (this.isDisplayed) {
|
||||
this.addPathListeners();
|
||||
} else {
|
||||
this.ticker.destroy(this);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
@ -63,24 +65,29 @@ export default Component.extend({
|
|||
!routes.find(item => get(item, 'Definition.Match.HTTP.PathPrefix') === '/') &&
|
||||
!routes.find(item => typeof item.Definition === 'undefined')
|
||||
) {
|
||||
let nextNode = `resolver:${this.chain.ServiceName}.${this.chain.Namespace}.${this.chain.Datacenter}`;
|
||||
const splitterID = `splitter:${this.chain.ServiceName}`;
|
||||
if (typeof this.chain.Nodes[splitterID] !== 'undefined') {
|
||||
let nextNode;
|
||||
const resolverID = `resolver:${this.chain.ServiceName}.${this.chain.Namespace}.${this.chain.Datacenter}`;
|
||||
const splitterID = `splitter:${this.chain.ServiceName}.${this.chain.Namespace}`;
|
||||
if (typeof this.chain.Nodes[resolverID] !== 'undefined') {
|
||||
nextNode = resolverID;
|
||||
} else if (typeof this.chain.Nodes[splitterID] !== 'undefined') {
|
||||
nextNode = splitterID;
|
||||
}
|
||||
routes.push({
|
||||
Default: true,
|
||||
ID: `route:${this.chain.ServiceName}`,
|
||||
Name: this.chain.ServiceName,
|
||||
Definition: {
|
||||
Match: {
|
||||
HTTP: {
|
||||
PathPrefix: '/',
|
||||
if (typeof nextNode !== 'undefined') {
|
||||
routes.push({
|
||||
Default: true,
|
||||
ID: `route:${this.chain.ServiceName}`,
|
||||
Name: this.chain.ServiceName,
|
||||
Definition: {
|
||||
Match: {
|
||||
HTTP: {
|
||||
PathPrefix: '/',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
NextNode: nextNode,
|
||||
});
|
||||
NextNode: nextNode,
|
||||
});
|
||||
}
|
||||
}
|
||||
return routes;
|
||||
}),
|
||||
|
@ -92,23 +99,17 @@ export default Component.extend({
|
|||
get(this, 'chain.Nodes')
|
||||
);
|
||||
}),
|
||||
graph: computed('chain.Nodes', function() {
|
||||
graph: computed('splitters', 'routes', function() {
|
||||
const graph = this.dataStructs.graph();
|
||||
const router = this.chain.ServiceName;
|
||||
Object.entries(get(this, 'chain.Nodes')).forEach(([key, item]) => {
|
||||
switch (item.Type) {
|
||||
case 'splitter':
|
||||
item.Splits.forEach(splitter => {
|
||||
graph.addLink(item.ID, splitter.NextNode);
|
||||
});
|
||||
break;
|
||||
case 'router':
|
||||
item.Routes.forEach((route, i) => {
|
||||
route = createRoute(route, router, this.dom.guid);
|
||||
graph.addLink(route.ID, route.NextNode);
|
||||
});
|
||||
break;
|
||||
}
|
||||
this.splitters.forEach(item => {
|
||||
item.Splits.forEach(splitter => {
|
||||
graph.addLink(item.ID, splitter.NextNode);
|
||||
});
|
||||
});
|
||||
this.routes.forEach((route, i) => {
|
||||
route = createRoute(route, router, this.dom.guid);
|
||||
graph.addLink(route.ID, route.NextNode);
|
||||
});
|
||||
return graph;
|
||||
}),
|
||||
|
|
|
@ -4,12 +4,6 @@ import { get, computed } from '@ember/object';
|
|||
export default Component.extend({
|
||||
tagName: '',
|
||||
path: computed('item', function() {
|
||||
if (get(this, 'item.Default')) {
|
||||
return {
|
||||
type: 'Default',
|
||||
value: '/',
|
||||
};
|
||||
}
|
||||
return Object.entries(get(this, 'item.Definition.Match.HTTP') || {}).reduce(
|
||||
function(prev, [key, value]) {
|
||||
if (key.toLowerCase().startsWith('path')) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
export default Route.extend({
|
||||
repo: service('repository/service'),
|
||||
|
@ -17,9 +18,15 @@ export default Route.extend({
|
|||
const nspace = this.modelFor('nspace').nspace.substr(1);
|
||||
return hash({
|
||||
item: this.repo.findBySlug(params.name, dc, nspace),
|
||||
chain: this.chainRepo.findBySlug(params.name, dc, nspace),
|
||||
urls: this.settings.findBySlug('urls'),
|
||||
dc: dc,
|
||||
}).then(model => {
|
||||
return hash({
|
||||
chain: ['connect-proxy', 'mesh-gateway'].includes(get(model, 'item.Service.Kind'))
|
||||
? null
|
||||
: this.chainRepo.findBySlug(params.name, dc, nspace),
|
||||
...model,
|
||||
});
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
|
|
|
@ -4,6 +4,9 @@ export default function(filterable) {
|
|||
const sLower = s.toLowerCase();
|
||||
return (
|
||||
get(item, 'Node')
|
||||
.toLowerCase()
|
||||
.indexOf(sLower) !== -1 ||
|
||||
get(item, 'Address')
|
||||
.toLowerCase()
|
||||
.indexOf(sLower) !== -1
|
||||
);
|
||||
|
|
|
@ -65,8 +65,13 @@
|
|||
padding: 10px;
|
||||
padding-left: 36px;
|
||||
}
|
||||
/* here the !important is only needed for what seems to be a difference */
|
||||
/* with the CSS before and after compression */
|
||||
/* i.e. before compression this style is applied */
|
||||
/* after compression it is in the source but doesn't seem to get */
|
||||
/* applied (unless you add the !important) */
|
||||
%menu-panel .is-active {
|
||||
position: relative;
|
||||
position: relative !important;
|
||||
}
|
||||
%menu-panel .is-active > *::after {
|
||||
position: absolute;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{{!<form>}}
|
||||
{{freetext-filter searchable=searchable value=search placeholder="Search by name"}}
|
||||
{{freetext-filter searchable=searchable value=search placeholder="Search"}}
|
||||
{{radio-group keyboardAccess=true name="status" value=status items=(array
|
||||
(hash label='All (Any Status)' value='' )
|
||||
(hash label='Critical Checks' value='critical')
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
{{#if dc}}
|
||||
<ul>
|
||||
{{#if (and (env 'CONSUL_NSPACES_ENABLED') (gt nspaces.length 0))}}
|
||||
<li>
|
||||
<li data-test-nspace-menu>
|
||||
{{#if (and (eq nspaces.length 1) (not canManageNspaces)) }}
|
||||
<span data-test-nspace-selected={{nspace.Name}}>{{nspace.Name}}</span>
|
||||
{{ else }}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{{yield (concat 'popover-menu-' guid)}}
|
||||
{{#aria-menu keyboardAccess=keyboardAccess as |change keypress ariaLabelledBy ariaControls ariaExpanded keypressClick|}}
|
||||
{{#toggle-button checked=expanded onchange=(queue change (action 'change')) as |click|}}
|
||||
{{#toggle-button checked=ariaExpanded onchange=(queue change (action 'change')) as |click|}}
|
||||
<button type="button" aria-haspopup="menu" onkeydown={{keypress}} onclick={{click}} id={{ariaLabelledBy}} aria-controls={{ariaControls}}>
|
||||
{{#yield-slot name='trigger'}}
|
||||
{{yield}}
|
||||
|
|
|
@ -12,9 +12,7 @@
|
|||
{{path.type}}
|
||||
</dt>
|
||||
<dd>
|
||||
{{#if (not-eq path.type 'Default')}}
|
||||
{{path.value}}
|
||||
{{/if}}
|
||||
</dd>
|
||||
</dl>
|
||||
</header>
|
||||
|
|
|
@ -1,59 +1,59 @@
|
|||
{{title item.Service.Service}}
|
||||
{{#app-view class="service show"}}
|
||||
{{#block-slot name='notification' as |status type|}}
|
||||
{{partial 'dc/services/notifications'}}
|
||||
{{/block-slot}}
|
||||
{{#block-slot name='breadcrumbs'}}
|
||||
<ol>
|
||||
<li><a data-test-back href={{href-to 'dc.services'}}>All Services</a></li>
|
||||
</ol>
|
||||
{{/block-slot}}
|
||||
{{#block-slot name='header'}}
|
||||
<h1>
|
||||
{{ item.Service.Service }}
|
||||
{{#with (service/external-source item.Service) as |externalSource| }}
|
||||
{{#with (css-var (concat '--' externalSource '-color-svg') 'none') as |bg| }}
|
||||
{{#if (not-eq bg 'none') }}
|
||||
<span data-test-external-source="{{externalSource}}" style={{{ concat 'background-image:' bg }}} data-tooltip="Registered via {{externalSource}}">Registered via {{externalSource}}</span>
|
||||
{{/if}}
|
||||
{{/with}}
|
||||
{{#block-slot name='notification' as |status type|}}
|
||||
{{partial 'dc/services/notifications'}}
|
||||
{{/block-slot}}
|
||||
{{#block-slot name='breadcrumbs'}}
|
||||
<ol>
|
||||
<li><a data-test-back href={{href-to 'dc.services'}}>All Services</a></li>
|
||||
</ol>
|
||||
{{/block-slot}}
|
||||
{{#block-slot name='header'}}
|
||||
<h1>
|
||||
{{item.Service.Service}}
|
||||
{{#with (service/external-source item.Service) as |externalSource|}}
|
||||
{{#with (css-var (concat '--' externalSource '-color-svg') 'none') as |bg|}}
|
||||
{{#if (not-eq bg 'none')}}
|
||||
<span data-test-external-source={{externalSource}} style={{{concat 'background-image:' bg}}} data-tooltip="Registered via {{externalSource}}">Registered via {{externalSource}}</span>
|
||||
{{/if}}
|
||||
{{/with}}
|
||||
{{/with}}
|
||||
{{#if (eq item.Service.Kind 'connect-proxy')}}
|
||||
<span class="kind-proxy">Proxy</span>
|
||||
<span class="kind-proxy">Proxy</span>
|
||||
{{else if (eq item.Service.Kind 'mesh-gateway')}}
|
||||
<span class="kind-proxy">Mesh Gateway</span>
|
||||
<span class="kind-proxy">Mesh Gateway</span>
|
||||
{{/if}}
|
||||
</h1>
|
||||
<label for="toolbar-toggle"></label>
|
||||
{{tab-nav
|
||||
items=(compact
|
||||
(array
|
||||
'Instances'
|
||||
'Routing'
|
||||
'Tags'
|
||||
)
|
||||
)
|
||||
selected=selectedTab
|
||||
}}
|
||||
{{/block-slot}}
|
||||
{{#block-slot name='actions'}}
|
||||
{{#if urls.service}}
|
||||
{{#templated-anchor data-test-dashboard-anchor href=urls.service vars=(hash Datacenter=dc Service=(hash Name=item.Service.Service)) rel="external"}}Open Dashboard{{/templated-anchor}}
|
||||
{{/if}}
|
||||
{{/block-slot}}
|
||||
{{#block-slot name='content'}}
|
||||
{{#each
|
||||
(compact
|
||||
(array
|
||||
(hash id=(slugify 'Instances') partial='dc/services/instances')
|
||||
(hash id=(slugify 'Routing') partial='dc/services/routing')
|
||||
(hash id=(slugify 'Tags') partial='dc/services/tags')
|
||||
)
|
||||
) as |panel|
|
||||
}}
|
||||
{{#tab-section id=panel.id selected=(eq (if selectedTab selectedTab '') panel.id) onchange=(action "change")}}
|
||||
{{partial panel.partial}}
|
||||
{{/tab-section}}
|
||||
{{/each}}
|
||||
{{/block-slot}}
|
||||
</h1>
|
||||
<label for="toolbar-toggle"></label>
|
||||
{{tab-nav
|
||||
items=(compact
|
||||
(array
|
||||
'Instances'
|
||||
(if (not-eq chain null) 'Routing' '')
|
||||
'Tags'
|
||||
)
|
||||
)
|
||||
selected=selectedTab
|
||||
}}
|
||||
{{/block-slot}}
|
||||
{{#block-slot name='actions'}}
|
||||
{{#if urls.service}}
|
||||
{{#templated-anchor data-test-dashboard-anchor href=urls.service vars=(hash Datacenter=dc Service=(hash Name=item.Service.Service)) rel="external"}}Open Dashboard{{/templated-anchor}}
|
||||
{{/if}}
|
||||
{{/block-slot}}
|
||||
{{#block-slot name='content'}}
|
||||
{{#each
|
||||
(compact
|
||||
(array
|
||||
(hash id=(slugify 'Instances') partial='dc/services/instances')
|
||||
(if (not-eq chain null) (hash id=(slugify 'Routing') partial='dc/services/routing') '')
|
||||
(hash id=(slugify 'Tags') partial='dc/services/tags')
|
||||
)
|
||||
) as |panel|
|
||||
}}
|
||||
{{#tab-section id=panel.id selected=(eq (if selectedTab selectedTab '') panel.id) onchange=(action 'change')}}
|
||||
{{partial panel.partial}}
|
||||
{{/tab-section}}
|
||||
{{/each}}
|
||||
{{/block-slot}}
|
||||
{{/app-view}}
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
</label>
|
||||
</fieldset>
|
||||
{{#if (not (env 'CONSUL_UI_DISABLE_REALTIME'))}}
|
||||
<fieldset>
|
||||
<fieldset data-test-blocking-queries>
|
||||
<h2>Blocking Queries</h2>
|
||||
<p>Keep catalog info up-to-date without refreshing the page. Any changes made to services, nodes and intentions would be reflected in real time.</p>
|
||||
<div class="type-toggle">
|
||||
|
|
|
@ -37,7 +37,6 @@ export const getAlternateServices = function(targets, a) {
|
|||
export const getSplitters = function(nodes) {
|
||||
return getNodesByType(nodes, 'splitter').map(function(item) {
|
||||
// Splitters need IDs adding so we can find them in the DOM later
|
||||
item.ID = `splitter:${item.Name}`;
|
||||
// splitters have a service.nspace as a name
|
||||
// do the reverse dance to ensure we don't mess up any
|
||||
// serivice names with dots in them
|
||||
|
@ -45,8 +44,11 @@ export const getSplitters = function(nodes) {
|
|||
temp.reverse();
|
||||
temp.shift();
|
||||
temp.reverse();
|
||||
item.Name = temp.join('.');
|
||||
return item;
|
||||
return {
|
||||
...item,
|
||||
ID: `splitter:${item.Name}`,
|
||||
Name: temp.join('.'),
|
||||
};
|
||||
});
|
||||
};
|
||||
export const getRoutes = function(nodes, uid) {
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"start:consul": "ember serve --proxy=${CONSUL_HTTP_ADDR:-http://localhost:8500} --port=${EMBER_SERVE_PORT:-4200} --live-reload-port=${EMBER_LIVE_RELOAD_PORT:-7020}",
|
||||
"start:api": "api-double --dir ./node_modules/@hashicorp/consul-api-double",
|
||||
"test": "ember test --test-port=${EMBER_TEST_PORT:-7357}",
|
||||
"test:ci": "ember test --test-port=${EMBER_TEST_PORT:-7357} --path dist --silent --reporter xunit",
|
||||
"test:parallel": "EMBER_EXAM_PARALLEL=true ember exam --split=4 --parallel",
|
||||
"test:view": "ember test --server --test-port=${EMBER_TEST_PORT:-7357}",
|
||||
"test:node": "tape ./node-tests/**/*.js",
|
||||
|
|
|
@ -6,6 +6,7 @@ Feature: dc / kvs / update: KV Update
|
|||
And 1 kv model from yaml
|
||||
---
|
||||
Key: "[Name]"
|
||||
Flags: 12
|
||||
---
|
||||
When I visit the kv page for yaml
|
||||
---
|
||||
|
@ -21,7 +22,7 @@ Feature: dc / kvs / update: KV Update
|
|||
value: [Value]
|
||||
---
|
||||
And I submit
|
||||
Then a PUT request was made to "/v1/kv/[EncodedName]?dc=datacenter&ns=@!namespace" with the body "[Value]"
|
||||
Then a PUT request was made to "/v1/kv/[EncodedName]?dc=datacenter&flags=12&ns=@!namespace" with the body "[Value]"
|
||||
And "[data-notification]" has the "notification-update" class
|
||||
And "[data-notification]" has the "success" class
|
||||
Where:
|
||||
|
@ -37,6 +38,7 @@ Feature: dc / kvs / update: KV Update
|
|||
And 1 kv model from yaml
|
||||
---
|
||||
Key: key
|
||||
Flags: 12
|
||||
---
|
||||
When I visit the kv page for yaml
|
||||
---
|
||||
|
@ -51,7 +53,7 @@ Feature: dc / kvs / update: KV Update
|
|||
value: ' '
|
||||
---
|
||||
And I submit
|
||||
Then a PUT request was made to "/v1/kv/key?dc=datacenter&ns=@!namespace" with the body " "
|
||||
Then a PUT request was made to "/v1/kv/key?dc=datacenter&flags=12&ns=@!namespace" with the body " "
|
||||
Then the url should be /datacenter/kv
|
||||
And the title should be "Key/Value - Consul"
|
||||
And "[data-notification]" has the "notification-update" class
|
||||
|
@ -60,6 +62,7 @@ Feature: dc / kvs / update: KV Update
|
|||
And 1 kv model from yaml
|
||||
---
|
||||
Key: key
|
||||
Flags: 12
|
||||
---
|
||||
When I visit the kv page for yaml
|
||||
---
|
||||
|
@ -74,15 +77,16 @@ Feature: dc / kvs / update: KV Update
|
|||
value: ''
|
||||
---
|
||||
And I submit
|
||||
Then a PUT request was made to "/v1/kv/key?dc=datacenter&ns=@!namespace" with no body
|
||||
Then a PUT request was made to "/v1/kv/key?dc=datacenter&flags=12&ns=@!namespace" with no body
|
||||
Then the url should be /datacenter/kv
|
||||
And "[data-notification]" has the "notification-update" class
|
||||
And "[data-notification]" has the "success" class
|
||||
Scenario: Update to a key when the value is empty
|
||||
And 1 kv model from yaml
|
||||
---
|
||||
Key: key
|
||||
Value: ~
|
||||
Key: key
|
||||
Value: ~
|
||||
Flags: 12
|
||||
---
|
||||
When I visit the kv page for yaml
|
||||
---
|
||||
|
@ -91,7 +95,7 @@ Feature: dc / kvs / update: KV Update
|
|||
---
|
||||
Then the url should be /datacenter/kv/key/edit
|
||||
And I submit
|
||||
Then a PUT request was made to "/v1/kv/key?dc=datacenter&ns=@!namespace" with no body
|
||||
Then a PUT request was made to "/v1/kv/key?dc=datacenter&flags=12&ns=@!namespace" with no body
|
||||
Then the url should be /datacenter/kv
|
||||
And "[data-notification]" has the "notification-update" class
|
||||
And "[data-notification]" has the "success" class
|
||||
|
|
|
@ -50,3 +50,30 @@ Feature: dc / nodes / index
|
|||
Then the url should be /dc-1/nodes
|
||||
Then I see 3 node models
|
||||
And I see leader on the healthyNodes
|
||||
Scenario: Searching the nodes with name and IP address
|
||||
Given 3 node models from yaml
|
||||
---
|
||||
- Node: node-01
|
||||
Address: 10.0.0.0
|
||||
- Node: node-02
|
||||
Address: 10.0.0.1
|
||||
- Node: node-03
|
||||
Address: 10.0.0.2
|
||||
---
|
||||
When I visit the nodes page for yaml
|
||||
---
|
||||
dc: dc-1
|
||||
---
|
||||
And I see 3 node models
|
||||
Then I fill in with yaml
|
||||
---
|
||||
s: node-01
|
||||
---
|
||||
And I see 1 node model
|
||||
And I see 1 node model with the name "node-01"
|
||||
Then I fill in with yaml
|
||||
---
|
||||
s: 10.0.0.1
|
||||
---
|
||||
And I see 1 node model
|
||||
And I see 1 node model with the name "node-02"
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
@setupApplicationTest
|
||||
Feature: dc / nspaces / manage : Managing Namespaces
|
||||
Scenario:
|
||||
Given settings from yaml
|
||||
---
|
||||
consul:token:
|
||||
SecretID: secret
|
||||
AccessorID: accessor
|
||||
Namespace: default
|
||||
---
|
||||
And 1 datacenter models from yaml
|
||||
---
|
||||
- dc-1
|
||||
---
|
||||
And 6 service models
|
||||
When I visit the services page for yaml
|
||||
---
|
||||
dc: dc-1
|
||||
---
|
||||
Then the url should be /dc-1/services
|
||||
Then I see 6 service models
|
||||
# In order to test this properly you have to click around a few times
|
||||
# between services and nspace management
|
||||
When I click nspace on the navigation
|
||||
And I click manageNspaces on the navigation
|
||||
Then the url should be /dc-1/namespaces
|
||||
And I don't see manageNspacesIsVisible on the navigation
|
||||
When I click services on the navigation
|
||||
Then the url should be /dc-1/services
|
||||
When I click nspace on the navigation
|
||||
And I click manageNspaces on the navigation
|
||||
Then the url should be /dc-1/namespaces
|
||||
And I don't see manageNspacesIsVisible on the navigation
|
|
@ -0,0 +1,37 @@
|
|||
@setupApplicationTest
|
||||
Feature: dc / services / Show Routing for Serivce
|
||||
Scenario: Given a service, the Routing tab should display
|
||||
Given 1 datacenter model with the value "dc1"
|
||||
And 1 node models
|
||||
And 1 service model from yaml
|
||||
---
|
||||
- Service:
|
||||
Kind: consul
|
||||
Name: service-0
|
||||
ID: service-0-with-id
|
||||
---
|
||||
When I visit the service page for yaml
|
||||
---
|
||||
dc: dc1
|
||||
service: service-0
|
||||
---
|
||||
And the title should be "service-0 - Consul"
|
||||
And I see routing on the tabs
|
||||
Scenario: Given a service proxy, the Routing tab should not display
|
||||
Given 1 datacenter model with the value "dc1"
|
||||
And 1 node models
|
||||
And 1 service model from yaml
|
||||
---
|
||||
- Service:
|
||||
Kind: connect-proxy
|
||||
Name: service-0-proxy
|
||||
ID: service-0-proxy-with-id
|
||||
---
|
||||
When I visit the service page for yaml
|
||||
---
|
||||
dc: dc1
|
||||
service: service-0-proxy
|
||||
---
|
||||
And the title should be "service-0-proxy - Consul"
|
||||
And I don't see routing on the tabs
|
||||
|
|
@ -2,8 +2,23 @@
|
|||
@notNamespaceable
|
||||
|
||||
Feature: settings / show: Show Settings Page
|
||||
Scenario:
|
||||
Scenario: I see the Blocking queries
|
||||
Given 1 datacenter model with the value "datacenter"
|
||||
When I visit the settings page
|
||||
Then the url should be /setting
|
||||
And the title should be "Settings - Consul"
|
||||
And I see blockingQueries
|
||||
Scenario: Setting CONSUL_UI_DISABLE_REALTIME hides Blocking Queries
|
||||
Given 1 datacenter model with the value "datacenter"
|
||||
And settings from yaml
|
||||
---
|
||||
CONSUL_UI_DISABLE_REALTIME: 1
|
||||
---
|
||||
Then I have settings like yaml
|
||||
---
|
||||
CONSUL_UI_DISABLE_REALTIME: "1"
|
||||
---
|
||||
When I visit the settings page
|
||||
Then the url should be /setting
|
||||
And the title should be "Settings - Consul"
|
||||
And I don't see blockingQueries
|
|
@ -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);
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
}
|
|
@ -58,7 +58,8 @@ module('Integration | Adapter | kv', function(hooks) {
|
|||
test(`requestForUpdateRecord returns the correct url/method when nspace is ${nspace}`, function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:kv');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const expected = `PUT /v1/kv/${id}?dc=${dc}${
|
||||
const flags = 12;
|
||||
const expected = `PUT /v1/kv/${id}?dc=${dc}&flags=${flags}${
|
||||
typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``
|
||||
}`;
|
||||
let actual = adapter
|
||||
|
@ -70,6 +71,7 @@ module('Integration | Adapter | kv', function(hooks) {
|
|||
Key: id,
|
||||
Value: '',
|
||||
Namespace: nspace,
|
||||
Flags: flags,
|
||||
}
|
||||
)
|
||||
.split('\n')
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
import { create, clickable, is, attribute, collection, text } from 'ember-cli-page-object';
|
||||
import {
|
||||
create,
|
||||
clickable,
|
||||
is,
|
||||
attribute,
|
||||
collection,
|
||||
text,
|
||||
isPresent,
|
||||
} from 'ember-cli-page-object';
|
||||
import { alias } from 'ember-cli-page-object/macros';
|
||||
import { visitable } from 'consul-ui/tests/lib/page-object/visitable';
|
||||
import createDeletable from 'consul-ui/tests/lib/page-object/createDeletable';
|
||||
|
@ -59,8 +67,10 @@ const roleSelector = roleSelectorFactory(clickable, deletable, collection, alias
|
|||
export default {
|
||||
index: create(index(visitable, collection)),
|
||||
dcs: create(dcs(visitable, clickable, attribute, collection)),
|
||||
services: create(services(visitable, clickable, attribute, collection, page, catalogFilter)),
|
||||
service: create(service(visitable, attribute, collection, text, catalogFilter)),
|
||||
services: create(
|
||||
services(visitable, clickable, attribute, collection, page, catalogFilter, radiogroup)
|
||||
),
|
||||
service: create(service(visitable, attribute, collection, text, catalogFilter, radiogroup)),
|
||||
instance: create(instance(visitable, attribute, collection, text, radiogroup)),
|
||||
nodes: create(nodes(visitable, clickable, attribute, collection, catalogFilter)),
|
||||
node: create(node(visitable, deletable, clickable, attribute, collection, radiogroup)),
|
||||
|
@ -112,5 +122,5 @@ export default {
|
|||
nspace: create(
|
||||
nspace(visitable, submitable, deletable, cancelable, policySelector, roleSelector)
|
||||
),
|
||||
settings: create(settings(visitable, submitable)),
|
||||
settings: create(settings(visitable, submitable, isPresent)),
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { clickable } from 'ember-cli-page-object';
|
||||
import { clickable, is } from 'ember-cli-page-object';
|
||||
const page = {
|
||||
navigation: ['services', 'nodes', 'kvs', 'acls', 'intentions', 'docs', 'settings'].reduce(
|
||||
function(prev, item, i, arr) {
|
||||
|
@ -24,4 +24,10 @@ const page = {
|
|||
),
|
||||
};
|
||||
page.navigation.dc = clickable('[data-test-datacenter-menu] button');
|
||||
page.navigation.nspace = clickable('[data-test-nspace-menu] button');
|
||||
page.navigation.manageNspaces = clickable('[data-test-main-nav-nspaces] a');
|
||||
page.navigation.manageNspacesIsVisible = is(
|
||||
':checked',
|
||||
'[data-test-nspace-menu] > input[type="checkbox"]'
|
||||
);
|
||||
export default page;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export default function(visitable, attribute, collection, text, filter) {
|
||||
export default function(visitable, attribute, collection, text, filter, radiogroup) {
|
||||
return {
|
||||
visit: visitable('/:dc/services/:service'),
|
||||
externalSource: attribute('data-test-external-source', 'h1 span'),
|
||||
|
@ -8,6 +8,7 @@ export default function(visitable, attribute, collection, text, filter) {
|
|||
dashboardAnchor: {
|
||||
href: attribute('href', '[data-test-dashboard-anchor]'),
|
||||
},
|
||||
tabs: radiogroup('tab', ['instances', 'routing', 'tags']),
|
||||
filter: filter,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export default function(visitable, submitable) {
|
||||
export default function(visitable, submitable, isPresent) {
|
||||
return submitable({
|
||||
visit: visitable('/setting'),
|
||||
blockingQueries: isPresent('[data-test-blocking-queries]'),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +1,30 @@
|
|||
/* eslint no-console: "off" */
|
||||
const notFound = 'Element not found';
|
||||
const cannotDestructure = "Cannot destructure property 'context'";
|
||||
const cannotReadContext = "Cannot read property 'context' of undefined";
|
||||
// checking for existence of pageObjects is pretty difficult
|
||||
// errors are thrown but we should check to make sure its the error that we
|
||||
// want and not another real error
|
||||
// to make things more difficult depending on how you reference the pageObject
|
||||
// an error with a different message is thrown for example:
|
||||
|
||||
// pageObject[thing]() will give you a Element not found error
|
||||
|
||||
// whereas:
|
||||
|
||||
// const obj = pageObject[thing]; obj() will give you a 'cannot destructure error'
|
||||
// and in CI it will give you a 'cannot read property' error
|
||||
|
||||
// the difference in CI could be a difference due to headless vs headed browser
|
||||
// or difference in Chrome/browser versions
|
||||
|
||||
// ideally we wouldn't be checking on error messages at all, but we want to make sure
|
||||
// that real errors are picked up by the tests, so if this gets unmanageable at any point
|
||||
// look at checking for the instance of e being TypeError or similar
|
||||
const isExpectedError = function(e) {
|
||||
return [notFound, cannotDestructure, cannotReadContext].some(item => e.message.startsWith(item));
|
||||
};
|
||||
|
||||
export default function(scenario, assert, find, currentPage) {
|
||||
scenario
|
||||
.then('I see $property on the $component like yaml\n$yaml', function(
|
||||
|
@ -64,34 +90,63 @@ export default function(scenario, assert, find, currentPage) {
|
|||
);
|
||||
})
|
||||
.then(["I don't see $property on the $component"], function(property, component) {
|
||||
// Collection
|
||||
var obj;
|
||||
const message = `Expected to not see ${property} on ${component}`;
|
||||
// Cope with collections
|
||||
let obj;
|
||||
if (typeof currentPage()[component].objectAt === 'function') {
|
||||
obj = currentPage()[component].objectAt(0);
|
||||
} else {
|
||||
obj = currentPage()[component];
|
||||
}
|
||||
assert.throws(
|
||||
function() {
|
||||
const func = obj[property].bind(obj);
|
||||
func();
|
||||
},
|
||||
function(e) {
|
||||
return e.message.startsWith('Element not found');
|
||||
},
|
||||
`Expected to not see ${property} on ${component}`
|
||||
);
|
||||
let prop;
|
||||
try {
|
||||
prop = obj[property];
|
||||
} catch (e) {
|
||||
if (isExpectedError(e)) {
|
||||
assert.ok(true, message);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
if (typeof prop === 'function') {
|
||||
assert.throws(
|
||||
function() {
|
||||
prop();
|
||||
},
|
||||
function(e) {
|
||||
return isExpectedError(e);
|
||||
},
|
||||
message
|
||||
);
|
||||
} else {
|
||||
assert.notOk(prop);
|
||||
}
|
||||
})
|
||||
.then(["I don't see $property"], function(property) {
|
||||
assert.throws(
|
||||
function() {
|
||||
return currentPage()[property]();
|
||||
},
|
||||
function(e) {
|
||||
return e.message.startsWith('Element not found');
|
||||
},
|
||||
`Expected to not see ${property}`
|
||||
);
|
||||
const message = `Expected to not see ${property}`;
|
||||
let prop;
|
||||
try {
|
||||
prop = currentPage()[property];
|
||||
} catch (e) {
|
||||
if (isExpectedError(e)) {
|
||||
assert.ok(true, message);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
if (typeof prop === 'function') {
|
||||
assert.throws(
|
||||
function() {
|
||||
prop();
|
||||
},
|
||||
function(e) {
|
||||
return isExpectedError(e);
|
||||
},
|
||||
message
|
||||
);
|
||||
} else {
|
||||
assert.notOk(prop);
|
||||
}
|
||||
})
|
||||
.then(['I see $property'], function(property) {
|
||||
assert.ok(currentPage()[property], `Expected to see ${property}`);
|
||||
|
|
|
@ -3,10 +3,11 @@ import { module, test } from 'qunit';
|
|||
|
||||
module('Unit | Search | Filter | node', function() {
|
||||
const filter = getFilter(cb => cb);
|
||||
test('items are found by properties', function(assert) {
|
||||
test('items are found by name', function(assert) {
|
||||
[
|
||||
{
|
||||
Node: 'node-HIT',
|
||||
Address: '10.0.0.0',
|
||||
},
|
||||
].forEach(function(item) {
|
||||
const actual = filter(item, {
|
||||
|
@ -15,10 +16,24 @@ module('Unit | Search | Filter | node', function() {
|
|||
assert.ok(actual);
|
||||
});
|
||||
});
|
||||
test('items are not found', function(assert) {
|
||||
test('items are found by IP address', function(assert) {
|
||||
[
|
||||
{
|
||||
Node: 'node-HIT',
|
||||
Address: '10.0.0.0',
|
||||
},
|
||||
].forEach(function(item) {
|
||||
const actual = filter(item, {
|
||||
s: '10',
|
||||
});
|
||||
assert.ok(actual);
|
||||
});
|
||||
});
|
||||
test('items are not found by name', function(assert) {
|
||||
[
|
||||
{
|
||||
Node: 'name',
|
||||
Address: '10.0.0.0',
|
||||
},
|
||||
].forEach(function(item) {
|
||||
const actual = filter(item, {
|
||||
|
@ -27,4 +42,17 @@ module('Unit | Search | Filter | node', function() {
|
|||
assert.notOk(actual);
|
||||
});
|
||||
});
|
||||
test('items are not found by IP address', function(assert) {
|
||||
[
|
||||
{
|
||||
Node: 'name',
|
||||
Address: '10.0.0.0',
|
||||
},
|
||||
].forEach(function(item) {
|
||||
const actual = filter(item, {
|
||||
s: '9',
|
||||
});
|
||||
assert.notOk(actual);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -992,9 +992,9 @@
|
|||
js-yaml "^3.13.1"
|
||||
|
||||
"@hashicorp/consul-api-double@^2.6.2":
|
||||
version "2.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-2.11.0.tgz#0b833893ccc5cfb9546b1513127d5e92d30f2262"
|
||||
integrity sha512-2MO1jiwuJyPlSGQ4AeFtLKJWmLSj0msoiaRHPtj6YPjm69ZkY/t4U4SU3cfpVn2Dx7wHzXe//9GvNHI1gRxAzg==
|
||||
version "2.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-2.12.0.tgz#725078f770bbd0ef75a5f2498968c5c8891f90a2"
|
||||
integrity sha512-8OcgesUjWQ8AjaXzbz3tGJQn1kM0sN6pLidGM7isNPUyYmIjIEXQzaeUQYzsfv0N2Ko9ZuOXYUsaBl8IK1KGow==
|
||||
|
||||
"@hashicorp/ember-cli-api-double@^2.0.0":
|
||||
version "2.0.0"
|
||||
|
|
Loading…
Reference in New Issue