From c12edc7f735756ba3d35636cdcdc05558f61eb32 Mon Sep 17 00:00:00 2001 From: Lisbet Alvarez <107949262+lisbet-alvarez@users.noreply.github.com> Date: Thu, 22 May 2025 10:27:19 -0700 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20implement=20sort=20for?= =?UTF-8?q?=20session=20recordings=20(#2837)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 implement sort for session recordings * refactor: 💡 update selectors w/ pr feedback * refactor: 💡 remove click in tests * test: 💍 update selectors due to merge ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17065 --- .../scopes/scope/session-recordings/index.js | 16 ++ .../scopes/scope/session-recordings/index.js | 16 +- .../scopes/scope/session-recordings/index.hbs | 161 ++++++++++-------- .../session-recordings/list-test.js | 131 ++++++++++++-- 4 files changed, 240 insertions(+), 84 deletions(-) diff --git a/ui/admin/app/controllers/scopes/scope/session-recordings/index.js b/ui/admin/app/controllers/scopes/scope/session-recordings/index.js index 6b8531f70..e025b20ad 100644 --- a/ui/admin/app/controllers/scopes/scope/session-recordings/index.js +++ b/ui/admin/app/controllers/scopes/scope/session-recordings/index.js @@ -24,6 +24,8 @@ export default class ScopesScopeSessionRecordingsIndexController extends Control { targets: { type: 'array' } }, 'page', 'pageSize', + 'sortAttribute', + 'sortDirection', ]; now = new Date(); @@ -35,6 +37,8 @@ export default class ScopesScopeSessionRecordingsIndexController extends Control @tracked targets = []; @tracked page = 1; @tracked pageSize = 10; + @tracked sortAttribute; + @tracked sortDirection; /** * Returns object of filters to be used for displaying selected filters @@ -190,4 +194,16 @@ export default class ScopesScopeSessionRecordingsIndexController extends Control refresh() { this.send('refreshAll'); } + + /** + * Sets sort values and sets page to 1 + * @param {string} sortBy + * @param {string} sortOrder + */ + @action + onSort(sortBy, sortOrder) { + this.sortAttribute = sortBy; + this.sortDirection = sortOrder; + this.page = 1; + } } diff --git a/ui/admin/app/routes/scopes/scope/session-recordings/index.js b/ui/admin/app/routes/scopes/scope/session-recordings/index.js index 9f3b39aea..2b00a6cff 100644 --- a/ui/admin/app/routes/scopes/scope/session-recordings/index.js +++ b/ui/admin/app/routes/scopes/scope/session-recordings/index.js @@ -43,6 +43,14 @@ export default class ScopesScopeSessionRecordingsIndexRoute extends Route { pageSize: { refreshModel: true, }, + sortAttribute: { + refreshModel: true, + replace: true, + }, + sortDirection: { + refreshModel: true, + replace: true, + }, }; allSessionRecordings; @@ -66,6 +74,8 @@ export default class ScopesScopeSessionRecordingsIndexRoute extends Route { targets, page, pageSize, + sortAttribute, + sortDirection, useDebounce, }) => { if (useDebounce) { @@ -100,10 +110,14 @@ export default class ScopesScopeSessionRecordingsIndexRoute extends Route { collection: 'session-recordings', }) ) { + const sort = { + attribute: sortAttribute, + direction: sortDirection, + }; const queryOptions = { scope_id, recursive: true, - query: { search, filters }, + query: { search, filters, sort }, page, pageSize, }; diff --git a/ui/admin/app/templates/scopes/scope/session-recordings/index.hbs b/ui/admin/app/templates/scopes/scope/session-recordings/index.hbs index 6f739d960..07458cbc6 100644 --- a/ui/admin/app/templates/scopes/scope/session-recordings/index.hbs +++ b/ui/admin/app/templates/scopes/scope/session-recordings/index.hbs @@ -126,85 +126,104 @@ {{#if @model.sessionRecordings}} + <:head as |H|> + + {{t 'resources.session-recording.form.time.label'}} + {{t 'resources.session-recording.form.status.label'}} + {{t 'resources.session-recording.form.user.label'}} + {{t 'resources.session-recording.form.target.label'}} + {{t + 'resources.session-recording.form.duration.label' + }} + {{t + 'actions.view' + }} + + <:body as |B|> - - {{! Created Time }} - - {{#if B.data.created_time}} - - - - {{/if}} - - {{! Status }} - - - - {{! User }} - -
- - {{B.data.userDisplayName}} -
-
- {{! Target }} - -
- - - {{B.data.create_time_values.target.name}} - - - - -
-
- {{! Duration }} - - {{#if B.data.duration}} + {{#each @model.sessionRecordings as |data|}} + + {{! Created Time }} + + {{#if data.created_time}} + + + + {{/if}} + + {{! Status }} + + + + {{! User }} +
+ + {{data.userDisplayName}} +
+
+ {{! Target }} + +
- - {{format-time-duration B.data.duration}} + + {{data.create_time_values.target.name}} + + +
- {{/if}} -
- - {{#if (can 'read session-recording' B.data)}} - - {{/if}} - -
+
+ {{! Duration }} + + {{#if data.duration}} +
+ + + {{format-time-duration data.duration}} + +
+ {{/if}} +
+ + {{#if (can 'read session-recording' data)}} + + {{/if}} + +
+ {{/each}}
{ + this.server.create('session-recording', { + created_time: value, + scope: instances.scopes.global, + create_time_values: createdTimeValues, + }); + }); + await visit(urls.sessionRecordings); + + assert + .dom(commonSelectors.TABLE_ROWS) + .isVisible({ count: CREATED_TIME_VALUES_ARRAY.length }); + expectedDescendingSort.forEach((expected, index) => { + // nth-child index starts at 1 + assert.dom(commonSelectors.TABLE_ROW(index + 1)).containsText(expected); + }); + }); + + test.each( + 'sorting', + { + 'on created_time': { + attribute: { + key: 'created_time', + values: CREATED_TIME_VALUES_ARRAY, + }, + expectedAscendingSort: CREATED_TIME_VALUES_ARRAY, + column: 1, + }, + 'on state': { + attribute: { + key: 'state', + values: [ + STATE_SESSION_RECORDING_AVAILABLE, + STATE_SESSION_RECORDING_UNKNOWN, + STATE_SESSION_RECORDING_STARTED, + ], + }, + expectedAscendingSort: ['Completed', 'Recording', 'Failed'], + column: 2, + }, + }, + + async function (assert, input) { + this.server.schema.sessionRecordings.all().destroy(); + faker.helpers.shuffle(input.attribute.values).forEach((value) => { + this.server.create('session-recording', { + [input.attribute.key]: value, + scope: instances.scopes.global, + create_time_values: createdTimeValues, + }); + }); + await visit(urls.sessionRecordings); + + // click the sort button to sort in ascending order for provided column key + await click(commonSelectors.TABLE_SORT_BTN(input.column)); + + assert.true(currentURL().includes('sortDirection=asc')); + assert.true( + currentURL().includes(`sortAttribute=${input.attribute.key}`), + ); + assert + .dom(commonSelectors.TABLE_SORT_BTN_ARROW_UP(input.column)) + .isVisible(); + assert + .dom(commonSelectors.TABLE_ROWS) + .isVisible({ count: input.attribute.values.length }); + input.expectedAscendingSort.forEach((expected, index) => { + // nth-child index starts at 1 + assert.dom(commonSelectors.TABLE_ROW(index + 1)).containsText(expected); + }); + + // click the sort button again to sort in descending order + await click(commonSelectors.TABLE_SORT_BTN(input.column)); + + assert.true(currentURL().includes('sortDirection=desc')); + assert.true( + currentURL().includes(`sortAttribute=${input.attribute.key}`), + ); + assert + .dom(commonSelectors.TABLE_SORT_BTN_ARROW_DOWN(input.column)) + .isVisible(); + input.expectedAscendingSort.toReversed().forEach((expected, index) => { + // nth-child index starts at 1 + assert.dom(commonSelectors.TABLE_ROW(index + 1)).containsText(expected); + }); + }, + ); });