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);
+ });
+ },
+ );
});