From 7949410208033490db406cf7fc2713cbd46a3b69 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Wed, 3 Jun 2020 17:07:49 +0100 Subject: [PATCH] ui: Add ability to sort service based on health (#7989) * ui: Add ability to sort service based on health * ui: Move custom sorting to sort/comparator Service/Helper (like search) This moves custom sorting to use the same pattern as custom searching. * Remove old Controller based comparator --- ui-v2/app/helpers/comparator.js | 9 ++ ui-v2/app/initializers/sort.js | 18 +++ ui-v2/app/services/sort.js | 4 + ui-v2/app/sort/comparators/service.js | 37 +++++ ui-v2/app/templates/dc/services/index.hbs | 6 +- .../acceptance/dc/services/sorting.feature | 130 ++++++++++++------ ui-v2/tests/unit/services/sort-test.js | 12 ++ .../unit/sort/comparators/service-test.js | 56 ++++++++ 8 files changed, 226 insertions(+), 46 deletions(-) create mode 100644 ui-v2/app/helpers/comparator.js create mode 100644 ui-v2/app/initializers/sort.js create mode 100644 ui-v2/app/services/sort.js create mode 100644 ui-v2/app/sort/comparators/service.js create mode 100644 ui-v2/tests/unit/services/sort-test.js create mode 100644 ui-v2/tests/unit/sort/comparators/service-test.js diff --git a/ui-v2/app/helpers/comparator.js b/ui-v2/app/helpers/comparator.js new file mode 100644 index 0000000000..3d502276e6 --- /dev/null +++ b/ui-v2/app/helpers/comparator.js @@ -0,0 +1,9 @@ +import Helper from '@ember/component/helper'; +import { inject as service } from '@ember/service'; + +export default Helper.extend({ + sort: service('sort'), + compute([type, key], hash) { + return this.sort.comparator(type)(key); + }, +}); diff --git a/ui-v2/app/initializers/sort.js b/ui-v2/app/initializers/sort.js new file mode 100644 index 0000000000..3065d0fc9b --- /dev/null +++ b/ui-v2/app/initializers/sort.js @@ -0,0 +1,18 @@ +import service from 'consul-ui/sort/comparators/service'; + +export function initialize(container) { + // Service-less injection using private properties at a per-project level + const Sort = container.resolveRegistration('service:sort'); + const comparators = { + service: service(), + }; + Sort.reopen({ + comparator: function(type) { + return comparators[type]; + }, + }); +} + +export default { + initialize, +}; diff --git a/ui-v2/app/services/sort.js b/ui-v2/app/services/sort.js new file mode 100644 index 0000000000..12fa3a5975 --- /dev/null +++ b/ui-v2/app/services/sort.js @@ -0,0 +1,4 @@ +import Service from '@ember/service'; +export default Service.extend({ + comparator: function(type) {}, +}); diff --git a/ui-v2/app/sort/comparators/service.js b/ui-v2/app/sort/comparators/service.js new file mode 100644 index 0000000000..a5c906d10a --- /dev/null +++ b/ui-v2/app/sort/comparators/service.js @@ -0,0 +1,37 @@ +export default () => key => { + if (key.startsWith('Status:')) { + return function(serviceA, serviceB) { + const [, dir] = key.split(':'); + let a, b; + if (dir === 'asc') { + b = serviceA; + a = serviceB; + } else { + a = serviceA; + b = serviceB; + } + switch (true) { + case a.ChecksCritical > b.ChecksCritical: + return 1; + case a.ChecksCritical < b.ChecksCritical: + return -1; + default: + switch (true) { + case a.ChecksWarning > b.ChecksWarning: + return 1; + case a.ChecksWarning < b.ChecksWarning: + return -1; + default: + switch (true) { + case a.ChecksPassing < b.ChecksPassing: + return 1; + case a.ChecksPassing > b.ChecksPassing: + return -1; + } + } + return 0; + } + }; + } + return key; +}; diff --git a/ui-v2/app/templates/dc/services/index.hbs b/ui-v2/app/templates/dc/services/index.hbs index dae6659bfc..e758cf6417 100644 --- a/ui-v2/app/templates/dc/services/index.hbs +++ b/ui-v2/app/templates/dc/services/index.hbs @@ -2,6 +2,8 @@ {{#let (selectable-key-values (array "Name:asc" "A to Z") (array "Name:desc" "Z to A") + (array "Status:asc" "Unhealthy to Healthy") + (array "Status:desc" "Healthy to Unhealthy") selected=sortBy ) as |sort| @@ -29,7 +31,8 @@ {{/if}} - +{{#let (sort-by (comparator 'service' sort.selected.key) services) as |sorted|}} + @@ -64,6 +67,7 @@ + {{/let}} {{/let}} diff --git a/ui-v2/tests/acceptance/dc/services/sorting.feature b/ui-v2/tests/acceptance/dc/services/sorting.feature index 1e9a500377..1ad568fe5c 100644 --- a/ui-v2/tests/acceptance/dc/services/sorting.feature +++ b/ui-v2/tests/acceptance/dc/services/sorting.feature @@ -1,45 +1,85 @@ -@setupApplicationTest -Feature: dc / services / sorting - Scenario: - Given 1 datacenter model with the value "dc-1" - And 6 service models from yaml - --- - - Name: Service-A - Kind: ~ - - Name: Service-B - Kind: ~ - - Name: Service-C - Kind: ~ - - Name: Service-D - Kind: ~ - - Name: Service-E - Kind: ~ - - Name: Service-F - Kind: ~ - --- - When I visit the services page for yaml - --- - dc: dc-1 - --- - When I click selected on the sort - When I click options.1.button on the sort - Then I see name on the services vertically like yaml - --- - - Service-F - - Service-E - - Service-D - - Service-C - - Service-B - - Service-A - --- - When I click selected on the sort - When I click options.0.button on the sort - Then I see name on the services vertically like yaml - --- - - Service-A - - Service-B - - Service-C - - Service-D - - Service-E - - Service-F - --- +@setupApplicationTest +Feature: dc / services / sorting + Scenario: + Given 1 datacenter model with the value "dc-1" + And 6 service models from yaml + --- + - Name: Service-A + Kind: ~ + ChecksPassing: 1 + ChecksWarning: 1 + ChecksCritical: 3 + - Name: Service-B + Kind: ~ + ChecksPassing: 1 + ChecksWarning: 1 + ChecksCritical: 5 + - Name: Service-C + Kind: ~ + ChecksPassing: 1 + ChecksWarning: 1 + ChecksCritical: 4 + - Name: Service-D + Kind: ~ + ChecksPassing: 1 + ChecksWarning: 5 + ChecksCritical: 1 + - Name: Service-E + Kind: ~ + ChecksPassing: 1 + ChecksWarning: 3 + ChecksCritical: 1 + - Name: Service-F + Kind: ~ + ChecksPassing: 1 + ChecksWarning: 4 + ChecksCritical: 1 + --- + When I visit the services page for yaml + --- + dc: dc-1 + --- + When I click selected on the sort + When I click options.1.button on the sort + Then I see name on the services vertically like yaml + --- + - Service-F + - Service-E + - Service-D + - Service-C + - Service-B + - Service-A + --- + When I click selected on the sort + When I click options.0.button on the sort + Then I see name on the services vertically like yaml + --- + - Service-A + - Service-B + - Service-C + - Service-D + - Service-E + - Service-F + --- + When I click selected on the sort + When I click options.2.button on the sort + Then I see name on the services vertically like yaml + --- + - Service-B + - Service-C + - Service-A + - Service-D + - Service-F + - Service-E + --- + When I click selected on the sort + When I click options.3.button on the sort + Then I see name on the services vertically like yaml + --- + - Service-E + - Service-F + - Service-D + - Service-A + - Service-C + - Service-B + --- diff --git a/ui-v2/tests/unit/services/sort-test.js b/ui-v2/tests/unit/services/sort-test.js new file mode 100644 index 0000000000..3582129dba --- /dev/null +++ b/ui-v2/tests/unit/services/sort-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Service | sort', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let service = this.owner.lookup('service:sort'); + assert.ok(service); + }); +}); diff --git a/ui-v2/tests/unit/sort/comparators/service-test.js b/ui-v2/tests/unit/sort/comparators/service-test.js new file mode 100644 index 0000000000..073ff39c91 --- /dev/null +++ b/ui-v2/tests/unit/sort/comparators/service-test.js @@ -0,0 +1,56 @@ +import comparatorFactory from 'consul-ui/sort/comparators/service'; +import { module, test } from 'qunit'; + +module('Unit | Sort | Comparator | service', function() { + const comparator = comparatorFactory(); + test('Passing anything but Status: just returns what you gave it', function(assert) { + const expected = 'Name:asc'; + const actual = comparator(expected); + assert.equal(actual, expected); + }); + test('items are sorted by a fake Status which uses Checks{Passing,Warning,Critical}', function(assert) { + const items = [ + { + ChecksPassing: 1, + ChecksWarning: 1, + ChecksCritical: 1, + }, + { + ChecksPassing: 1, + ChecksWarning: 1, + ChecksCritical: 2, + }, + { + ChecksPassing: 1, + ChecksWarning: 1, + ChecksCritical: 3, + }, + ]; + const comp = comparator('Status:asc'); + assert.equal(typeof comp, 'function'); + + const expected = [ + { + ChecksPassing: 1, + ChecksWarning: 1, + ChecksCritical: 3, + }, + { + ChecksPassing: 1, + ChecksWarning: 1, + ChecksCritical: 2, + }, + { + ChecksPassing: 1, + ChecksWarning: 1, + ChecksCritical: 1, + }, + ]; + let actual = items.sort(comp); + assert.deepEqual(actual, expected); + + expected.reverse(); + actual = items.sort(comparator('Status:desc')); + assert.deepEqual(actual, expected); + }); +});