ui: [BUGFIX] Request intention listing with ns parameter (#9432)

This PR adds the ns=* query parameter when namespaces are enabled to keep backwards compatibility with how the UI used to work (Intentions page always lists all intention across all namespace you have access to)

I found a tiny dev bug for printing out the current URL during acceptance testing and fixed that up while I was there.
This commit is contained in:
John Cowen 2021-01-04 17:22:10 +00:00 committed by GitHub
parent 1dcf7fe0f8
commit 7a21bd7720
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 107 additions and 30 deletions

4
.changelog/9432.txt Normal file
View File

@ -0,0 +1,4 @@
```release-note:bug
ui: request intention listing with ns=* parameter to retrieve all intentions
across namespaces
```

View File

@ -2,12 +2,16 @@ import Adapter, { DATACENTER_QUERY_PARAM as API_DATACENTER_KEY } from './applica
import { get } from '@ember/object';
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
// Intentions use SourceNS and DestinationNS properties for namespacing so we
// don't need to add the `?ns=` anywhere here
// Intentions have different namespacing to the rest of the UI in that the don't
// have a Namespace property, the DestinationNS is essentially its namespace.
// Listing of intentions still requires the `ns` query string parameter which
// will give us all the intentions that have the `ns` as either the SourceNS or
// the DestinationNS.
// We currently list intentions by the * wildcard namespace for back compat reasons
// TODO: Update to use this.formatDatacenter()
export default class IntentionAdapter extends Adapter {
requestForQuery(request, { dc, filter, index, uri }) {
requestForQuery(request, { dc, ns, filter, index, uri }) {
return request`
GET /v1/connect/intentions?${{ dc }}
X-Request-ID: ${uri}${
@ -18,6 +22,7 @@ export default class IntentionAdapter extends Adapter {
}
${{
...this.formatNspace('*'),
index,
filter,
}}

View File

@ -72,7 +72,7 @@ as |api|>
{{/if}}
<DataSource
@src={{concat '/' @nspace '/' @dc '/services'}}
@src={{concat '/*/' @dc '/services'}}
@onchange={{action this.createServices item}}
/>
{{#if (env 'CONSUL_NSPACES_ENABLED')}}

View File

@ -1,25 +1,31 @@
import { inject as service } from '@ember/service';
import Route from 'consul-ui/routing/route';
import { hash } from 'rsvp';
export default class EditRoute extends Route {
@service('repository/intention')
repo;
@service('repository/intention') repo;
@service('env') env;
model({ intention_id }, transition) {
async model({ intention_id }, transition) {
const dc = this.modelFor('dc').dc.Name;
const nspace = '*';
return hash({
dc: dc,
nspace: nspace,
item:
typeof intention_id !== 'undefined'
? this.repo.findBySlug(intention_id, dc, nspace)
: this.repo.create({
const nspace = this.modelFor('nspace').nspace.substr(1);
let item;
if (typeof intention_id !== 'undefined') {
item = await this.repo.findBySlug(intention_id, dc, nspace);
} else {
const defaultNspace = this.env.var('CONSUL_NSPACES_ENABLED') ? '*' : 'default';
item = await this.repo.create({
SourceNS: nspace || defaultNspace,
DestinationNS: nspace || defaultNspace,
Datacenter: dc,
}),
});
}
return {
dc,
nspace,
item,
};
}
setupController(controller, model) {
super.setupController(...arguments);

View File

@ -17,7 +17,7 @@ export default class IndexRoute extends Route {
model(params) {
return {
dc: this.modelFor('dc').dc.Name,
nspace: this.modelFor('nspace').nspace.substr(1) || 'default',
nspace: this.modelFor('nspace').nspace.substr(1),
};
}

View File

@ -30,7 +30,7 @@ export default class RepositoryService extends Service {
if (dc === meta.dc) {
if (checkNspace) {
const nspace = get(item, 'Namespace');
if (nspace !== meta.namespace) {
if (typeof nspace !== 'undefined' && nspace !== meta.nspace) {
return;
}
}

View File

@ -3,7 +3,51 @@ Feature: dc / intentions / create: Intention Create
In order to define intentions
As a user
I want to visit the intention create page, fill in the form and hit the create button and see a success notification
Scenario:
@onlyNamespaceable
Scenario: with namespaces enabled
Given 1 datacenter model with the value "datacenter"
And 3 service models from yaml
---
- Name: web
Kind: ~
- Name: db
Kind: ~
- Name: cache
Kind: ~
---
When I visit the intention page for yaml
---
dc: datacenter
---
Then the url should be /datacenter/intentions/create
And the title should be "New Intention - Consul"
# Set source
And I click "[data-test-source-element] .ember-power-select-trigger"
And I type "web" into ".ember-power-select-search-input"
And I click ".ember-power-select-option:first-child"
Then I see the text "web" in "[data-test-source-element] .ember-power-select-selected-item"
# Set destination
And I click "[data-test-destination-element] .ember-power-select-trigger"
And I type "db" into ".ember-power-select-search-input"
And I click ".ember-power-select-option:first-child"
Then I see the text "db" in "[data-test-destination-element] .ember-power-select-selected-item"
# Specifically set deny
And I click "[value=deny]"
And I submit
# TODO: When namespace is empty we expect *
# Then a PUT request was made to "/v1/connect/intentions/exact?source=@namespace%2Fweb&destination=@namespace%2Fdb&dc=datacenter" from yaml
# ---
# body:
# SourceName: web
# DestinationName: db
# Action: deny
# ---
Then the url should be /datacenter/intentions
And the title should be "Intentions - Consul"
And "[data-notification]" has the "notification-update" class
And "[data-notification]" has the "success" class
@notNamespaceable
Scenario: with namespaces disabled
Given 1 datacenter model with the value "datacenter"
And 3 service models from yaml
---

View File

@ -1,17 +1,35 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import getNspaceRunner from 'consul-ui/tests/helpers/get-nspace-runner';
const nspaceRunner = getNspaceRunner('intention');
module('Integration | Adapter | intention', function(hooks) {
setupTest(hooks);
const dc = 'dc-1';
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');
const expected = `GET /v1/connect/intentions?dc=${dc}`;
const actual = adapter.requestForQuery(client.requestParams.bind(client), {
return nspaceRunner(
(adapter, serializer, client) => {
return adapter.requestForQuery(client.body, {
dc: dc,
ns: 'team-1',
filter: '*',
index: 1,
});
assert.equal(`${actual.method} ${actual.url}`, expected);
},
{
filter: '*',
index: 1,
ns: '*',
},
{
filter: '*',
index: 1,
},
this,
assert
);
});
test('requestForQueryRecord returns the correct url', function(assert) {
const adapter = this.owner.lookup('adapter:intention');

View File

@ -102,7 +102,7 @@ export default function({
visit(library, pages, utils.setCurrentPage, reset);
click(library, utils.find, helpers.click);
form(library, utils.find, helpers.fillIn, helpers.triggerKeyEvent, utils.getCurrentPage);
debug(library, assert, utils.currentURL);
debug(library, assert, helpers.currentURL);
assertHttp(library, assert, lastNthRequest);
assertModel(library, assert, utils.find, utils.getCurrentPage, pauseUntil, pluralize);
assertPage(library, assert, utils.find, utils.getCurrentPage, $);