refactor: 💡 use recrusive listing for desktop queries

and add mock rescursive support for scopes, targets, and sessions
This commit is contained in:
Randall Morey committed 2021-02-10 13:19:24 -05:00
1 parent 450b8c2ab0
commit 0b8550c136
11 files changed
+132 -110

No files matched your search

+8
View File
@@ -32,6 +32,14 @@ export default class SessionModel extends GeneratedSessionModel {
: null;
}
/**
* The target associated with this session (if loaded).
* @type {TargetModel}
*/
get target() {
return this.store.peekRecord('target', this.target_id);
}
// =methods
/**
+8
View File
@@ -45,6 +45,14 @@ export default class TargetModel extends GeneratedTargetModel {
.filter(s => s && (s.target_id === this.id));
}
/**
* The project associated with this target (if already loaded).
* @type {ScopeModel}
*/
get project() {
return this.store.peekRecord('scope', this.scopeID);
}
/**
* True if any sessions associated with this target are active or pending.
* @type {boolean}
+30 -9
View File
@@ -23,13 +23,16 @@ export default function() {
// Scope resources
this.get('/scopes', ({ scopes }, { queryParams: { scope_id } }) => {
this.get('/scopes', ({ scopes }, { queryParams: { scope_id, recursive } }) => {
// Default parent scope is global
if (!scope_id) scope_id = 'global';
const results = scopes.where(scope => {
return scope.scope ? scope.scope.id === scope_id : false
});
return results;
if (recursive && scope_id === 'global') {
return scopes.all();
} else {
return scopes.where(scope => {
return scope.scope ? scope.scope.id === scope_id : false
});
}
});
this.post('/scopes', function ({ scopes }) {
// Parent scope comes through the payload via `scope_id`, but this needs
@@ -287,7 +290,16 @@ export default function() {
// target
this.get('/targets', function ({ targets }, { queryParams: { scope_id } }) {
this.get('/targets', function ({ targets }, { queryParams: { scope_id, recursive } }) {
if (recursive && scope_id === 'global') {
return targets.all();
} else if (recursive) {
return targets.where(target => {
const targetModel = targets.find(target.id);
return (target.scopeId === scope_id)
|| (targetModel?.scope?.scope?.id === scope_id)
});
}
return targets.where(target => target.scopeId === scope_id);
});
this.post('/targets', function ({ targets }) {
@@ -325,7 +337,7 @@ export default function() {
// session
this.get('/sessions', function ({ sessions }, { queryParams: { scope_id } }) {
this.get('/sessions', function ({ sessions }, { queryParams: { scope_id, recursive } }) {
// To simulate changes to `session.status` that may occur in the backend,
// we quietly randomize the value of the field on GET.
// To populate sessions for logged in user,
@@ -333,7 +345,7 @@ export default function() {
// But only if not in testing mode.
// In tests, we need deterministic statuses.
if (!Ember.testing) {
sessions.where(session => session.scopeId === scope_id)
sessions.all()
.models
.forEach(session => {
session.update({
@@ -344,7 +356,16 @@ export default function() {
});
});
}
return sessions.where(session => session.scopeId === scope_id)
if (recursive && scope_id === 'global') {
return sessions.all();
} else if (recursive) {
return sessions.where(session => {
const sessionModel = sessions.find(session.id);
return (session.scopeId === scope_id)
|| (sessionModel?.scope?.scope?.id === scope_id)
});
}
return sessions.where(session => session.scopeId === scope_id);
});
this.get('/sessions/:id', function ({ sessions }, { params: { id } }) {
const session = sessions.find(id);
@@ -0,0 +1,27 @@
import Controller from '@ember/controller';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';
export default class ScopesScopeProjectsSessionsIndexController extends Controller {
// =sessions
@service session;
// =attributes
@computed('model.@each.{created_time,active}', 'session.data.authenticated.user_id')
get sorted() {
const userId = this.session.data.authenticated.user_id;
const sessions = this.model;
const sortedSessions = sessions
.filter(session => session.user_id === userId)
.sortBy('session.created_time').reverse();
// Then move active sessions to the top...
return [
...sortedSessions.filter((session) => session.isCancelable),
...sortedSessions.filter((session) => !session.isCancelable),
];
}
}
+10 -10
View File
@@ -89,14 +89,14 @@ export default class ScopesScopeRoute extends Route {
});
}
/**
* In global scope, redirect to first org on authentication.
*/
redirect(model) {
const { isAuthenticated } = this.session;
const { isGlobal } = model;
if (isAuthenticated && isGlobal && this.scopes.orgs?.length) {
this.replaceWith('scopes.scope', this.scopes.orgs.firstObject.id);
}
}
// /**
// * In global scope, redirect to first org on authentication.
// */
// redirect(model) {
// const { isAuthenticated } = this.session;
// const { isGlobal } = model;
// if (isAuthenticated && isGlobal && this.scopes.orgs?.length) {
// this.replaceWith('scopes.scope', this.scopes.orgs.firstObject.id);
// }
// }
}
@@ -20,10 +20,9 @@ export default class ScopesScopeProjectsRoute extends Route {
* @return {Promise
*/
model() {
const currentScope = this.modelFor('scopes.scope');
return this.store.query('scope', {
scope_id: currentScope.id,
});
return this.store
.query('scope', { recursive: true, scope_id: 'global' })
.filter(({ isProject }) => isProject);
}
}
@@ -1,8 +1,6 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { all, hash } from 'rsvp';
import { task, timeout } from 'ember-concurrency';
import { A } from '@ember/array';
import config from '../../../../config/environment';
import { action } from '@ember/object';
import { notifySuccess, notifyError } from 'core/decorators/notify';
@@ -49,43 +47,9 @@ export default class ScopesScopeProjectsSessionsRoute extends Route {
* @return {Promise{[{session: SessionModel, target: TargetModel}]}}
*/
async model() {
const projects = this.modelFor('scopes.scope.projects');
const userId = this.session.data.authenticated.user_id;
let projectSessions = await all(
projects.map(({ id: scope_id }) =>
this.store.query('session', { scope_id })
)
);
// Filter sessions to current user
let sessions = projectSessions.map(
sessions => sessions.filter(
session => session.user_id === userId
)
).flat();
const sessionAggregates = await all(
sessions.map(session => hash({
session,
target: session.target_id
? (
this.store.peekRecord('target', session.target_id) ||
this.store.findRecord('target', session.target_id)
)
: null,
}))
);
// Sort sessions by time created...
let sortedSessionAggregates =
A(sessionAggregates).sortBy('session.created_time').reverse();
// Then move active sessions to the top...
sortedSessionAggregates = [
...sortedSessionAggregates.filter((aggregate) => aggregate.session.status === 'active'),
...sortedSessionAggregates.filter((aggregate) => aggregate.session.status !== 'active'),
];
return sortedSessionAggregates;
const { id: scope_id } = this.modelFor('scopes.scope');
await this.store.query('target', { recursive: true, scope_id });
return await this.store.query('session', { recursive: true, scope_id });
}
/**
@@ -1,6 +1,5 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { all } from 'rsvp';
import { action } from '@ember/object';
import { task, timeout } from 'ember-concurrency';
import { later } from '@ember/runloop';
@@ -33,7 +32,7 @@ export default class ScopesScopeProjectsTargetsRoute extends Route {
@task(function * () {
while(true) {
yield timeout(POLL_TIMEOUT_SECONDS * 1000);
yield this.refreshSessions();
//yield this.refreshSessions();
}
}).drop() poller;
@@ -51,20 +50,9 @@ export default class ScopesScopeProjectsTargetsRoute extends Route {
* @return {Promise{[TargetModel]}}
*/
async model() {
const projects = this.modelFor('scopes.scope.projects');
let projectTargets = await all(
projects.map(({ id: scope_id }) =>
this.store.query('target', { scope_id })
)
);
let targets = projectTargets.map((target) => target.toArray()).flat();
const { id: scope_id } = this.modelFor('scopes.scope');
await this.refreshSessions();
return targets.map((target) => {
return {
target,
project: this.store.peekRecord('scope', target.scopeID),
};
});
return this.store.query('target', { recursive: true, scope_id });
}
/**
@@ -73,13 +61,8 @@ export default class ScopesScopeProjectsTargetsRoute extends Route {
* TODO: query only for sessions associated with the current user.
*/
async refreshSessions() {
const projects = this.modelFor('scopes.scope.projects');
//const userId = this.session.data.authenticated.user_id;
await all(
projects.map(({ id: scope_id }) =>
this.store.query('session', { scope_id })
)
);
const { id: scope_id } = this.modelFor('scopes.scope');
await this.store.query('session', { recursive: true, scope_id });
}
/**
@@ -115,7 +98,7 @@ export default class ScopesScopeProjectsTargetsRoute extends Route {
target_id: model.id,
token: this.session.data.authenticated.token
};
if (host) options.host_id = host.id;
// Create target session
@@ -6,7 +6,7 @@
<page.body>
{{#if @model}}
{{#if this.sorted}}
<Rose::Table as |table|>
<table.header as |header|>
@@ -20,22 +20,22 @@
</header.row>
</table.header>
<table.body as |body|>
{{#each @model as |sessionAggregate|}}
{{#each this.sorted as |model|}}
<body.row as |row|>
<row.headerCell class="nowrap">
<Rose::Icon
class={{concat "session-status-" sessionAggregate.session.status}}
class={{concat "session-status-" model.status}}
@name="app-icons/action-session"
@size="medium" />
<Copyable
@text={{sessionAggregate.session.id}}
@text={{model.id}}
@buttonText={{t "actions.copy-to-clipboard"}}
@acknowledgeText={{t "states.copied"}}
>
<code>{{sessionAggregate.session.id}}</code>
<code>{{model.id}}</code>
</Copyable>
</row.headerCell>
<row.cell>{{sessionAggregate.target.displayName}}</row.cell>
<row.cell>{{model.target.displayName}}</row.cell>
<row.cell>
{{!--
Two if's here because:
@@ -48,29 +48,29 @@
from other devices (or even another tab), in which case we
won't have the local proxy info available here.
--}}
{{#if sessionAggregate.session.isCancelable}}
{{#if sessionAggregate.session.proxy}}
{{#if model.isCancelable}}
{{#if model.proxy}}
<Copyable
@text={{sessionAggregate.session.proxy}}
@text={{model.proxy}}
@buttonText={{t "actions.copy-to-clipboard"}}
@acknowledgeText={{t "states.copied"}}
>
<code>{{sessionAggregate.session.proxy}}</code>
<code>{{model.proxy}}</code>
</Copyable>
{{/if}}
{{/if}}
</row.cell>
<row.cell>
<time datetime={{format-date-iso sessionAggregate.session.created_time}}>
{{format-date-iso-human sessionAggregate.session.created_time}}
<time datetime={{format-date-iso model.created_time}}>
{{format-date-iso-human model.created_time}}
</time>
</row.cell>
<row.cell>
<Rose::Badge>{{sessionAggregate.session.status}}</Rose::Badge>
<Rose::Badge>{{model.status}}</Rose::Badge>
</row.cell>
<row.cell>
{{#if sessionAggregate.session.isCancelable}}
<Rose::Button @style="secondary" {{on "click" (route-action "cancelSession" sessionAggregate.session)}}>
{{#if model.isCancelable}}
<Rose::Button @style="secondary" {{on "click" (route-action "cancelSession" model)}}>
{{t "actions.cancel"}}
</Rose::Button>
{{/if}}
@@ -22,32 +22,32 @@
<body.row as |row|>
<row.headerCell>
<Rose::Icon
class={{concat "session-status-" (if model.target.isActive 'active' 'canceled')}}
class={{concat "session-status-" (if model.isActive 'active' 'canceled')}}
@name="app-icons/action-session"
@size="medium"
title={{if model.target.isActive (t 'states.active')}}
title={{if model.isActive (t 'states.active')}}
/>
<LinkTo
@route="scopes.scope.projects.targets.target"
@model={{model.target.id}}
@model={{model.id}}
>
{{model.target.displayName}}
{{model.displayName}}
</LinkTo>
<p>{{model.target.description}}</p>
<p>{{model.description}}</p>
</row.headerCell>
<row.cell>
<Copyable
@text={{model.target.id}}
@text={{model.id}}
@buttonText={{t "actions.copy-to-clipboard"}}
@acknowledgeText={{t "states.copied"}}
>
<code>{{model.target.id}}</code>
<code>{{model.id}}</code>
</Copyable>
</row.cell>
<row.cell>
{{#if model.target.type}}
{{#if model.type}}
<Rose::Badge>
{{t (concat "resources.target.types." model.target.type)}}
{{t (concat "resources.target.types." model.type)}}
</Rose::Badge>
{{/if}}
</row.cell>
@@ -66,7 +66,7 @@
</Rose::Badge>
</row.cell>
<row.cell>
<Rose::Button @style="secondary" {{on "click" (route-action "connect" model.target)}}>
<Rose::Button @style="secondary" {{on "click" (route-action "connect" model)}}>
{{t "resources.session.actions.connect"}}
</Rose::Button>
</row.cell>
@@ -0,0 +1,12 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Controller | scopes/scope/projects/sessions/index', function(hooks) {
setupTest(hooks);
// TODO: Replace this with your real tests.
test('it exists', function(assert) {
let controller = this.owner.lookup('controller:scopes/scope/projects/sessions/index');
assert.ok(controller);
});
});