From fe509d2e86e71a65a05db2eda6743f78956b59dc Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sun, 2 Jan 2022 19:21:03 -0500 Subject: [PATCH] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20rewrite=20sessions?= =?UTF-8?q?=20polling=20for=20ember-pollster?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../routes/scopes/scope/projects/sessions.js | 68 +++++---------- ui/desktop/package.json | 3 +- .../acceptance/projects/sessions-test.js | 84 ++++++------------- 3 files changed, 50 insertions(+), 105 deletions(-) diff --git a/ui/desktop/app/routes/scopes/scope/projects/sessions.js b/ui/desktop/app/routes/scopes/scope/projects/sessions.js index b353fb8a7..668d0cc52 100644 --- a/ui/desktop/app/routes/scopes/scope/projects/sessions.js +++ b/ui/desktop/app/routes/scopes/scope/projects/sessions.js @@ -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 diff --git a/ui/desktop/package.json b/ui/desktop/package.json index e1af18657..8bbca0a80 100755 --- a/ui/desktop/package.json +++ b/ui/desktop/package.json @@ -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", diff --git a/ui/desktop/tests/acceptance/projects/sessions-test.js b/ui/desktop/tests/acceptance/projects/sessions-test.js index 2b31e5eaa..e2e4db9e0 100644 --- a/ui/desktop/tests/acceptance/projects/sessions-test.js +++ b/ui/desktop/tests/acceptance/projects/sessions-test.js @@ -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')); }); });