From daa23f66c41c498cc0a3f5796f04490ffcb15d30 Mon Sep 17 00:00:00 2001 From: hashicc <191911133+hashicc@users.noreply.github.com> Date: Tue, 3 Jun 2025 09:40:32 -0400 Subject: [PATCH] ICU-16785 sort admin scopes (#2842) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 add sorting to scopes/scope/scopes/index route ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16785 * refactor: 💡 use direct hds components to support sorting ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16785 * feat: 🎸 wire up sorting to the scopes table ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16785 * test: 💍 add tests for sorting scopes ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-16785 --- .../controllers/scopes/scope/scopes/index.js | 17 +++- .../app/routes/scopes/scope/scopes/index.js | 24 ++++- .../templates/scopes/scope/scopes/index.hbs | 66 ++++++++----- ui/admin/tests/acceptance/scopes/list-test.js | 99 ++++++++++++++++++- 4 files changed, 177 insertions(+), 29 deletions(-) diff --git a/ui/admin/app/controllers/scopes/scope/scopes/index.js b/ui/admin/app/controllers/scopes/scope/scopes/index.js index 8cf136e2d..4046ae853 100644 --- a/ui/admin/app/controllers/scopes/scope/scopes/index.js +++ b/ui/admin/app/controllers/scopes/scope/scopes/index.js @@ -10,11 +10,19 @@ import { tracked } from '@glimmer/tracking'; export default class ScopesScopeScopesIndexController extends Controller { // =attributes - queryParams = ['search', 'page', 'pageSize']; + queryParams = [ + 'search', + 'page', + 'pageSize', + 'sortAttribute', + 'sortDirection', + ]; @tracked search; @tracked page = 1; @tracked pageSize = 10; + @tracked sortAttribute; + @tracked sortDirection; // =methods @@ -28,4 +36,11 @@ export default class ScopesScopeScopesIndexController extends Controller { this.search = value; this.page = 1; } + + @action + sortBy(attribute, direction) { + this.sortAttribute = attribute; + this.sortDirection = direction; + this.page = 1; + } } diff --git a/ui/admin/app/routes/scopes/scope/scopes/index.js b/ui/admin/app/routes/scopes/scope/scopes/index.js index 940d702c2..a843f7f52 100644 --- a/ui/admin/app/routes/scopes/scope/scopes/index.js +++ b/ui/admin/app/routes/scopes/scope/scopes/index.js @@ -21,6 +21,14 @@ export default class ScopesScopeScopesIndexRoute extends Route { pageSize: { refreshModel: true, }, + sortAttribute: { + refreshModel: true, + replace: true, + }, + sortDirection: { + refreshModel: true, + replace: true, + }, }; // =services @@ -41,7 +49,14 @@ export default class ScopesScopeScopesIndexRoute extends Route { } retrieveData = restartableTask( - async ({ search, page, pageSize, useDebounce }) => { + async ({ + search, + page, + pageSize, + sortAttribute, + sortDirection, + useDebounce, + }) => { if (useDebounce) { await timeout(250); } @@ -52,9 +67,14 @@ export default class ScopesScopeScopesIndexRoute extends Route { scope_id: [{ equals: scope_id }], }; + const sort = { + attribute: sortAttribute, + direction: sortDirection, + }; + const subScopes = await this.store.query('scope', { scope_id, - query: { search, filters }, + query: { search, filters, sort }, page, pageSize, }); diff --git a/ui/admin/app/templates/scopes/scope/scopes/index.hbs b/ui/admin/app/templates/scopes/scope/scopes/index.hbs index 44d5191fd..4d3e0151b 100644 --- a/ui/admin/app/templates/scopes/scope/scopes/index.hbs +++ b/ui/admin/app/templates/scopes/scope/scopes/index.hbs @@ -59,36 +59,52 @@ {{#if @model.subScopes}} + <:head as |H|> + + + {{t 'form.name.label'}} + + + {{t 'form.id.label'}} + + + <:body as |B|> - - - {{#if (can 'read model' B.data)}} - - {{B.data.displayName}} - - {{else}} + {{#each @model.subScopes as |data|}} + + + {{#if (can 'read model' data)}} + + {{data.displayName}} + + {{else}} + + {{data.displayName}} + + {{/if}} - {{B.data.displayName}} + {{data.description}} - {{/if}} - - {{B.data.description}} - - - - - - + + + + + + {{/each}} { + return this.server.create('scope', { + id: `o_${year}`, + type: 'org', + name: `Org ${year}`, + scope: { id: 'global', type: 'global' }, + created_time: `${year}-01-01T00:00:00Z`, + }); + }); + + await visit(urls.globalScope); + + assert.dom(commonSelectors.TABLE_ROWS).exists({ count: years.length }); + assert.dom(commonSelectors.TABLE_ROW(1)).containsText('Org 2026'); + assert.dom(commonSelectors.TABLE_ROW(2)).containsText('Org 2025'); + assert.dom(commonSelectors.TABLE_ROW(3)).containsText('Org 2024'); + assert.dom(commonSelectors.TABLE_ROW(4)).containsText('Org 2023'); + }); + + test.each( + 'sorting', + { + 'on name': { + attribute: { + key: 'name', + values: ['Alpha', 'Beta', 'Delta', 'Gamma', 'Epsilon'], + }, + expectedAscendingSort: ['Alpha', 'Beta', 'Delta', 'Epsilon', 'Gamma'], + column: 1, + }, + 'on id': { + attribute: { + key: 'id', + values: ['o_0001', 'o_0010', 'o_0100', 'o_1000', 'o_10000'], + }, + expectedAscendingSort: [ + 'o_0001', + 'o_0010', + 'o_0100', + 'o_1000', + 'o_10000', + ], + column: 2, + }, + }, + async function (assert, input) { + this.server.schema.scopes.all().destroy(); + + faker.helpers.shuffle(input.attribute.values).forEach((value) => { + this.server.create('scope', { + [input.attribute.key]: value, + type: 'org', + scope: { id: 'global', type: 'global' }, + }); + }); + + await visit(urls.globalScope); + + // click the sort button again to sort in ascending order + 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); + }); + }, + ); });