refactor: 💡 rewrite sessions polling for ember-pollster
This commit is contained in:
3 files changed
+50
-105
No files matched your search
@@ -1,15 +1,14 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { task, timeout } from 'ember-concurrency';
|
||||
import config from '../../../../config/environment';
|
||||
import { action } from '@ember/object';
|
||||
import runEvery from 'ember-pollster/decorators/route/run-every';
|
||||
import { notifySuccess, notifyError } from 'core/decorators/notify';
|
||||
import { resourceFilter } from 'core/decorators/resource-filter';
|
||||
import config from '../../../../config/environment';
|
||||
|
||||
const POLL_TIMEOUT_SECONDS = config.sessionPollingTimeoutSeconds;
|
||||
|
||||
export default class ScopesScopeProjectsSessionsRoute extends Route {
|
||||
|
||||
// =services
|
||||
|
||||
@service ipc;
|
||||
@@ -20,32 +19,16 @@ export default class ScopesScopeProjectsSessionsRoute extends Route {
|
||||
|
||||
@resourceFilter({
|
||||
allowed: ['active', 'pending', 'canceling', 'terminated'],
|
||||
defaultValue: ['active','pending', 'canceling']
|
||||
}) status;
|
||||
defaultValue: ['active', 'pending', 'canceling'],
|
||||
})
|
||||
status;
|
||||
|
||||
@resourceFilter({
|
||||
allowed: (route) => route.modelFor('scopes.scope.projects'),
|
||||
serialize: ({ id }) => id,
|
||||
findBySerialized: ({ id }, value) => id === value
|
||||
}) project;
|
||||
|
||||
/**
|
||||
* A simple Ember Concurrency-based polling task that refreshes the route
|
||||
* every POLL_TIMEOUT_SECONDS seconds. This is necessary to display changes
|
||||
* to session `status` that may occur.
|
||||
*
|
||||
* NOTE: tasks are sort of attributes and sort of methods, but they are not
|
||||
* language-level constructs. Thus we annotate this task as if it
|
||||
* is an attribute.
|
||||
* @type {Task}
|
||||
*/
|
||||
@task(function * () {
|
||||
while(true) {
|
||||
yield timeout(POLL_TIMEOUT_SECONDS * 1000);
|
||||
yield this.refresh();
|
||||
}
|
||||
/* eslint-disable-next-line prettier/prettier */
|
||||
}).drop() poller;
|
||||
findBySerialized: ({ id }, value) => id === value,
|
||||
})
|
||||
project;
|
||||
|
||||
// =methods
|
||||
|
||||
@@ -67,28 +50,23 @@ export default class ScopesScopeProjectsSessionsRoute extends Route {
|
||||
const { user_id } = this.session.data.authenticated;
|
||||
const projects = this.project || [];
|
||||
await this.store.query('target', { recursive: true, scope_id });
|
||||
return await this.resourceFilterStore.queryBy('session', {
|
||||
user_id,
|
||||
status,
|
||||
scope_id: projects.map(({ id }) => id)
|
||||
}, {
|
||||
recursive: true,
|
||||
scope_id
|
||||
});
|
||||
return await this.resourceFilterStore.queryBy(
|
||||
'session',
|
||||
{
|
||||
user_id,
|
||||
status,
|
||||
scope_id: projects.map(({ id }) => id),
|
||||
},
|
||||
{
|
||||
recursive: true,
|
||||
scope_id,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* When this route is activated (entered), begin polling for changes.
|
||||
*/
|
||||
activate() {
|
||||
this.poller.perform();
|
||||
}
|
||||
|
||||
/**
|
||||
* When this route is deactivated (exited), stop polling for changes.
|
||||
*/
|
||||
deactivate() {
|
||||
this.poller.cancelAll();
|
||||
@runEvery(POLL_TIMEOUT_SECONDS * 1000)
|
||||
poller() {
|
||||
return this.refresh();
|
||||
}
|
||||
|
||||
// =actions
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"ember-named-blocks-polyfill": "^0.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"js-bexpr": "hashicorp/js-bexpr#9b4a4b54d85eba67fdfc0990133d1518d890b1e1",
|
||||
"@doyensec/electronegativity": "^1.9.1",
|
||||
"@ember/optional-features": "^2.0.0",
|
||||
"@ember/render-modifiers": "^1.0.2",
|
||||
@@ -93,6 +92,7 @@
|
||||
"ember-modifier": "^2.1.2",
|
||||
"ember-notify": "^6.0.1",
|
||||
"ember-page-title": "^6.2.2",
|
||||
"ember-pollster": "^0.0.1-beta.2",
|
||||
"ember-qunit": "^5.1.4",
|
||||
"ember-resolver": "^8.0.3",
|
||||
"ember-source": "~3.28.4",
|
||||
@@ -105,6 +105,7 @@
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-qunit": "^6.2.0",
|
||||
"faker": "^5.5.3",
|
||||
"js-bexpr": "hashicorp/js-bexpr#9b4a4b54d85eba67fdfc0990133d1518d890b1e1",
|
||||
"loader.js": "^4.7.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"playwright": "^1.15.0",
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
//getRootElement
|
||||
//setupOnerror,
|
||||
} from '@ember/test-helpers';
|
||||
import { run, later } from '@ember/runloop';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
import { Response } from 'miragejs';
|
||||
@@ -167,112 +166,79 @@ module('Acceptance | projects | sessions', function (hooks) {
|
||||
test('visiting index', async function (assert) {
|
||||
assert.expect(2);
|
||||
const sessionsCount = this.server.schema.sessions.all().models.length;
|
||||
// This later/cancelTimers technique allows us to test a page with
|
||||
// active polling. Normally an acceptance test waits for all runloop timers
|
||||
// to stop before returning from an awaited test, but polling means that
|
||||
// runloop timers exist indefinitely. We thus schedule a cancelation before
|
||||
// proceeding with our tests.
|
||||
later(async () => {
|
||||
run.cancelTimers();
|
||||
// await a11yAudit();
|
||||
assert.equal(currentURL(), urls.sessions);
|
||||
assert.equal(findAll('tbody tr').length, sessionsCount);
|
||||
}, 750);
|
||||
await visit(urls.sessions);
|
||||
assert.equal(currentURL(), urls.sessions);
|
||||
assert.equal(findAll('tbody tr').length, sessionsCount);
|
||||
});
|
||||
|
||||
test('visiting empty sessions', async function (assert) {
|
||||
assert.expect(1);
|
||||
this.server.get('/sessions', () => new Response(200));
|
||||
later(async () => {
|
||||
run.cancelTimers();
|
||||
await a11yAudit();
|
||||
assert.ok(
|
||||
find('.rose-message-title').textContent.trim(),
|
||||
'No Sessions Available'
|
||||
);
|
||||
}, 750);
|
||||
await visit(urls.sessions);
|
||||
await a11yAudit();
|
||||
assert.ok(
|
||||
find('.rose-message-title').textContent.trim(),
|
||||
'No Sessions Available'
|
||||
);
|
||||
});
|
||||
|
||||
test('visiting sessions without targets is OK', async function (assert) {
|
||||
assert.expect(2);
|
||||
instances.session.update({ targetId: undefined });
|
||||
const sessionsCount = this.server.schema.sessions.all().models.length;
|
||||
later(async () => {
|
||||
run.cancelTimers();
|
||||
await a11yAudit();
|
||||
assert.equal(currentURL(), urls.sessions);
|
||||
assert.equal(findAll('tbody tr').length, sessionsCount);
|
||||
}, 750);
|
||||
await visit(urls.sessions);
|
||||
await a11yAudit();
|
||||
assert.equal(currentURL(), urls.sessions);
|
||||
assert.equal(findAll('tbody tr').length, sessionsCount);
|
||||
});
|
||||
|
||||
test('can identify active sessions', async function (assert) {
|
||||
assert.expect(1);
|
||||
later(async () => {
|
||||
run.cancelTimers();
|
||||
assert.ok(
|
||||
find('tbody tr:first-child th:first-child .session-status-active')
|
||||
);
|
||||
}, 750);
|
||||
await visit(urls.sessions);
|
||||
assert.ok(
|
||||
find('tbody tr:first-child th:first-child .session-status-active')
|
||||
);
|
||||
});
|
||||
|
||||
test('cannot identify pending sessions', async function (assert) {
|
||||
assert.expect(1);
|
||||
instances.session.update({ status: 'pending' });
|
||||
later(async () => {
|
||||
run.cancelTimers();
|
||||
assert.notOk(
|
||||
find('tbody tr:first-child th:first-child .session-status-active')
|
||||
);
|
||||
}, 750);
|
||||
await visit(urls.sessions);
|
||||
assert.notOk(
|
||||
find('tbody tr:first-child th:first-child .session-status-active')
|
||||
);
|
||||
});
|
||||
|
||||
test('cannot identify terminated sessions', async function (assert) {
|
||||
assert.expect(1);
|
||||
instances.session.update({ status: 'terminated' });
|
||||
later(async () => {
|
||||
run.cancelTimers();
|
||||
assert.notOk(
|
||||
find('tbody tr:first-child th:first-child .session-status-active')
|
||||
);
|
||||
}, 750);
|
||||
await visit(urls.sessions);
|
||||
assert.notOk(
|
||||
find('tbody tr:first-child th:first-child .session-status-active')
|
||||
);
|
||||
});
|
||||
|
||||
test('cancelling a session', async function (assert) {
|
||||
assert.expect(1);
|
||||
stubs.ipcService.withArgs('stop');
|
||||
later(async () => {
|
||||
run.cancelTimers();
|
||||
await click('tbody tr:first-child td:last-child button');
|
||||
assert.ok(find('[role="alert"].is-success'));
|
||||
}, 750);
|
||||
await visit(urls.sessions);
|
||||
await click('tbody tr:first-child td:last-child button');
|
||||
assert.ok(find('[role="alert"].is-success'));
|
||||
});
|
||||
|
||||
test('cancelling a session with error shows notification', async function (assert) {
|
||||
assert.expect(1);
|
||||
this.server.post('/sessions/:id_method', () => new Response(400));
|
||||
later(async () => {
|
||||
run.cancelTimers();
|
||||
await click('tbody tr:first-child td:last-child button');
|
||||
assert.ok(find('[role="alert"].is-error'));
|
||||
}, 750);
|
||||
await visit(urls.sessions);
|
||||
await click('tbody tr:first-child td:last-child button');
|
||||
assert.ok(find('[role="alert"].is-error'));
|
||||
});
|
||||
|
||||
test('cancelling a session with ipc error shows notification', async function (assert) {
|
||||
assert.expect(1);
|
||||
stubs.ipcService.withArgs('stop').throws();
|
||||
later(async () => {
|
||||
run.cancelTimers();
|
||||
await click('tbody tr:first-child td:last-child button');
|
||||
assert.ok(find('[role="alert"].is-error'));
|
||||
}, 750);
|
||||
await visit(urls.sessions);
|
||||
await click('tbody tr:first-child td:last-child button');
|
||||
assert.ok(find('[role="alert"].is-error'));
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user