ui: Split ember-data service model into service/service-instance (#8557)

This commit is contained in:
John Cowen 2020-08-26 15:24:30 +01:00 committed by GitHub
parent ad9f118d14
commit 1846e6316c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 348 additions and 228 deletions

View File

@ -0,0 +1,23 @@
import Adapter from './application';
// TODO: Update to use this.formatDatacenter()
export default Adapter.extend({
requestForQuery: function(request, { dc, ns, index, id, uri }) {
if (typeof id === 'undefined') {
throw new Error('You must specify an id');
}
return request`
GET /v1/health/service/${id}?${{ dc }}
X-Request-ID: ${uri}
${{
...this.formatNspace(ns),
index,
}}
`;
},
requestForQueryRecord: function(request, { dc, ns, index, id, uri }) {
// query and queryRecord both use the same endpoint
// they are just serialized differently
return this.requestForQuery(...arguments);
},
});

View File

@ -20,7 +20,7 @@
<ConsulInstanceChecks @type="service" @items={{filter-by 'ServiceID' '' item.Checks}} /> <ConsulInstanceChecks @type="service" @items={{filter-by 'ServiceID' '' item.Checks}} />
<ConsulInstanceChecks @type="node" @items={{reject-by 'ServiceID' '' item.Checks}} /> <ConsulInstanceChecks @type="node" @items={{reject-by 'ServiceID' '' item.Checks}} />
{{/if}} {{/if}}
{{#if (get proxies item.Service.ID)}} {{#if item.ProxyInstance}}
<dl class="proxy"> <dl class="proxy">
<dt> <dt>
<Tooltip> <Tooltip>

View File

@ -1,9 +1,11 @@
import Controller from '@ember/controller'; import Controller from '@ember/controller';
import { get } from '@ember/object'; import { get } from '@ember/object';
import { alias } from '@ember/object/computed';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
export default Controller.extend({ export default Controller.extend({
dom: service('dom'), dom: service('dom'),
notify: service('flashMessages'), notify: service('flashMessages'),
item: alias('items.firstObject'),
actions: { actions: {
error: function(e) { error: function(e) {
if (e.target.readyState === 1) { if (e.target.readyState === 1) {

View File

@ -1,9 +1,6 @@
import Controller from '@ember/controller'; import Controller from '@ember/controller';
import { computed } from '@ember/object';
import { alias } from '@ember/object/computed';
export default Controller.extend({ export default Controller.extend({
items: alias('item.Nodes'),
queryParams: { queryParams: {
sortBy: 'sort', sortBy: 'sort',
search: { search: {
@ -11,12 +8,4 @@ export default Controller.extend({
replace: true, replace: true,
}, },
}, },
keyedProxies: computed('proxies.[]', function() {
const proxies = {};
this.proxies.forEach(item => {
proxies[item.ServiceProxy.DestinationServiceID] = true;
});
return proxies;
}),
}); });

View File

@ -0,0 +1,31 @@
import Model from 'ember-data/model';
import attr from 'ember-data/attr';
import { belongsTo } from 'ember-data/relationships';
import { filter, alias } from '@ember/object/computed';
export const PRIMARY_KEY = 'uid';
export const SLUG_KEY = 'Node.Node,Service.ID';
export default Model.extend({
[PRIMARY_KEY]: attr('string'),
[SLUG_KEY]: attr('string'),
Datacenter: attr('string'),
// ProxyInstance is the ember-data model relationship
ProxyInstance: belongsTo('Proxy'),
// Proxy is the actual JSON api response
Proxy: attr(),
Node: attr(),
Service: attr(),
Checks: attr(),
SyncTime: attr('number'),
meta: attr(),
Tags: alias('Service.Tags'),
Meta: alias('Service.Meta'),
Namespace: alias('Service.Namespace'),
ServiceChecks: filter('Checks', function(item, i, arr) {
return item.ServiceID !== '';
}),
NodeChecks: filter('Checks', function(item, i, arr) {
return item.ServiceID === '';
}),
});

View File

@ -14,8 +14,8 @@ export default Model.extend({
}, },
}), }),
InstanceCount: attr('number'), InstanceCount: attr('number'),
ProxyFor: attr(),
Proxy: attr(), Proxy: attr(),
ProxyFor: attr(),
Kind: attr('string'), Kind: attr('string'),
ExternalSources: attr(), ExternalSources: attr(),
GatewayConfig: attr(), GatewayConfig: attr(),

View File

@ -10,7 +10,7 @@ export default Route.extend({
return this.modelFor(parent); return this.modelFor(parent);
}, },
afterModel: function(model, transition) { afterModel: function(model, transition) {
if (get(model, 'item.Kind') !== 'mesh-gateway') { if (get(model, 'item.Service.Kind') !== 'mesh-gateway') {
const parent = this.routeName const parent = this.routeName
.split('.') .split('.')
.slice(0, -1) .slice(0, -1)

View File

@ -10,7 +10,7 @@ export default Route.extend({
return this.modelFor(parent); return this.modelFor(parent);
}, },
afterModel: function(model, transition) { afterModel: function(model, transition) {
if (get(model, 'item.Kind') !== 'connect-proxy') { if (get(model, 'item.Service.Kind') !== 'connect-proxy') {
const parent = this.routeName const parent = this.routeName
.split('.') .split('.')
.slice(0, -1) .slice(0, -1)

View File

@ -13,13 +13,15 @@ export default Route.extend({
slug: params.name, slug: params.name,
dc: dc, dc: dc,
nspace: nspace, nspace: nspace,
item: this.data.source(uri => uri`/${nspace}/${dc}/service/${params.name}`), items: this.data.source(
uri => uri`/${nspace}/${dc}/service-instances/for-service/${params.name}`
),
urls: this.settings.findBySlug('urls'), urls: this.settings.findBySlug('urls'),
proxies: [], proxies: [],
}) })
.then(model => { .then(model => {
return ['connect-proxy', 'mesh-gateway', 'ingress-gateway', 'terminating-gateway'].includes( return ['connect-proxy', 'mesh-gateway', 'ingress-gateway', 'terminating-gateway'].includes(
get(model, 'item.Service.Kind') get(model, 'items.firstObject.Service.Kind')
) )
? model ? model
: hash({ : hash({
@ -31,7 +33,9 @@ export default Route.extend({
}); });
}) })
.then(model => { .then(model => {
return ['ingress-gateway', 'terminating-gateway'].includes(get(model, 'item.Service.Kind')) return ['ingress-gateway', 'terminating-gateway'].includes(
get(model, 'items.firstObject.Service.Kind')
)
? hash({ ? hash({
...model, ...model,
gatewayServices: this.data.source( gatewayServices: this.data.source(

View File

@ -0,0 +1,45 @@
import Serializer from './application';
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/service-instance';
export default Serializer.extend({
primaryKey: PRIMARY_KEY,
slugKey: SLUG_KEY,
respondForQuery: function(respond, query) {
return this._super(function(cb) {
return respond(function(headers, body) {
if (body.length === 0) {
const e = new Error();
e.errors = [
{
status: '404',
title: 'Not found',
},
];
throw e;
}
return cb(headers, body);
});
}, query);
},
respondForQueryRecord: function(respond, query) {
return this._super(function(cb) {
return respond(function(headers, body) {
body = body.find(function(item) {
return item.Node.Node === query.node && item.Service.ID === query.serviceId;
});
if (typeof body === 'undefined') {
const e = new Error();
e.errors = [
{
status: '404',
title: 'Not found',
},
];
throw e;
}
body.Namespace = body.Service.Namespace;
return cb(headers, body);
});
}, query);
},
});

View File

@ -8,7 +8,8 @@ export default Service.extend({
gateways: service('repository/service'), gateways: service('repository/service'),
services: service('repository/service'), services: service('repository/service'),
service: service('repository/service'), service: service('repository/service'),
['service-instance']: service('repository/service'), ['service-instance']: service('repository/service-instance'),
['service-instances']: service('repository/service-instance'),
proxies: service('repository/proxy'), proxies: service('repository/proxy'),
['proxy-instance']: service('repository/proxy'), ['proxy-instance']: service('repository/proxy'),
['discovery-chain']: service('repository/discovery-chain'), ['discovery-chain']: service('repository/discovery-chain'),
@ -67,6 +68,14 @@ export default Service.extend({
break; break;
} }
break; break;
case 'service-instances':
[method, ...slug] = rest;
switch (method) {
case 'for-service':
find = configuration => repo.findByService(slug, dc, nspace, configuration);
break;
}
break;
case 'coordinates': case 'coordinates':
[method, ...slug] = rest; [method, ...slug] = rest;
switch (method) { switch (method) {
@ -102,12 +111,15 @@ export default Service.extend({
case 'token': case 'token':
find = configuration => repo.self(rest[1], dc); find = configuration => repo.self(rest[1], dc);
break; break;
case 'service':
case 'discovery-chain': case 'discovery-chain':
case 'node': case 'node':
find = configuration => repo.findBySlug(rest[0], dc, nspace, configuration); find = configuration => repo.findBySlug(rest[0], dc, nspace, configuration);
break; break;
case 'service-instance': case 'service-instance':
// id, node, service
find = configuration =>
repo.findBySlug(rest[0], rest[1], rest[2], dc, nspace, configuration);
break;
case 'proxy-instance': case 'proxy-instance':
// id, node, service // id, node, service
find = configuration => find = configuration =>

View File

@ -19,7 +19,20 @@ export default RepositoryService.extend({
query.index = configuration.cursor; query.index = configuration.cursor;
query.uri = configuration.uri; query.uri = configuration.uri;
} }
return this.store.query(this.getModelName(), query); return this.store.query(this.getModelName(), query).then(items => {
items.forEach(item => {
// swap out the id for the services id
// so we can then assign the proxy to it if it exists
const id = JSON.parse(item.uid);
id.pop();
id.push(item.ServiceProxy.DestinationServiceID);
const service = this.store.peekRecord('service-instance', JSON.stringify(id));
if (service) {
set(service, 'ProxyInstance', item);
}
});
return items;
});
}, },
findInstanceBySlug: function(id, node, slug, dc, nspace, configuration) { findInstanceBySlug: function(id, node, slug, dc, nspace, configuration) {
return this.findAllBySlug(slug, dc, nspace, configuration).then(function(items) { return this.findAllBySlug(slug, dc, nspace, configuration).then(function(items) {

View File

@ -0,0 +1,33 @@
import RepositoryService from 'consul-ui/services/repository';
const modelName = 'service-instance';
export default RepositoryService.extend({
getModelName: function() {
return modelName;
},
findByService: function(slug, dc, nspace, configuration = {}) {
const query = {
dc: dc,
nspace: nspace,
id: slug,
};
if (typeof configuration.cursor !== 'undefined') {
query.index = configuration.cursor;
query.uri = configuration.uri;
}
return this.store.query(this.getModelName(), query);
},
findBySlug: function(serviceId, node, service, dc, nspace, configuration = {}) {
const query = {
dc: dc,
ns: nspace,
serviceId: serviceId,
node: node,
id: service,
};
if (typeof configuration.cursor !== 'undefined') {
query.index = configuration.cursor;
query.uri = configuration.uri;
}
return this.store.queryRecord(this.getModelName(), query);
},
});

View File

@ -1,74 +1,9 @@
import RepositoryService from 'consul-ui/services/repository'; import RepositoryService from 'consul-ui/services/repository';
import { get, set } from '@ember/object';
const modelName = 'service'; const modelName = 'service';
export default RepositoryService.extend({ export default RepositoryService.extend({
getModelName: function() { getModelName: function() {
return modelName; return modelName;
}, },
findBySlug: function(slug, dc) {
return this._super(...arguments).then(function(item) {
// TODO: Move this to the Serializer
const nodes = get(item, 'Nodes');
if (nodes.length === 0) {
// TODO: Add an store.error("404", "message") or similar
// or move all this to serializer
const e = new Error();
e.errors = [
{
status: '404',
title: 'Not found',
},
];
throw e;
}
const service = get(nodes, 'firstObject');
// TODO: Use [...new Set()] instead of uniq
const tags = nodes
.reduce(function(prev, item) {
return prev.concat(get(item, 'Service.Tags') || []);
}, [])
.uniq();
set(service, 'Tags', tags);
set(service, 'Nodes', nodes);
set(service, 'meta', get(item, 'meta'));
set(service, 'Namespace', get(item, 'Namespace'));
return service;
});
},
findInstanceBySlug: function(id, node, slug, dc, nspace, configuration) {
return this.findBySlug(slug, dc, nspace, configuration).then(function(item) {
// TODO: Move this to the Serializer
// Loop through all the service instances and pick out the one
// that has the same service id AND node name
// node names are unique per datacenter
const i = item.Nodes.findIndex(function(item) {
return item.Service.ID === id && item.Node.Node === node;
});
if (i !== -1) {
const service = item.Nodes[i].Service;
service.Node = item.Nodes[i].Node;
service.ServiceChecks = item.Nodes[i].Checks.filter(function(item) {
return item.ServiceID != '';
});
service.NodeChecks = item.Nodes[i].Checks.filter(function(item) {
return item.ServiceID == '';
});
set(service, 'meta', get(item, 'meta'));
set(service, 'Namespace', get(item, 'Namespace'));
return service;
}
// TODO: Add an store.error("404", "message") or similar
// or move all this to serializer
const e = new Error();
e.errors = [
{
status: '404',
title: 'Unable to find instance',
},
];
throw e;
});
},
findGatewayBySlug: function(slug, dc, nspace, configuration = {}) { findGatewayBySlug: function(slug, dc, nspace, configuration = {}) {
const query = { const query = {
dc: dc, dc: dc,

View File

@ -1,4 +1,4 @@
{{title item.ID}} {{title item.Service.ID}}
<EventSource @src={{item}} @onerror={{action "error"}} /> <EventSource @src={{item}} @onerror={{action "error"}} />
<EventSource @src={{proxy}} /> <EventSource @src={{proxy}} />
<EventSource @src={{proxyMeta}} /> <EventSource @src={{proxyMeta}} />
@ -9,12 +9,12 @@
<BlockSlot @name="breadcrumbs"> <BlockSlot @name="breadcrumbs">
<ol> <ol>
<li><a data-test-back href={{href-to 'dc.services'}}>All Services</a></li> <li><a data-test-back href={{href-to 'dc.services'}}>All Services</a></li>
<li><a data-test-back href={{href-to 'dc.services.show'}}>Service ({{item.Service}})</a></li> <li><a data-test-back href={{href-to 'dc.services.show'}}>Service ({{item.Service.Service}})</a></li>
</ol> </ol>
</BlockSlot> </BlockSlot>
<BlockSlot @name="header"> <BlockSlot @name="header">
<h1> <h1>
{{ item.ID }} {{ item.Service.ID }}
</h1> </h1>
<ConsulExternalSource @item={{item}} /> <ConsulExternalSource @item={{item}} />
<ConsulKind @item={{item}} @withInfo={{true}} /> <ConsulKind @item={{item}} @withInfo={{true}} />
@ -22,7 +22,7 @@
<BlockSlot @name="nav"> <BlockSlot @name="nav">
<dl> <dl>
<dt>Service Name</dt> <dt>Service Name</dt>
<dd><a href="{{href-to 'dc.services.show' item.Service}}">{{item.Service}}</a></dd> <dd><a href="{{href-to 'dc.services.show' item.Service.Service}}">{{item.Service.Service}}</a></dd>
</dl> </dl>
<dl> <dl>
<dt>Node Name</dt> <dt>Node Name</dt>
@ -30,7 +30,7 @@
</dl> </dl>
</BlockSlot> </BlockSlot>
<BlockSlot @name="actions"> <BlockSlot @name="actions">
{{#let (or item.Address item.Node.Address) as |address|}} {{#let (or item.Service.Address item.Node.Address) as |address|}}
<CopyButton @value={{address}} @name="Address">{{address}}</CopyButton> <CopyButton @value={{address}} @name="Address">{{address}}</CopyButton>
{{/let}} {{/let}}
</BlockSlot> </BlockSlot>
@ -40,7 +40,7 @@
(array (array
(hash label="Health Checks" href=(href-to "dc.services.instance.healthchecks") selected=(is-href "dc.services.instance.healthchecks")) (hash label="Health Checks" href=(href-to "dc.services.instance.healthchecks") selected=(is-href "dc.services.instance.healthchecks"))
(if (if
(eq item.Kind 'mesh-gateway') (eq item.Service.Kind 'mesh-gateway')
(hash label="Addresses" href=(href-to "dc.services.instance.addresses") selected=(is-href "dc.services.instance.addresses")) "" (hash label="Addresses" href=(href-to "dc.services.instance.addresses") selected=(is-href "dc.services.instance.addresses")) ""
) )
(if proxy (if proxy

View File

@ -1,9 +1,9 @@
<div id="addresses" class="tab-section"> <div id="addresses" class="tab-section">
<div role="tabpanel"> <div role="tabpanel">
{{#if item.TaggedAddresses }} {{#if item.Service.TaggedAddresses }}
<TabularCollection <TabularCollection
data-test-addresses data-test-addresses
@items={{object-entries item.TaggedAddresses}} as |taggedAddress index| @items={{object-entries item.Service.TaggedAddresses}} as |taggedAddress index|
> >
<BlockSlot @name="header"> <BlockSlot @name="header">
<th>Tag</th> <th>Tag</th>

View File

@ -1,18 +1,18 @@
<div class="tab-section"> <div class="tab-section">
<div role="tabpanel"> <div role="tabpanel">
{{#if (gt proxy.Proxy.Upstreams.length 0)}} {{#if (gt proxy.Service.Proxy.Upstreams.length 0)}}
<section class="proxy-upstreams"> <section class="proxy-upstreams">
<h3>Upstreams</h3> <h3>Upstreams</h3>
<ConsulUpstreamInstanceList @items={{proxy.Proxy.Upstreams}} @dc={{dc}} @nspace={{nspace}} /> <ConsulUpstreamInstanceList @items={{proxy.Service.Proxy.Upstreams}} @dc={{dc}} @nspace={{nspace}} />
</section> </section>
{{/if}} {{/if}}
{{#if (gt proxy.Proxy.Expose.Paths.length 0)}} {{#if (gt proxy.Service.Proxy.Expose.Paths.length 0)}}
<section class="proxy-exposed-paths"> <section class="proxy-exposed-paths">
<h3>Exposed paths</h3> <h3>Exposed paths</h3>
<p> <p>
The following list shows individual HTTP paths exposed through Envoy for external services like Prometheus. Read more about this in our documentation. The following list shows individual HTTP paths exposed through Envoy for external services like Prometheus. Read more about this in our documentation.
</p> </p>
<ConsulExposedPathList @items={{proxy.Proxy.Expose.Paths}} @address={{item.Address}} /> <ConsulExposedPathList @items={{proxy.Service.Proxy.Expose.Paths}} @address={{item.Address}} />
</section> </section>
{{/if}} {{/if}}
{{#if (or (gt proxy.ServiceChecks.length 0) (gt proxy.NodeChecks.length 0))}} {{#if (or (gt proxy.ServiceChecks.length 0) (gt proxy.NodeChecks.length 0))}}

View File

@ -1,9 +1,9 @@
{{title item.Service.Service}} <EventSource @src={{items}} @onerror={{action "error"}} />
<EventSource @src={{item}} @onerror={{action "error"}} />
<EventSource @src={{chain}} /> <EventSource @src={{chain}} />
<EventSource @src={{intentions}} /> <EventSource @src={{intentions}} />
<EventSource @src={{proxies}} /> <EventSource @src={{proxies}} />
<EventSource @src={{gatewayServices}} /> <EventSource @src={{gatewayServices}} />
{{title item.Service.Service}}
<AppView @class="service show"> <AppView @class="service show">
<BlockSlot @name="notification" as |status type|> <BlockSlot @name="notification" as |status type|>
{{partial 'dc/services/notifications'}} {{partial 'dc/services/notifications'}}

View File

@ -9,7 +9,7 @@
{{/if}} {{/if}}
<ChangeableSet @dispatcher={{searchable 'serviceInstance' items}} @terms={{search}}> <ChangeableSet @dispatcher={{searchable 'serviceInstance' items}} @terms={{search}}>
<BlockSlot @name="set" as |filtered|> <BlockSlot @name="set" as |filtered|>
<ConsulServiceInstanceList @routeName="dc.services.instance" @items={{filtered}} @proxies={{keyedProxies}}/> <ConsulServiceInstanceList @routeName="dc.services.instance" @items={{filtered}}/>
</BlockSlot> </BlockSlot>
<BlockSlot @name="empty"> <BlockSlot @name="empty">
<EmptyState> <EmptyState>

View File

@ -1,8 +1,9 @@
<div id="tags" class="tab-section"> <div id="tags" class="tab-section">
<div role="tabpanel"> <div role="tabpanel">
{{#if (gt item.Tags.length 0) }} {{#let (flatten (map-by "Tags" items)) as |tags|}}
<TagList @item={{item}} /> {{#if (gt tags.length 0) }}
{{else}} <TagList @item={{hash Tags=tags}} />
{{else}}
<EmptyState> <EmptyState>
<BlockSlot @name="body"> <BlockSlot @name="body">
<p> <p>
@ -10,6 +11,7 @@
</p> </p>
</BlockSlot> </BlockSlot>
</EmptyState> </EmptyState>
{{/if}} {{/if}}
{{/let}}
</div> </div>
</div> </div>

View File

@ -1,3 +1,4 @@
import { get } from '@ember/object';
export default function(foreignKey, nspaceKey, hash = JSON.stringify) { export default function(foreignKey, nspaceKey, hash = JSON.stringify) {
return function(primaryKey, slugKey, foreignKeyValue) { return function(primaryKey, slugKey, foreignKeyValue) {
if (foreignKeyValue == null || foreignKeyValue.length < 1) { if (foreignKeyValue == null || foreignKeyValue.length < 1) {
@ -6,12 +7,12 @@ export default function(foreignKey, nspaceKey, hash = JSON.stringify) {
return function(item) { return function(item) {
const slugKeys = slugKey.split(','); const slugKeys = slugKey.split(',');
const slugValues = slugKeys.map(function(slugKey) { const slugValues = slugKeys.map(function(slugKey) {
if (item[slugKey] == null || item[slugKey].length < 1) { if (get(item, slugKey) == null || get(item, slugKey).length < 1) {
throw new Error('Unable to create fingerprint, missing slug'); throw new Error('Unable to create fingerprint, missing slug');
} }
return item[slugKey]; return get(item, slugKey);
}); });
const nspaceValue = item[nspaceKey] || 'default'; const nspaceValue = get(item, nspaceKey) || 'default';
return { return {
...item, ...item,
...{ ...{

View File

@ -0,0 +1,36 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { env } from '../../../env';
const shouldHaveNspace = function(nspace) {
return typeof nspace !== 'undefined' && env('CONSUL_NSPACES_ENABLED');
};
module('Integration | Adapter | service-instance', function(hooks) {
setupTest(hooks);
const dc = 'dc-1';
const id = 'service-name';
const undefinedNspace = 'default';
[undefinedNspace, 'team-1', undefined].forEach(nspace => {
test(`requestForQueryRecord returns the correct url/method when nspace is ${nspace}`, function(assert) {
const adapter = this.owner.lookup('adapter:service-instance');
const client = this.owner.lookup('service:client/http');
const expected = `GET /v1/health/service/${id}?dc=${dc}${
shouldHaveNspace(nspace) ? `&ns=${nspace}` : ``
}`;
let actual = adapter.requestForQueryRecord(client.requestParams.bind(client), {
dc: dc,
id: id,
ns: nspace,
});
assert.equal(`${actual.method} ${actual.url}`, expected);
});
});
test("requestForQueryRecord throws if you don't specify an id", function(assert) {
const adapter = this.owner.lookup('adapter:service-instance');
const client = this.owner.lookup('service:client/http');
assert.throws(function() {
adapter.requestForQueryRecord(client.url, {
dc: dc,
});
});
});
});

View File

@ -0,0 +1,54 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { get } from 'consul-ui/tests/helpers/api';
import {
HEADERS_SYMBOL as META,
HEADERS_DATACENTER as DC,
HEADERS_NAMESPACE as NSPACE,
} from 'consul-ui/utils/http/consul';
module('Integration | Serializer | service-instance', function(hooks) {
setupTest(hooks);
const dc = 'dc-1';
const undefinedNspace = 'default';
[undefinedNspace, 'team-1', undefined].forEach(nspace => {
test(`respondForQueryRecord returns the correct data for item endpoint when nspace is ${nspace}`, function(assert) {
const serializer = this.owner.lookup('serializer:service-instance');
const id = 'service-name';
const node = 'node-0';
const request = {
url: `/v1/health/service/${id}?dc=${dc}${
typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``
}`,
};
return get(request.url).then(function(payload) {
payload[0].Node.Node = node;
payload[0].Service.ID = id;
const expected = {
...payload[0],
Datacenter: dc,
[META]: {
[DC.toLowerCase()]: dc,
[NSPACE.toLowerCase()]: payload[0].Service.Namespace || undefinedNspace,
},
Namespace: payload[0].Service.Namespace || undefinedNspace,
uid: `["${payload[0].Service.Namespace || undefinedNspace}","${dc}","${node}","${id}"]`,
};
const actual = serializer.respondForQueryRecord(
function(cb) {
const headers = {};
const body = payload;
return cb(headers, body);
},
{
dc: dc,
ns: nspace,
id: id,
node: node,
serviceId: id,
}
);
assert.deepEqual(actual, expected);
});
});
});
});

View File

@ -1,11 +1,6 @@
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit'; import { setupTest } from 'ember-qunit';
import { get } from 'consul-ui/tests/helpers/api'; import { get } from 'consul-ui/tests/helpers/api';
import {
HEADERS_SYMBOL as META,
HEADERS_DATACENTER as DC,
HEADERS_NAMESPACE as NSPACE,
} from 'consul-ui/utils/http/consul';
module('Integration | Serializer | service', function(hooks) { module('Integration | Serializer | service', function(hooks) {
setupTest(hooks); setupTest(hooks);
const dc = 'dc-1'; const dc = 'dc-1';
@ -73,40 +68,5 @@ module('Integration | Serializer | service', function(hooks) {
assert.deepEqual(actual, expected); assert.deepEqual(actual, expected);
}); });
}); });
test(`respondForQueryRecord returns the correct data for item endpoint when nspace is ${nspace}`, function(assert) {
const serializer = this.owner.lookup('serializer:service');
const id = 'service-name';
const request = {
url: `/v1/health/service/${id}?dc=${dc}${
typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``
}`,
};
return get(request.url).then(function(payload) {
const expected = {
Datacenter: dc,
[META]: {
[DC.toLowerCase()]: dc,
[NSPACE.toLowerCase()]: payload[0].Service.Namespace || undefinedNspace,
},
Namespace: payload[0].Service.Namespace || undefinedNspace,
uid: `["${payload[0].Service.Namespace || undefinedNspace}","${dc}","${id}"]`,
Name: id,
Nodes: payload,
};
const actual = serializer.respondForQueryRecord(
function(cb) {
const headers = {};
const body = payload;
return cb(headers, body);
},
{
dc: dc,
ns: nspace,
id: id,
}
);
assert.deepEqual(actual, expected);
});
});
}); });
}); });

View File

@ -7,89 +7,9 @@ moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, {
integration: true, integration: true,
}); });
const dc = 'dc-1'; const dc = 'dc-1';
const id = 'token-name';
const now = new Date().getTime(); const now = new Date().getTime();
const undefinedNspace = 'default'; const undefinedNspace = 'default';
[undefinedNspace, 'team-1', undefined].forEach(nspace => { [undefinedNspace, 'team-1', undefined].forEach(nspace => {
test(`findInstanceBySlug calls findBySlug with the correct arguments when nspace is ${nspace}`, function(assert) {
assert.expect(4);
const id = 'id';
const slug = 'slug';
const node = 'node-name';
const datacenter = 'dc-1';
const conf = {
cursor: 1,
};
const service = this.subject();
service.findBySlug = function(slug, dc, nspace, configuration) {
assert.equal(
arguments.length,
4,
'Expected to be called with the correct number of arguments'
);
assert.equal(dc, datacenter);
assert.deepEqual(configuration, conf);
return Promise.resolve({ Nodes: [] });
};
// This will throw an error as we don't resolve any services that match what was requested
// so a 404 is the correct error response
return service.findInstanceBySlug(id, slug, node, datacenter, nspace, conf).catch(function(e) {
assert.equal(e.errors[0].status, '404');
});
});
test(`findBySlug returns the correct data for item endpoint when the nspace is ${nspace}`, function(assert) {
return repo(
'Service',
'findBySlug',
this.subject(),
function(stub) {
return stub(
`/v1/health/service/${id}?dc=${dc}${
typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``
}`,
{
CONSUL_NODE_COUNT: 1,
}
);
},
function(service) {
return service.findBySlug(id, dc, nspace || undefinedNspace);
},
function(actual, expected) {
assert.deepEqual(
actual,
expected(function(payload) {
// TODO: So this tree is all 'wrong', it's not having any major impact
// this this tree needs revisting to something that makes more sense
payload = Object.assign(
{},
{ Nodes: payload },
{
Datacenter: dc,
Namespace: payload[0].Service.Namespace || undefinedNspace,
uid: `["${payload[0].Service.Namespace || undefinedNspace}","${dc}","${id}"]`,
}
);
const nodes = payload.Nodes;
const service = payload.Nodes[0];
service.Nodes = nodes;
service.Tags = [...new Set(payload.Nodes[0].Service.Tags)];
service.Namespace = payload.Namespace;
service.meta = {
cacheControl: undefined,
cursor: undefined,
dc: dc,
nspace: payload.Namespace,
};
return service;
})
);
}
);
});
test(`findGatewayBySlug returns the correct data for list endpoint when nspace is ${nspace}`, function(assert) { test(`findGatewayBySlug returns the correct data for list endpoint when nspace is ${nspace}`, function(assert) {
get(this.subject(), 'store').serializerFor(NAME).timestamp = function() { get(this.subject(), 'store').serializerFor(NAME).timestamp = function() {
return now; return now;

View File

@ -0,0 +1,12 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Adapter | service-instance', function(hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function(assert) {
let adapter = this.owner.lookup('adapter:service-instance');
assert.ok(adapter);
});
});

View File

@ -0,0 +1,13 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Model | service-instance', function(hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function(assert) {
let store = this.owner.lookup('service:store');
let model = store.createRecord('service-instance', {});
assert.ok(model);
});
});

View File

@ -0,0 +1,23 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Serializer | service-instance', function(hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function(assert) {
let store = this.owner.lookup('service:store');
let serializer = store.serializerFor('service-instance');
assert.ok(serializer);
});
test('it serializes records', function(assert) {
let store = this.owner.lookup('service:store');
let record = store.createRecord('service-instance', {});
let serializedRecord = record.serialize();
assert.ok(serializedRecord);
});
});

View File

@ -0,0 +1,12 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Repository | service-instance', function(hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function(assert) {
const repo = this.owner.lookup('service:repository/service-instance');
assert.ok(repo);
});
});