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: Add setting to enable/disable blocking queries #5352

Merged
merged 1 commit into from
Feb 21, 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
29 changes: 29 additions & 0 deletions ui-v2/app/controllers/settings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Controller from '@ember/controller';
import { get, set } from '@ember/object';
import { inject as service } from '@ember/service';

export default Controller.extend({
repo: service('settings'),
dom: service('dom'),
actions: {
change: function(e, value, item) {
const event = get(this, 'dom').normalizeEvent(e, value);
// TODO: Switch to using forms like the rest of the app
// setting utils/form/builder for things to be done before we
// can do that. For the moment just do things normally its a simple
// enough form at the moment

const target = event.target;
const blocking = get(this, 'item.client.blocking');
switch (target.name) {
case 'client[blocking]':
if (typeof blocking === 'undefined') {
set(this, 'item.client', {});
}
set(this, 'item.client.blocking', !blocking);
this.send('update', get(this, 'item'));
break;
}
},
},
});
6 changes: 3 additions & 3 deletions ui-v2/app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ export const routes = {
_options: { path: '/' },
},
// The settings page is global.
// settings: {
// _options: { path: '/setting' },
// },
settings: {
_options: { path: '/setting' },
},
notfound: {
_options: { path: '/*path' },
},
Expand Down
16 changes: 10 additions & 6 deletions ui-v2/app/routes/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { inject as service } from '@ember/service';
import { hash } from 'rsvp';
import { get } from '@ember/object';

import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
export default Route.extend(WithBlockingActions, {
export default Route.extend({
client: service('client/http'),
repo: service('settings'),
dcRepo: service('repository/dc'),
model: function(params) {
Expand All @@ -24,8 +24,12 @@ export default Route.extend(WithBlockingActions, {
this._super(...arguments);
controller.setProperties(model);
},
// overwrite afterUpdate and afterDelete hooks
// to avoid the default 'return to listing page'
afterUpdate: function() {},
afterDelete: function() {},
actions: {
update: function(item) {
if (!get(item, 'client.blocking')) {
get(this, 'client').abort();
}
get(this, 'repo').persist(item);
},
},
});
3 changes: 3 additions & 0 deletions ui-v2/app/services/client/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ export default Service.extend({
});
}
},
abort: function(id = null) {
get(this, 'connections').purge();
},
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We've included an id argument here, to remind us at some point that it maybe useful to be able to abort individual connections here. We initially named it abortAll, and then changed to just abort when we figured this might be useful.

whenAvailable: function(e) {
const doc = get(this, 'dom').document();
// if we are using a connection limited protocol and the user has hidden the tab (hidden browser/tab switch)
Expand Down
17 changes: 13 additions & 4 deletions ui-v2/app/services/repository/type/event-source.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { cache as createCache, BlockingEventSource } from 'consul-ui/utils/dom/e
const createProxy = function(repo, find, settings, cache, serialize = JSON.stringify) {
// proxied find*..(id, dc)
const throttle = get(this, 'wait').execute;
const client = get(this, 'client');
return function() {
const key = `${repo.getModelName()}.${find}.${serialize([...arguments])}`;
const _args = arguments;
Expand All @@ -17,7 +18,7 @@ const createProxy = function(repo, find, settings, cache, serialize = JSON.strin
// take a copy of the original arguments
// this means we don't have any configuration object on it
let args = [..._args];
if (settings.blocking) {
if (configuration.settings.enabled) {
// ...and only add our current cursor/configuration if we are blocking
args = args.concat([configuration]);
}
Expand All @@ -26,7 +27,7 @@ const createProxy = function(repo, find, settings, cache, serialize = JSON.strin
// original find... with configuration now added
return repo[find](...args)
.then(res => {
if (!settings.blocking) {
if (!configuration.settings.enabled) {
// blocking isn't enabled, immediately close
this.close();
}
Expand All @@ -38,20 +39,27 @@ const createProxy = function(repo, find, settings, cache, serialize = JSON.strin
const status = get(e, 'errors.firstObject.status');
if (status === '0') {
// Any '0' errors (abort) should possibly try again, depending upon the circumstances
// whenAvailable returns a Promise that resolves when the client is available
// again
return client.whenAvailable(e);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This returns a Promise which resolves when the client (tab) becomes available again. As this is all based on a recurring Promise this essentially pauses the blocking query when the status of the error is zero (aborted) and then resolves the promise, and hence restarts the blocking query.

}
throw e;
});
};
// if we have a cursor (which means its at least the second call)
// and we have a throttle setting, wait for so many ms
if (typeof configuration.cursor !== 'undefined' && settings.throttle) {
return throttle(settings.throttle).then(cb);
if (typeof configuration.cursor !== 'undefined' && configuration.settings.throttle) {
return throttle(configuration.settings.throttle).then(cb);
}
return cb();
},
{
key: key,
type: BlockingEventSource,
settings: {
enabled: settings.blocking,
throttle: settings.throttle,
},
}
);
};
Expand All @@ -61,6 +69,7 @@ export default LazyProxyService.extend({
store: service('store'),
settings: service('settings'),
wait: service('timeout'),
client: service('client/http'),
init: function() {
this._super(...arguments);
if (cache === null) {
Expand Down
2 changes: 0 additions & 2 deletions ui-v2/app/templates/components/hashicorp-consul.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,9 @@
<li data-test-main-nav-docs>
<a href="{{ env 'CONSUL_DOCUMENTATION_URL'}}/index.html" rel="help noopener noreferrer" target="_blank">Documentation</a>
</li>
{{#if false }}
<li data-test-main-nav-settings class={{if (is-href 'settings') 'is-active'}}>
<a href={{href-to 'settings'}}>Settings</a>
</li>
{{/if}}
</ul>
</nav>
</div>
Expand Down
27 changes: 6 additions & 21 deletions ui-v2/app/templates/settings.hbs
Original file line number Diff line number Diff line change
@@ -1,20 +1,5 @@
{{#hashicorp-consul id="wrapper" dcs=dcs dc=dc}}
{{#app-view class="settings show"}}
{{#block-slot 'notification' as |status type|}}
{{#if (eq type 'update')}}
{{#if (eq status 'success') }}
Your settings were saved.
{{else}}
There was an error saving your settings.
{{/if}}
{{ else if (eq type 'delete')}}
{{#if (eq status 'success') }}
You settings have been reset.
{{else}}
There was an error resetting your settings.
{{/if}}
{{/if}}
{{/block-slot}}
{{#block-slot 'header'}}
<h1>
Settings
Expand All @@ -26,13 +11,13 @@
</p>
<form>
<fieldset>
<label class="type-text">
<span>ACL Token</span>
{{ input type='password' value=item.token name="token" }}
<em>The token is sent with requests as the <code>X-Consul-Token</code> HTTP header parameter. This is used to control the ACL for the web UI.</em>
</label>
<div class="type-toggle">
<label>
<input type="checkbox" name="client[blocking]" checked={{if item.client.blocking 'checked' }} onchange={{action 'change'}} />
<span>Enable Catalog realtime updates (blocking queries)</span>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried to be specific what this does as I've no idea when this will land in a release. We may be able to add in blocking queries to the rest of the app before then, we may not.

</label>
</div>
</fieldset>
<button type="submit" {{action 'update' item}}>Save</button>
</form>
{{/block-slot}}
{{/app-view}}
Expand Down
6 changes: 5 additions & 1 deletion ui-v2/app/utils/dom/event-source/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ export default function(source, DefaultEventSource, P = Promise) {
return function(sources) {
return function(cb, configuration) {
const key = configuration.key;
if (typeof sources[key] !== 'undefined') {
if (typeof sources[key] !== 'undefined' && configuration.settings.enabled) {
if (typeof sources[key].configuration === 'undefined') {
sources[key].configuration = {};
}
sources[key].configuration.settings = configuration.settings;
return source(sources[key]);
} else {
const EventSource = configuration.type || DefaultEventSource;
Expand Down
4 changes: 4 additions & 0 deletions ui-v2/app/utils/form/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,13 @@ export default function(changeset = defaultChangeset, getFormNameProperty = pars
}
const data = this.getData();
// ember-data/changeset dance
// TODO: This works for ember-data RecordSets and Changesets but not for plain js Objects
// see settings
const json = typeof data.toJSON === 'function' ? data.toJSON() : get(data, 'data').toJSON();
// if the form doesn't include a property then throw so it can be
// caught outside, therefore the user can deal with things that aren't in the data
// TODO: possibly need to add support for deeper properties using `get` here
// for example `client.blocking` instead of just `blocking`
if (!Object.keys(json).includes(prop)) {
const error = new Error(`${prop} property doesn't exist`);
error.target = target;
Expand Down
12 changes: 12 additions & 0 deletions ui-v2/tests/unit/controllers/settings-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { moduleFor, test } from 'ember-qunit';

moduleFor('controller:settings', 'Unit | Controller | settings', {
// Specify the other units that are required for this test.
needs: ['service:settings', 'service:dom'],
});

// Replace this with your real tests.
test('it exists', function(assert) {
let controller = this.subject();
assert.ok(controller);
});
1 change: 1 addition & 0 deletions ui-v2/tests/unit/routes/settings-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('route:settings', 'Unit | Route | settings', {
// Specify the other units that are required for this test.
needs: [
'service:client/http',
'service:repository/dc',
'service:settings',
'service:logger',
Expand Down
9 changes: 9 additions & 0 deletions ui-v2/tests/unit/utils/dom/event-source/cache-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,17 @@ test('cache creates the default EventSource and keeps it open when there is a cu
const cache = getCache(obj);
const promisedEventSource = cache(cb, {
key: 'key',
settings: {
enabled: true,
},
});
assert.ok(source.calledOnce, 'promisifying source called once');
assert.ok(promisedEventSource instanceof Promise, 'source returns a Promise');
const retrievedEventSource = cache(cb, {
key: 'key',
settings: {
enabled: true,
},
});
assert.deepEqual(promisedEventSource, retrievedEventSource);
assert.ok(source.calledTwice, 'promisifying source called once');
Expand All @@ -100,6 +106,9 @@ test('cache creates the default EventSource and keeps it open when there is a cu
const cache = getCache(obj);
const promisedEventSource = cache(cb, {
key: 0,
settings: {
enabled: true,
},
});
assert.ok(source.calledOnce, 'promisifying source called once');
assert.ok(cb.calledOnce, 'callable event source callable called once');
Expand Down