Skip to content

Commit

Permalink
Merge pull request #4572 from hashicorp/f-ui-region-switcher
Browse files Browse the repository at this point in the history
UI: Region Switcher
  • Loading branch information
DingoEatingFuzz committed Aug 13, 2018
2 parents b05b00f + a61ad7a commit 3fd11d4
Show file tree
Hide file tree
Showing 39 changed files with 847 additions and 179 deletions.
12 changes: 12 additions & 0 deletions ui/app/adapters/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const namespace = 'v1';
export default RESTAdapter.extend({
namespace,

system: service(),
token: service(),

headers: computed('token.secret', function() {
Expand All @@ -35,6 +36,17 @@ export default RESTAdapter.extend({
});
},

ajaxOptions(url, type, options = {}) {
options.data || (options.data = {});
if (this.get('system.shouldIncludeRegion')) {
const region = this.get('system.activeRegion');
if (region) {
options.data.region = region;
}
}
return this._super(url, type, options);
},

// In order to remove stale records from the store, findHasMany has to unload
// all records related to the request in question.
findHasMany(store, snapshot, link, relationship) {
Expand Down
2 changes: 1 addition & 1 deletion ui/app/adapters/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default ApplicationAdapter.extend({
namespace: namespace + '/acl',

findSelf() {
return this.ajax(`${this.buildURL()}/token/self`).then(token => {
return this.ajax(`${this.buildURL()}/token/self`, 'GET').then(token => {
const store = this.get('store');
store.pushPayload('token', {
tokens: [token],
Expand Down
2 changes: 2 additions & 0 deletions ui/app/components/global-header.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Component from '@ember/component';

export default Component.extend({
'data-test-global-header': true,

onHamburgerClick() {},
});
21 changes: 21 additions & 0 deletions ui/app/components/region-switcher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';

export default Component.extend({
system: service(),
router: service(),
store: service(),

sortedRegions: computed('system.regions', function() {
return this.get('system.regions')
.toArray()
.sort();
}),

gotoRegion(region) {
this.get('router').transitionTo('jobs', {
queryParams: { region },
});
},
});
7 changes: 7 additions & 0 deletions ui/app/controllers/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import codesForError from '../utils/codes-for-error';

export default Controller.extend({
config: service(),
system: service(),

queryParams: {
region: 'region',
},

region: null,

error: null,

Expand Down
24 changes: 0 additions & 24 deletions ui/app/controllers/jobs.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { inject as service } from '@ember/service';
import Controller from '@ember/controller';
import { observer } from '@ember/object';
import { run } from '@ember/runloop';

export default Controller.extend({
system: service(),
Expand All @@ -13,26 +11,4 @@ export default Controller.extend({
isForbidden: false,

jobNamespace: 'default',

// The namespace query param should act as an alias to the system active namespace.
// But query param defaults can't be CPs: https://github.com/emberjs/ember.js/issues/9819
syncNamespaceService: forwardNamespace('jobNamespace', 'system.activeNamespace'),
syncNamespaceParam: forwardNamespace('system.activeNamespace', 'jobNamespace'),
});

function forwardNamespace(source, destination) {
return observer(source, `${source}.id`, function() {
const newNamespace = this.get(`${source}.id`) || this.get(source);
const currentNamespace = this.get(`${destination}.id`) || this.get(destination);
const bothAreDefault =
(currentNamespace == undefined || currentNamespace === 'default') &&
(newNamespace == undefined || newNamespace === 'default');

if (currentNamespace !== newNamespace && !bothAreDefault) {
this.set(destination, newNamespace);
run.next(() => {
this.send('refreshRoute');
});
}
});
}
26 changes: 11 additions & 15 deletions ui/app/controllers/jobs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,17 @@ export default Controller.extend(Sortable, Searchable, {
Filtered jobs are those that match the selected namespace and aren't children
of periodic or parameterized jobs.
*/
filteredJobs: computed(
'model.[]',
'model.@each.parent',
'system.activeNamespace',
'system.namespaces.length',
function() {
const hasNamespaces = this.get('system.namespaces.length');
const activeNamespace = this.get('system.activeNamespace.id') || 'default';

return this.get('model')
.compact()
.filter(job => !hasNamespaces || job.get('namespace.id') === activeNamespace)
.filter(job => !job.get('parent.content'));
}
),
filteredJobs: computed('model.[]', 'model.@each.parent', function() {
// Namespace related properties are ommitted from the dependent keys
// due to a prop invalidation bug caused by region switching.
const hasNamespaces = this.get('system.namespaces.length');
const activeNamespace = this.get('system.activeNamespace.id') || 'default';

return this.get('model')
.compact()
.filter(job => !hasNamespaces || job.get('namespace.id') === activeNamespace)
.filter(job => !job.get('parent.content'));
}),

listToSort: alias('filteredJobs'),
listToSearch: alias('listSorted'),
Expand Down
2 changes: 2 additions & 0 deletions ui/app/controllers/settings/tokens.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getOwner } from '@ember/application';

export default Controller.extend({
token: service(),
system: service(),
store: service(),

secret: reads('token.secret'),
Expand Down Expand Up @@ -43,6 +44,7 @@ export default Controller.extend({

// Clear out all data to ensure only data the new token is privileged to
// see is shown
this.get('system').reset();
this.resetStore();

// Immediately refetch the token now that the store is empty
Expand Down
53 changes: 53 additions & 0 deletions ui/app/routes/application.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,69 @@
import { inject as service } from '@ember/service';
import { next } from '@ember/runloop';
import Route from '@ember/routing/route';
import { AbortError } from 'ember-data/adapters/errors';
import RSVP from 'rsvp';

export default Route.extend({
config: service(),
system: service(),
store: service(),

queryParams: {
region: {
refreshModel: true,
},
},

resetController(controller, isExiting) {
if (isExiting) {
controller.set('error', null);
}
},

beforeModel(transition) {
return RSVP.all([this.get('system.regions'), this.get('system.defaultRegion')]).then(
promises => {
if (!this.get('system.shouldShowRegions')) return promises;

const queryParam = transition.queryParams.region;
const defaultRegion = this.get('system.defaultRegion.region');
const currentRegion = this.get('system.activeRegion') || defaultRegion;

// Only reset the store if the region actually changed
if (
(queryParam && queryParam !== currentRegion) ||
(!queryParam && currentRegion !== defaultRegion)
) {
this.get('system').reset();
this.get('store').unloadAll();
}

this.set('system.activeRegion', queryParam || defaultRegion);

return promises;
}
);
},

// Model is being used as a way to transfer the provided region
// query param to update the controller state.
model(params) {
return params.region;
},

setupController(controller, model) {
const queryParam = model;

if (queryParam === this.get('system.defaultRegion.region')) {
next(() => {
controller.set('region', null);
});
}

return this._super(...arguments);
},

actions: {
didTransition() {
if (!this.get('config.isTest')) {
Expand Down
36 changes: 13 additions & 23 deletions ui/app/routes/jobs.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { inject as service } from '@ember/service';
import Route from '@ember/routing/route';
import { run } from '@ember/runloop';
import WithForbiddenState from 'nomad-ui/mixins/with-forbidden-state';
import notifyForbidden from 'nomad-ui/utils/notify-forbidden';

Expand All @@ -15,34 +14,25 @@ export default Route.extend(WithForbiddenState, {
},
],

beforeModel() {
return this.get('system.namespaces');
},

model() {
return this.get('store')
.findAll('job', { reload: true })
.catch(notifyForbidden(this));
queryParams: {
jobNamespace: {
refreshModel: true,
},
},

syncToController(controller) {
const namespace = this.get('system.activeNamespace.id');
beforeModel(transition) {
return this.get('system.namespaces').then(namespaces => {
const queryParam = transition.queryParams.namespace;
this.set('system.activeNamespace', queryParam || 'default');

// The run next is necessary to let the controller figure
// itself out before updating QPs.
// See: https://github.com/emberjs/ember.js/issues/5465
run.next(() => {
if (namespace && namespace !== 'default') {
controller.set('jobNamespace', namespace);
} else {
controller.set('jobNamespace', 'default');
}
return namespaces;
});
},

setupController(controller) {
this.syncToController(controller);
return this._super(...arguments);
model() {
return this.get('store')
.findAll('job', { reload: true })
.catch(notifyForbidden(this));
},

actions: {
Expand Down
Loading

0 comments on commit 3fd11d4

Please sign in to comment.