ui: Correct readonly L7 Intentions API calls (#8725)

* Disable ability to select destination if not creating

* Add a LegacyID for intentions that don't have them

* Use `/exact/` endpoint for reading single intentions

* Fix up test for new API interaction

* Upgrade consul-api-double

* Comment out tests using destination for the moment

Also, removed any intention related things from the old page-navigation
tests
This commit is contained in:
John Cowen 2020-09-24 16:07:13 +01:00 committed by GitHub
parent 7d58901ae8
commit 011b7b8570
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 61 additions and 116 deletions

View File

@ -1,6 +1,5 @@
import Adapter, { DATACENTER_QUERY_PARAM as API_DATACENTER_KEY } from './application';
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
import { SLUG_KEY } from 'consul-ui/models/intention';
// Intentions use SourceNS and DestinationNS properties for namespacing
// so we don't need to add the `?ns=` anywhere here
@ -26,8 +25,13 @@ export default Adapter.extend({
if (typeof id === 'undefined') {
throw new Error('You must specify an id');
}
const [SourceNS, SourceName, DestinationNS, DestinationName] = id
.split(':')
.map(decodeURIComponent);
return request`
GET /v1/connect/intentions/${id}?${{ dc }}
GET /v1/connect/intentions/exact?source=${SourceNS +
'/' +
SourceName}&destination=${DestinationNS + '/' + DestinationName}&${{ dc }}
Cache-Control: no-store
${{ index }}
@ -51,7 +55,7 @@ export default Adapter.extend({
},
requestForUpdateRecord: function(request, serialized, data) {
return request`
PUT /v1/connect/intentions/${data[SLUG_KEY]}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
PUT /v1/connect/intentions/${data.LegacyID}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
${{
SourceNS: serialized.SourceNS,
@ -60,13 +64,14 @@ export default Adapter.extend({
DestinationName: serialized.DestinationName,
SourceType: serialized.SourceType,
Action: serialized.Action,
Meta: serialized.Meta,
Description: serialized.Description,
}}
`;
},
requestForDeleteRecord: function(request, serialized, data) {
return request`
DELETE /v1/connect/intentions/${data[SLUG_KEY]}?${{
DELETE /v1/connect/intentions/${data.LegacyID}?${{
[API_DATACENTER_KEY]: data[DATACENTER_KEY],
}}
`;

View File

@ -43,7 +43,9 @@
{{nspace.Name}}
{{/if}}
</PowerSelectWithCreate>
<em>Search for an existing namespace, or enter any Namespace name.</em>
{{#if create}}
<em>Search for an existing namespace, or enter any Namespace name.</em>
{{/if}}
</label>
{{/if}}
</fieldset>
@ -52,6 +54,7 @@
<label data-test-destination-element class="type-select{{if item.error.DestinationName ' has-error'}}">
<span>Destination Service</span>
<PowerSelectWithCreate
@disabled={{not create}}
@options={{services}}
@searchField="Name"
@selected={{DestinationName}}
@ -66,12 +69,15 @@
{{service.Name}}
{{/if}}
</PowerSelectWithCreate>
<em>Search for an existing service, or enter any Service name.</em>
{{#if create}}
<em>Search for an existing service, or enter any Service name.</em>
{{/if}}
</label>
{{#if (env 'CONSUL_NSPACES_ENABLED')}}
<label data-test-destination-nspace class="type-select{{if item.error.DestinationNS ' has-error'}}">
<span>Destination Namespace</span>
<PowerSelectWithCreate
@disabled={{not create}}
@options={{nspaces}}
@searchField="Name"
@selected={{DestinationNS}}

View File

@ -50,6 +50,7 @@
@DestinationNS={{DestinationNS}}
@item={{item}}
@disabled={{api.disabled}}
@create={{api.isCreate}}
@onchange={{api.change}}
/>
<div>

View File

@ -20,6 +20,7 @@ export default Model.extend({
Action: attr('string'),
Meta: attr(),
Legacy: attr('boolean', { defaultValue: true }),
LegacyID: attr('string'),
IsManagedByCRD: computed('Meta', function() {
const meta = Object.entries(this.Meta || {}).find(

View File

@ -1,5 +1,6 @@
import Serializer from './application';
import { inject as service } from '@ember/service';
import { get } from '@ember/object';
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/intention';
export default Serializer.extend({
@ -11,13 +12,14 @@ export default Serializer.extend({
this.uri = this.encoder.uriTag();
},
ensureID: function(item) {
if (typeof item.ID !== 'string') {
item.ID = this
.uri`${item.SourceNS}:${item.SourceName}:${item.DestinationNS}:${item.DestinationName}`;
if (!get(item, 'ID.length')) {
item.Legacy = false;
} else {
item.Legacy = true;
item.LegacyID = item.ID;
}
item.ID = this
.uri`${item.SourceNS}:${item.SourceName}:${item.DestinationNS}:${item.DestinationName}`;
return item;
},
respondForQuery: function(respond, query) {
@ -42,4 +44,16 @@ export default Serializer.extend({
query
);
},
respondForUpdateRecord: function(respond, serialized, data) {
return this._super(
cb =>
respond((headers, body) => {
body.LegacyID = body.ID;
body.ID = serialized.ID;
return cb(headers, body);
}),
serialized,
data
);
},
});

View File

@ -35,7 +35,7 @@ Feature: dc / intentions / filtered-select: Intention Service Select Dropdowns
---------------
| Name |
| source |
| destination |
#| destination |
---------------
Scenario: Opening the [Name] dropdown with 2 services with the same name from different nspaces
Given 1 datacenter model with the value "datacenter"
@ -65,5 +65,5 @@ Feature: dc / intentions / filtered-select: Intention Service Select Dropdowns
---------------
| Name |
| source |
| destination |
#| destination |
---------------

View File

@ -19,5 +19,5 @@ Feature: dc / intentions / form-select: Intention Service Select Dropdowns
---------------
| Name |
| source |
| destination |
# | destination |
---------------

View File

@ -26,7 +26,6 @@ Feature: page-navigation
| nodes | /dc-1/nodes | /v1/internal/ui/nodes?dc=dc-1 |
| kvs | /dc-1/kv | /v1/kv/?keys&dc=dc-1&separator=%2F&ns=@namespace |
| acls | /dc-1/acls/tokens | /v1/acl/tokens?dc=dc-1&ns=@namespace |
| intentions | /dc-1/intentions | /v1/connect/intentions?dc=dc-1 |
# | settings | /settings | /v1/catalog/datacenters |
-------------------------------------------------------------------------------------
Scenario: Clicking a [Item] in the [Model] listing and back again
@ -87,20 +86,6 @@ Feature: page-navigation
- /v1/namespaces
- /v1/acl/policies?dc=dc-1&ns=@namespace
---
Scenario: The intention detail page calls the correct API endpoints
When I visit the intention page for yaml
---
dc: dc-1
intention: intention
---
Then the url should be /dc-1/intentions/intention
Then the last GET requests included from yaml
---
- /v1/catalog/datacenters
- /v1/namespaces
- /v1/connect/intentions/intention?dc=dc-1
- /v1/internal/ui/services?dc=dc-1&ns=*
---
Scenario: Clicking a [Item] in the [Model] listing and cancelling
When I visit the [Model] page for yaml
@ -116,7 +101,6 @@ Feature: page-navigation
| Item | Model | URL | Back |
| kv | kvs | /dc-1/kv/0-key-value/edit | /dc-1/kv |
# | acl | acls | /dc-1/acls/anonymous | /dc-1/acls |
# | intention | intentions | /dc-1/intentions/ee52203d-989f-4f7a-ab5a-2bef004164ca | /dc-1/intentions |
--------------------------------------------------------------------------------------------------------
@ignore
Scenario: Clicking items in the listings, without depending on the salt ^

View File

@ -3,7 +3,8 @@ import { setupTest } from 'ember-qunit';
module('Integration | Adapter | intention', function(hooks) {
setupTest(hooks);
const dc = 'dc-1';
const id = 'intention-name';
const legacyId = 'intention-name';
const id = 'SourceNS:SourceName:DestinationNS:DestinationName';
test('requestForQuery returns the correct url', function(assert) {
const adapter = this.owner.lookup('adapter:intention');
const client = this.owner.lookup('service:client/http');
@ -16,7 +17,7 @@ module('Integration | Adapter | intention', function(hooks) {
test('requestForQueryRecord returns the correct url', function(assert) {
const adapter = this.owner.lookup('adapter:intention');
const client = this.owner.lookup('service:client/http');
const expected = `GET /v1/connect/intentions/${id}?dc=${dc}`;
const expected = `GET /v1/connect/intentions/exact?source=SourceNS%2FSourceName&destination=DestinationNS%2FDestinationName&dc=${dc}`;
const actual = adapter
.requestForQueryRecord(client.url, {
dc: dc,
@ -53,7 +54,7 @@ module('Integration | Adapter | intention', function(hooks) {
test('requestForUpdateRecord returns the correct url', function(assert) {
const adapter = this.owner.lookup('adapter:intention');
const client = this.owner.lookup('service:client/http');
const expected = `PUT /v1/connect/intentions/${id}?dc=${dc}`;
const expected = `PUT /v1/connect/intentions/${legacyId}?dc=${dc}`;
const actual = adapter
.requestForUpdateRecord(
client.url,
@ -61,6 +62,7 @@ module('Integration | Adapter | intention', function(hooks) {
{
Datacenter: dc,
ID: id,
LegacyID: legacyId,
}
)
.split('\n')[0];
@ -69,7 +71,7 @@ module('Integration | Adapter | intention', function(hooks) {
test('requestForDeleteRecord returns the correct url', function(assert) {
const adapter = this.owner.lookup('adapter:intention');
const client = this.owner.lookup('service:client/http');
const expected = `DELETE /v1/connect/intentions/${id}?dc=${dc}`;
const expected = `DELETE /v1/connect/intentions/${legacyId}?dc=${dc}`;
const actual = adapter
.requestForDeleteRecord(
client.url,
@ -77,6 +79,7 @@ module('Integration | Adapter | intention', function(hooks) {
{
Datacenter: dc,
ID: id,
LegacyID: legacyId,
}
)
.split('\n')[0];

View File

@ -23,7 +23,7 @@ module('Integration | Serializer | intention', function(hooks) {
// TODO: default isn't required here, once we've
// refactored out our Serializer this can go
Namespace: nspace,
uid: `["${nspace}","${dc}","${item.ID}"]`,
uid: `["${nspace}","${dc}","${item.SourceNS}:${item.SourceName}:${item.DestinationNS}:${item.DestinationName}"]`,
})
);
const actual = serializer.respondForQuery(
@ -46,7 +46,17 @@ module('Integration | Serializer | intention', function(hooks) {
const request = {
url: `/v1/connect/intentions/${id}?dc=${dc}`,
};
const item = {
SourceNS: 'SourceNS',
SourceName: 'SourceName',
DestinationNS: 'DestinationNS',
DestinationName: 'DestinationName',
};
return get(request.url).then(function(payload) {
payload = {
...payload,
...item,
};
const expected = Object.assign({}, payload, {
Datacenter: dc,
[META]: {
@ -56,7 +66,7 @@ module('Integration | Serializer | intention', function(hooks) {
// TODO: default isn't required here, once we've
// refactored out our Serializer this can go
Namespace: nspace,
uid: `["${nspace}","${dc}","${id}"]`,
uid: `["${nspace}","${dc}","${item.SourceNS}:${item.SourceName}:${item.DestinationNS}:${item.DestinationName}"]`,
});
const actual = serializer.respondForQueryRecord(
function(cb) {

View File

@ -1,79 +0,0 @@
import { moduleFor, test } from 'ember-qunit';
import repo from 'consul-ui/tests/helpers/repo';
import { get } from '@ember/object';
const NAME = 'intention';
moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, {
integration: true,
});
const now = new Date().getTime();
const dc = 'dc-1';
const id = 'token-name';
const nspace = 'default';
test('findAllByDatacenter returns the correct data for list endpoint', function(assert) {
get(this.subject(), 'store').serializerFor(NAME).timestamp = function() {
return now;
};
return repo(
'Intention',
'findAllByDatacenter',
this.subject(),
function retrieveStub(stub) {
return stub(`/v1/connect/intentions?dc=${dc}`, {
CONSUL_INTENTION_COUNT: '1',
});
},
function performTest(service) {
return service.findAllByDatacenter(dc);
},
function performAssertion(actual, expected) {
assert.deepEqual(
actual[0],
expected(function(payload) {
const item = payload[0];
return {
...item,
CreatedAt: new Date(item.CreatedAt),
UpdatedAt: new Date(item.UpdatedAt),
Legacy: true,
SyncTime: now,
Datacenter: dc,
// TODO: nspace isn't required here, once we've
// refactored out our Serializer this can go
uid: `["${nspace}","${dc}","${item.ID}"]`,
};
})
);
}
);
});
test('findBySlug returns the correct data for item endpoint', function(assert) {
return repo(
'Intention',
'findBySlug',
this.subject(),
function(stub) {
return stub(`/v1/connect/intentions/${id}?dc=${dc}`);
},
function(service) {
return service.findBySlug(id, dc);
},
function(actual, expected) {
assert.deepEqual(
actual,
expected(function(payload) {
const item = payload;
return Object.assign({}, item, {
Legacy: true,
CreatedAt: new Date(item.CreatedAt),
UpdatedAt: new Date(item.UpdatedAt),
Datacenter: dc,
// TODO: nspace isn't required here, once we've
// refactored out our Serializer this can go
uid: `["${nspace}","${dc}","${item.ID}"]`,
});
})
);
}
);
});

View File

@ -1520,9 +1520,9 @@
js-yaml "^3.13.1"
"@hashicorp/consul-api-double@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-5.0.0.tgz#099e56ded356421cdfa5e63b4a07c9a2232ffb88"
integrity sha512-2+Rg4mfxTTUrJiYeRWV5mEWVZTYUK1udFNMb79ygNdC/HScDvU8sTVwPrf6GuRve6oLakk1lB/D4d6AsMmtS4w==
version "5.0.1"
resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-5.0.1.tgz#07880706ab26cc242332cef86b2c03b3b4ec4e56"
integrity sha512-uptXq/XTGL5uzGqvwRqC0tzHKCJMVAaRMucPxjbMb4r9wOmOdT4Z2BUJD8GDcCSFIWE8hbWeqAlCXRrokZ3wbw==
"@hashicorp/ember-cli-api-double@^3.1.0":
version "3.1.1"