Skip to content

Commit

Permalink
UI: helper sort-objects to alphabetize list items (#24103)
Browse files Browse the repository at this point in the history
* move list to component

* use helper instead

* add changelog

* clarify changelog copy

* delete components now that helper is in use

* move helper to util, remove template helper invokation

* add optional sorting to lazyPaginatedQuery based on sortBy query attribute

* Add serialization to entity-alias and entity so that they can be sorted by name on list view

* Same logic as base normalizeItems for extractLazyPaginatedData so that metadata shows on list

* Add headers

---------

Co-authored-by: Chelsea Shaw <[email protected]>
Co-authored-by: Chelsea Shaw <[email protected]>
  • Loading branch information
hashishaw and hashishaw committed Nov 15, 2023
1 parent 3577316 commit 2bcdc90
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 4 deletions.
3 changes: 3 additions & 0 deletions changelog/24103.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
ui: Sort list view of entities and aliases alphabetically using the item name
```
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default Route.extend(ListRoute, {
responsePath: 'data.keys',
page: params.page,
pageFilter: params.pageFilter,
sortBy: 'name',
})
.catch((err) => {
if (err.httpStatus === 404) {
Expand Down
1 change: 1 addition & 0 deletions ui/app/routes/vault/cluster/access/identity/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default Route.extend(ListRoute, {
responsePath: 'data.keys',
page: params.page,
pageFilter: params.pageFilter,
sortBy: 'name',
})
.catch((err) => {
if (err.httpStatus === 404) {
Expand Down
4 changes: 4 additions & 0 deletions ui/app/serializers/identity/_base.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import ApplicationSerializer from '../application';
export default ApplicationSerializer.extend({
normalizeItems(payload) {
if (payload.data.keys && Array.isArray(payload.data.keys)) {
if (typeof payload.data.keys[0] !== 'string') {
// If keys is not an array of strings, it was already normalized into objects in extractLazyPaginatedData
return payload.data.keys;
}
return payload.data.keys.map((key) => {
const model = payload.data.key_info[key];
model.id = key;
Expand Down
13 changes: 12 additions & 1 deletion ui/app/serializers/identity/entity-alias.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,15 @@
*/

import IdentitySerializer from './_base';
export default IdentitySerializer.extend();
export default IdentitySerializer.extend({
extractLazyPaginatedData(payload) {
return payload.data.keys.map((key) => {
const model = payload.data.key_info[key];
model.id = key;
if (payload.backend) {
model.backend = payload.backend;
}
return model;
});
},
});
10 changes: 10 additions & 0 deletions ui/app/serializers/identity/entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,14 @@ export default IdentitySerializer.extend(EmbeddedRecordsMixin, {
attrs: {
aliases: { embedded: 'always' },
},
extractLazyPaginatedData(payload) {
return payload.data.keys.map((key) => {
const model = payload.data.key_info[key];
model.id = key;
if (payload.backend) {
model.backend = payload.backend;
}
return model;
});
},
});
8 changes: 5 additions & 3 deletions ui/app/services/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { assert } from '@ember/debug';
import { set, get, computed } from '@ember/object';
import clamp from 'vault/utils/clamp';
import config from 'vault/config/environment';
import sortObjects from 'vault/utils/sort-objects';

const { DEFAULT_PAGE_SIZE } = config.APP;

Expand Down Expand Up @@ -185,11 +186,12 @@ export default Store.extend({
// store data cache as { response, dataset}
// also populated `lazyCaches` attribute
storeDataset(modelName, query, response, array) {
const dataSet = {
const dataset = query.sortBy ? sortObjects(array, query.sortBy) : array;
const value = {
response,
dataset: array,
dataset,
};
this.setLazyCacheForModel(modelName, query, dataSet);
this.setLazyCacheForModel(modelName, query, value);
},

clearDataset(modelName) {
Expand Down
14 changes: 14 additions & 0 deletions ui/app/utils/sort-objects.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export default function sortObjects(array, key) {
if (Array.isArray(array) && array?.every((e) => e[key] && typeof e[key] === 'string')) {
return array.sort((a, b) => {
// ignore upper vs lowercase
const valueA = a[key].toUpperCase();
const valueB = b[key].toUpperCase();
if (valueA < valueB) return -1;
if (valueA > valueB) return 1;
return 0;
});
}
// if not sortable, return original array
return array;
}
86 changes: 86 additions & 0 deletions ui/tests/unit/utils/sort-objects-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import sortObjects from 'vault/utils/sort-objects';
import { module, test } from 'qunit';

module('Unit | Utility | sort-objects', function () {
test('it sorts array of objects', function (assert) {
const originalArray = [
{ foo: 'grape', bar: 'third' },
{ foo: 'banana', bar: 'second' },
{ foo: 'lemon', bar: 'fourth' },
{ foo: 'apple', bar: 'first' },
];
const expectedArray = [
{ bar: 'first', foo: 'apple' },
{ bar: 'second', foo: 'banana' },
{ bar: 'third', foo: 'grape' },
{ bar: 'fourth', foo: 'lemon' },
];

assert.propEqual(sortObjects(originalArray, 'foo'), expectedArray, 'it sorts array of objects');

const originalWithNumbers = [
{ foo: 'Z', bar: 'fourth' },
{ foo: '1', bar: 'first' },
{ foo: '2', bar: 'second' },
{ foo: 'A', bar: 'third' },
];
const expectedWithNumbers = [
{ bar: 'first', foo: '1' },
{ bar: 'second', foo: '2' },
{ bar: 'third', foo: 'A' },
{ bar: 'fourth', foo: 'Z' },
];
assert.propEqual(
sortObjects(originalWithNumbers, 'foo'),
expectedWithNumbers,
'it sorts strings with numbers and letters'
);
});

test('it disregards capitalization', function (assert) {
// sort() arranges capitalized values before lowercase, the helper removes case by making all strings toUppercase()
const originalArray = [
{ foo: 'something-a', bar: 'third' },
{ foo: 'D-something', bar: 'second' },
{ foo: 'SOMETHING-b', bar: 'fourth' },
{ foo: 'a-something', bar: 'first' },
];
const expectedArray = [
{ bar: 'first', foo: 'a-something' },
{ bar: 'second', foo: 'D-something' },
{ bar: 'third', foo: 'something-a' },
{ bar: 'fourth', foo: 'SOMETHING-b' },
];

assert.propEqual(
sortObjects(originalArray, 'foo'),
expectedArray,
'it sorts array of objects regardless of capitalization'
);
});

test('it fails gracefully', function (assert) {
const originalArray = [
{ foo: 'b', bar: 'two' },
{ foo: 'a', bar: 'one' },
];
assert.propEqual(
sortObjects(originalArray, 'someKey'),
originalArray,
'it returns original array if key does not exist'
);
assert.deepEqual(sortObjects('not an array'), 'not an array', 'it returns original arg if not an array');

const notStrings = [
{ foo: '1', bar: 'third' },
{ foo: 'Z', bar: 'second' },
{ foo: 1, bar: 'fourth' },
{ foo: 2, bar: 'first' },
];
assert.propEqual(
sortObjects(notStrings, 'foo'),
notStrings,
'it returns original array if values are not all strings'
);
});
});

0 comments on commit 2bcdc90

Please sign in to comment.