refactor: 💡 rewrite sessions polling for ember-pollster

This commit is contained in:
Randall Morey committed 2022-01-02 19:21:03 -05:00
1 parent 618d418093
commit fe509d2e86
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
+2 -1
View File
@@ -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'));
});
});