diff --git a/addons/api/mirage/factories/worker.js b/addons/api/mirage/factories/worker.js index 730948e33..3fcdce27b 100644 --- a/addons/api/mirage/factories/worker.js +++ b/addons/api/mirage/factories/worker.js @@ -20,30 +20,54 @@ export default factory.extend({ 'delete', ], type: (i) => types[i % types.length], - canonical_tags: (i) => { - if (i % 3 === 0) { - return { os: ['ubuntu'] }; - } - if (i % 2 === 0) { - return { - type: [faker.word.words(1), faker.word.words(1)], - os: ['ubuntu'], - env: ['dev', 'local'], - }; - } - return undefined; - }, config_tags: (i) => { if (i % 3 === 0) { - return { os: ['ubuntu'] }; - } - if (i % 2 === 0) { return { - type: [faker.word.words(1), faker.word.words(1)], os: ['ubuntu'], env: ['dev', 'local'], }; } + if (i % 2 === 0) { + return { os: ['ubuntu'] }; + } return undefined; }, + api_tags: (i) => { + if (i % 3 === 0) { + return { + type: [faker.word.words(1), faker.word.words(1)], + os: ['z-os'], + env: ['prod', 'qa'], + sample: [faker.word.words(1), faker.word.words(1)], + path: [faker.word.words(1)], + }; + } + if (i % 2 === 0) { + return { os: ['z-os'] }; + } + return undefined; + }, + canonical_tags() { + const configTags = this.config_tags || {}; + const apiTags = this.api_tags || {}; + const canonicalTags = {}; + + Object.keys(configTags).forEach((key) => { + canonicalTags[key] = new Set(configTags[key]); + }); + + Object.keys(apiTags).forEach((key) => { + if (canonicalTags[key]) { + apiTags[key].forEach((tag) => canonicalTags[key].add(tag)); + } else { + canonicalTags[key] = new Set(apiTags[key]); + } + }); + + Object.keys(canonicalTags).forEach((key) => { + canonicalTags[key] = Array.from(canonicalTags[key]); + }); + + return canonicalTags; + }, }); diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index c23c7df39..f972197c0 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -983,6 +983,13 @@ worker: ip_address: IP Address release_version: Release Version tag_count: '{tagCount, plural, =1 {# tag} other {# tags}}' + flyout: + title: Tags in {workerName} + table: + tag: Tag + type: Type + action: + view_more: View more tags form: cluster_id: label: Boundary Cluster ID diff --git a/ui/admin/app/controllers/scopes/scope/workers/index.js b/ui/admin/app/controllers/scopes/scope/workers/index.js index 2570308f7..0619700d8 100644 --- a/ui/admin/app/controllers/scopes/scope/workers/index.js +++ b/ui/admin/app/controllers/scopes/scope/workers/index.js @@ -7,6 +7,7 @@ import Controller from '@ember/controller'; import { inject as service } from '@ember/service'; import { action } from '@ember/object'; import { loading } from 'ember-loading'; +import { tracked } from '@glimmer/tracking'; import { confirm } from 'core/decorators/confirm'; import { notifySuccess, notifyError } from 'core/decorators/notify'; @@ -16,8 +17,39 @@ export default class ScopesScopeWorkersIndexController extends Controller { @service router; @service can; + // =attributes + + @tracked selectedWorker; + + /** + * Get the first 10 tags of the selected worker. + * @type {object[]} + */ + get selectedWorkerTags() { + // only show the first 10 tags if there are more than 10 + return this.selectedWorker.allTags.slice(0, 10); + } + + /** + * Build the display name for a worker tag + * @param {object} tag + * @returns {string} + */ + tagDisplayName(tag) { + return `${tag.key} = ${tag.value}`; + } + // =actions + /** + * Toggle the tags flyout to display or hide the tags of a worker. + * @param {object} selectedWorker + */ + @action + selectWorker(selectedWorker) { + this.selectedWorker = selectedWorker; + } + /** * Rollback changes on workers. * @param {WorkerModel} worker diff --git a/ui/admin/app/routes/scopes/scope/workers/index.js b/ui/admin/app/routes/scopes/scope/workers/index.js index 401f573de..9b0f77965 100644 --- a/ui/admin/app/routes/scopes/scope/workers/index.js +++ b/ui/admin/app/routes/scopes/scope/workers/index.js @@ -68,6 +68,16 @@ export default class ScopesScopeWorkersIndexRoute extends Route { return workers; } + resetController(controller, isExiting) { + // Clear selected worker when exiting route to prevent + // the flyout from showing when returning to this route + if (isExiting) { + controller.setProperties({ + selectedWorker: null, + }); + } + } + /** * Clears filter selections. */ diff --git a/ui/admin/app/styles/app.scss b/ui/admin/app/styles/app.scss index 7aedc1d8f..d5fdb924d 100644 --- a/ui/admin/app/styles/app.scss +++ b/ui/admin/app/styles/app.scss @@ -1069,3 +1069,15 @@ margin-bottom: 2rem; } } + +// workers +.view-more-tags { + margin-top: 1.5rem; +} + +.tag-shortener { + display: inline-block; + max-width: 310px; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/ui/admin/app/templates/scopes/scope/workers/index.hbs b/ui/admin/app/templates/scopes/scope/workers/index.hbs index 0e12ec593..c33f3ec3c 100644 --- a/ui/admin/app/templates/scopes/scope/workers/index.hbs +++ b/ui/admin/app/templates/scopes/scope/workers/index.hbs @@ -124,12 +124,16 @@ {{#if B.data.tagCount}} - {{/if}} @@ -145,6 +149,56 @@ + + {{#if this.selectedWorker}} + + + {{t + 'resources.worker.flyout.title' + workerName=this.selectedWorker.displayName + }} + + + + <:body as |B|> + + + + {{this.tagDisplayName B.data}} + + + + + + + + + {{#if (gt this.selectedWorker.allTags.length 10)}} + + {{! TODO: Update route to worker.tags once route is added }} + + + {{/if}} + + + {{/if}} {{else}} + `[data-test-worker-tags-flyout-button="${workerId}"]`; + module('Acceptance | workers | list', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); @@ -26,17 +35,22 @@ module('Acceptance | workers | list', function (hooks) { scopes: { global: null, }, + worker: null, }; const urls = { globalScope: null, workers: null, + worker: null, }; hooks.beforeEach(function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); + const scope = instances.scopes.global; + instances.worker = this.server.create('worker', { scopeId: scope.id }); urls.globalScope = `/scopes/global/scopes`; urls.workers = `/scopes/global/workers`; + urls.worker = `${urls.workers}/${instances.worker.id}`; authenticateSession({}); featuresService = this.owner.lookup('service:features'); }); @@ -77,4 +91,109 @@ module('Acceptance | workers | list', function (hooks) { await visit(urls.globalScope); assert.dom(`[href="${urls.workers}"]`).isVisible(); }); + + test('Users can open and close tags flyout for a specific worker', async function (assert) { + featuresService.enable('byow'); + await visit(urls.workers); + assert.dom(WORKERS_FLYOUT).isNotVisible(); + + await click(WORKER_TAGS_BUTTON(instances.worker.id)); + assert.dom(WORKERS_FLYOUT).isVisible(); + + await click(WORKERS_FLYOUT_DISMISS); + assert.dom(WORKERS_FLYOUT).isNotVisible(); + }); + + test('Users can see worker tags in the tags flyout', async function (assert) { + featuresService.enable('byow'); + await visit(urls.workers); + await click(WORKER_TAGS_BUTTON(instances.worker.id)); + + assert.dom(WORKERS_FLYOUT_TABLE_BODY).includesText('os = z-os'); + }); + + test('Users can only see first 10 tags in the tags flyout', async function (assert) { + featuresService.enable('byow'); + instances.worker.update({ + configTags: { + tag1: ['value1', 'value2'], + tag2: ['value3'], + tag3: ['value4'], + tag4: ['value5'], + tag5: ['value6'], + tag6: ['value7'], + tag7: ['value8'], + tag8: ['value9'], + tag9: ['value10'], + tag10: ['value11'], + tag11: ['value12'], + }, + }); + + await visit(urls.workers); + await click(WORKER_TAGS_BUTTON(instances.worker.id)); + + assert.dom(WORKERS_FLYOUT_TABLE_ROWS).exists({ count: 10 }); + }); + + test('Users can click on "view more tags" if there are more than 10 tags', async function (assert) { + featuresService.enable('byow'); + instances.worker.update({ + configTags: { + tag1: ['value1', 'value2'], + tag2: ['value3'], + tag3: ['value4'], + tag4: ['value5'], + tag5: ['value6'], + tag6: ['value7'], + tag7: ['value8'], + tag8: ['value9'], + tag9: ['value10'], + tag10: ['value11'], + tag11: ['value12'], + }, + }); + + await visit(urls.workers); + await click(WORKER_TAGS_BUTTON(instances.worker.id)); + + assert.dom(WORKERS_FLYOUT_TABLE_ROWS).exists({ count: 10 }); + assert.dom(WORKERS_FLYOUT_VIEW_MORE_TAGS).isVisible(); + }); + + test('Users do not see "view more tags" if there are 10 or less tags', async function (assert) { + featuresService.enable('byow'); + instances.worker.update({ + configTags: { + tag1: ['value1', 'value2'], + tag2: ['value3'], + tag3: ['value4'], + tag4: ['value5'], + }, + apiTags: null, + }); + + await visit(urls.workers); + await click(WORKER_TAGS_BUTTON(instances.worker.id)); + + assert.dom(WORKERS_FLYOUT_TABLE_ROWS).exists({ count: 5 }); + assert.dom(WORKERS_FLYOUT_VIEW_MORE_TAGS).isNotVisible(); + }); + + test('Users do not see worker tags flyout when returning to workers list', async function (assert) { + featuresService.enable('byow'); + await visit(urls.workers); + await click(WORKER_TAGS_BUTTON(instances.worker.id)); + + assert.dom(WORKERS_FLYOUT).isVisible(); + + await click(WORKERS_FLYOUT_VIEW_MORE_TAGS); + + assert.strictEqual(currentURL(), urls.worker); + + await click(`[href="${urls.workers}"]`); + + assert.strictEqual(currentURL(), urls.workers); + assert.dom(WORKERS_FLYOUT).isNotVisible(); + }); });