Icu 13892 create tags tab route and view (#2362)

* feat: 🎸 add workers/worker/tags route

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-13892

* feat: 🎸 create workers/worker/header component

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-13892

* feat: 🎸 create workers/worker/nav component

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-13892

* feat: 🎸 create workers/worker/actions component

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-13892

* feat: 🎸 add breadcrumbs, header, nav, and actions for tags

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-13892

* feat: 🎸 create table to display worker tags

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-13892

* chore: 🤖 add license headers

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-13892

* refactor: 💡 small changes after flyout changes added

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-13892

* refactor: 💡 update manage dropdown to use hds::dropdown

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-13892

* test: 💍 fix failing test after HDS refactor

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-13892
This commit is contained in:
Cameron Perera authored and cameronperera committed 2024-07-16 15:48:23 -05:00
1 parent 40ebbf8b09
commit fd5143286f
18 files changed
+321 -42

No files matched your search

@@ -983,11 +983,15 @@ worker:
ip_address: IP Address
release_version: Release Version
tag_count: '{tagCount, plural, =1 {# tag} other {# tags}}'
tag: Tag
key: Key
value: Value
type: Type
tags:
title_plural: Tags
description: Tags are used to define the type of worker and to make it distinguishable amongst other workers.
flyout:
title: Tags in {workerName}
table:
tag: Tag
type: Type
action:
view_more: View more tags
form:
@@ -0,0 +1,16 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
}}
<Hds::Dropdown data-test-manage-worker-dropdown as |dd|>
<dd.ToggleButton @text={{t 'actions.manage'}} @color='secondary' />
{{#if (can 'delete worker' @model)}}
<dd.Interactive
@icon='trash'
@text={{t 'resources.worker.actions.delete'}}
@color='critical'
{{on 'click' (fn @delete @model)}}
/>
{{/if}}
</Hds::Dropdown>
@@ -0,0 +1,11 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
}}
<h2>
{{t 'resources.worker.title'}}
<DocLink @doc='worker' @iconSize='large' />
</h2>
<p>{{t 'resources.worker.description'}}</p>
<Hds::Copy::Snippet @textToCopy={{@model.id}} @color='secondary' />
@@ -0,0 +1,13 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
}}
<Rose::Nav::Tabs as |nav|>
<nav.link @route='scopes.scope.workers.worker.index'>
{{t 'titles.details'}}
</nav.link>
<nav.link @route='scopes.scope.workers.worker.tags'>
{{t 'resources.worker.tags.title_plural'}}
</nav.link>
</Rose::Nav::Tabs>
@@ -0,0 +1,10 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Controller, { inject as controller } from '@ember/controller';
export default class ScopesScopeWorkersWorkerTagsController extends Controller {
@controller('scopes/scope/workers/index') workers;
}
+3 -1
View File
@@ -153,7 +153,9 @@ Router.map(function () {
});
this.route('workers', function () {
this.route('new');
this.route('worker', { path: ':worker_id' }, function () {});
this.route('worker', { path: ':worker_id' }, function () {
this.route('tags');
});
});
this.route('session-recordings', function () {
this.route(
@@ -0,0 +1,8 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Route from '@ember/routing/route';
export default class ScopesScopeWorkersWorkerTagsRoute extends Route {}
@@ -166,8 +166,8 @@
<Hds::Table
@model={{this.selectedWorkerTags}}
@columns={{array
(hash label=(t 'resources.worker.flyout.table.tag'))
(hash label=(t 'resources.worker.flyout.table.type'))
(hash label=(t 'resources.worker.table.tag'))
(hash label=(t 'resources.worker.table.type'))
}}
@valign='middle'
>
@@ -186,12 +186,11 @@
</Hds::Table>
{{#if (gt this.selectedWorker.allTags.length 10)}}
<div class='view-more-tags'>
{{! TODO: Update route to worker.tags once route is added }}
<Hds::Link::Standalone
@icon='arrow-right'
@iconPosition='trailing'
@text={{t 'resources.worker.flyout.action.view_more'}}
@route='scopes.scope.workers.worker'
@route='scopes.scope.workers.worker.tags'
@model={{this.selectedWorker}}
/>
</div>
@@ -10,38 +10,18 @@
</page.breadcrumbs>
<page.header>
<h2>
{{t 'resources.worker.title'}}
<DocLink @doc='worker' @iconSize='large' />
</h2>
<p>{{t 'resources.worker.description'}}</p>
<Hds::Copy::Snippet @textToCopy={{@model.id}} @color='secondary' />
<Workers::Worker::Header @model={{@model}} />
</page.header>
<page.navigation>
<Rose::Nav::Tabs as |nav|>
<nav.link @route='scopes.scope.workers.worker.index'>
{{t 'titles.details'}}
</nav.link>
</Rose::Nav::Tabs>
<Workers::Worker::Nav />
</page.navigation>
<page.actions>
<Rose::Dropdown
@text={{t 'actions.manage'}}
@dropdownRight={{true}}
as |dropdown|
>
{{#if (can 'delete worker' @model)}}
<dropdown.button
@style='danger'
@disabled={{@model.canSave}}
{{on 'click' (fn this.workers.delete @model)}}
>
{{t 'resources.worker.actions.delete'}}
</dropdown.button>
{{/if}}
</Rose::Dropdown>
<Workers::Worker::Actions
@model={{@model}}
@delete={{this.workers.delete}}
/>
</page.actions>
<page.body>
@@ -0,0 +1,62 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
}}
{{page-title (t 'resources.worker.tags.title_plural')}}
<Breadcrumbs::Item
@text={{t 'resources.worker.tags.title_plural'}}
@route='scopes.scope.workers.worker.tags'
/>
<Rose::Layout::Page as |page|>
<page.breadcrumbs>
<Breadcrumbs::Container />
</page.breadcrumbs>
<page.header>
<Workers::Worker::Header @model={{@model}} />
</page.header>
<page.navigation>
<Workers::Worker::Nav />
</page.navigation>
<page.actions>
<Workers::Worker::Actions
@model={{@model}}
@delete={{this.workers.delete}}
/>
</page.actions>
<page.body>
{{#if @model.allTags}}
<Hds::Table
@model={{@model.allTags}}
@columns={{array
(hash label=(t 'resources.worker.table.key'))
(hash label=(t 'resources.worker.table.value'))
(hash label=(t 'resources.worker.table.type'))
}}
@valign='middle'
>
<:body as |B|>
<B.Tr>
<B.Td>
<Hds::Text::Code @tag='pre'>{{B.data.key}}</Hds::Text::Code>
</B.Td>
<B.Td>
<Hds::Text::Code @tag='pre'>{{B.data.value}}</Hds::Text::Code>
</B.Td>
<B.Td>
<Hds::Badge @text={{B.data.type}} />
</B.Td>
</B.Tr>
</:body>
</Hds::Table>
{{/if}}
</page.body>
</Rose::Layout::Page>
@@ -21,6 +21,10 @@ module('Acceptance | workers | delete', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
const MANAGE_DROPDOWN_TOGGLE =
'[data-test-manage-worker-dropdown] div button';
const REMOVE_WORKER_BUTTON =
'[data-test-manage-worker-dropdown] div:nth-child(2) button';
let getWorkerCount;
const instances = {
@@ -54,7 +58,8 @@ module('Acceptance | workers | delete', function (hooks) {
test('can delete a worker', async function (assert) {
const count = getWorkerCount();
await visit(urls.worker);
await click('.rose-layout-page-actions .rose-dropdown-button-danger');
await click(MANAGE_DROPDOWN_TOGGLE);
await click(REMOVE_WORKER_BUTTON);
assert.strictEqual(getWorkerCount(), count - 1);
});
@@ -64,7 +69,8 @@ module('Acceptance | workers | delete', function (hooks) {
confirmService.confirm = sinon.fake.returns(resolve());
const count = getWorkerCount();
await visit(urls.worker);
await click('.rose-layout-page-actions .rose-dropdown-button-danger');
await click(MANAGE_DROPDOWN_TOGGLE);
await click(REMOVE_WORKER_BUTTON);
assert.strictEqual(getWorkerCount(), count - 1);
assert.ok(confirmService.confirm.calledOnce);
});
@@ -75,7 +81,8 @@ module('Acceptance | workers | delete', function (hooks) {
confirmService.confirm = sinon.fake.returns(reject());
const count = getWorkerCount();
await visit(urls.worker);
await click('.rose-layout-page-actions .rose-dropdown-button-danger');
await click(MANAGE_DROPDOWN_TOGGLE);
await click(REMOVE_WORKER_BUTTON);
assert.strictEqual(getWorkerCount(), count);
assert.ok(confirmService.confirm.calledOnce);
});
@@ -84,9 +91,8 @@ module('Acceptance | workers | delete', function (hooks) {
instances.worker.authorized_actions =
instances.worker.authorized_actions.filter((item) => item !== 'delete');
await visit(urls.worker);
assert
.dom('.rose-layout-page-actions .rose-dropdown-button-danger')
.isNotVisible();
await click(MANAGE_DROPDOWN_TOGGLE);
assert.dom(REMOVE_WORKER_BUTTON).isNotVisible();
});
test('deleting a worker which errors displays error messages', async function (assert) {
@@ -102,7 +108,8 @@ module('Acceptance | workers | delete', function (hooks) {
);
});
await visit(urls.worker);
await click('.rose-layout-page-actions .rose-dropdown-button-danger');
await click(MANAGE_DROPDOWN_TOGGLE);
await click(REMOVE_WORKER_BUTTON);
assert.ok(find('[role="alert"]').textContent.trim(), 'Oops.');
});
});
@@ -42,6 +42,7 @@ module('Acceptance | workers | list', function (hooks) {
globalScope: null,
workers: null,
worker: null,
workerTags: null,
};
hooks.beforeEach(function () {
@@ -51,6 +52,7 @@ module('Acceptance | workers | list', function (hooks) {
urls.globalScope = `/scopes/global/scopes`;
urls.workers = `/scopes/global/workers`;
urls.worker = `${urls.workers}/${instances.worker.id}`;
urls.workerTags = `${urls.worker}/tags`;
authenticateSession({});
featuresService = this.owner.lookup('service:features');
});
@@ -189,7 +191,7 @@ module('Acceptance | workers | list', function (hooks) {
await click(WORKERS_FLYOUT_VIEW_MORE_TAGS);
assert.strictEqual(currentURL(), urls.worker);
assert.strictEqual(currentURL(), urls.workerTags);
await click(`[href="${urls.workers}"]`);
@@ -0,0 +1,47 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { visit, currentURL, click, findAll } from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
import a11yAudit from 'ember-a11y-testing/test-support/audit';
import { authenticateSession } from 'ember-simple-auth/test-support';
module('Acceptance | workers | tags', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
const instances = {
scopes: {
global: null,
},
};
const urls = {
workers: null,
worker: null,
tags: 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.workers = `/scopes/global/workers`;
urls.worker = `${urls.workers}/${instances.worker.id}`;
urls.tags = `${urls.worker}/tags`;
authenticateSession({});
});
test('visiting worker tags', async function (assert) {
await visit(urls.worker);
await click(`[href="${urls.tags}"]`);
await a11yAudit();
assert.strictEqual(currentURL(), urls.tags);
assert.strictEqual(findAll('tbody tr').length, 11);
});
});
@@ -0,0 +1,30 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupRenderingTest } from 'admin/tests/helpers';
import { setupIntl } from 'ember-intl/test-support';
import { click, render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module(
'Integration | Component | workers/worker/actions/index',
function (hooks) {
setupRenderingTest(hooks);
setupIntl(hooks);
test('it renders', async function (assert) {
this.set('model', { id: 'w_123', authorized_actions: ['delete'] });
this.set('delete', () => {});
await render(
hbs`<Workers::Worker::Actions @model={{this.model}} @delete={{this.delete}} />`,
);
await click('button');
assert.dom(this.element).includesText('Remove Worker');
});
},
);
@@ -0,0 +1,28 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupRenderingTest } from 'admin/tests/helpers';
import { setupIntl } from 'ember-intl/test-support';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module(
'Integration | Component | workers/worker/header/index',
function (hooks) {
setupRenderingTest(hooks);
setupIntl(hooks);
test('it renders with the id of the model passed in', async function (assert) {
this.set('model', { id: 'w_123' });
await render(hbs`
<Workers::Worker::Header @model={{this.model}} />
`);
assert.dom(this.element).includesText('w_123');
});
},
);
@@ -0,0 +1,23 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupRenderingTest } from 'admin/tests/helpers';
import { setupIntl } from 'ember-intl/test-support';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | workers/worker/nav/index', function (hooks) {
setupRenderingTest(hooks);
setupIntl(hooks);
test('it renders', async function (assert) {
await render(hbs`
<Workers::Worker::Nav />
`);
assert.dom(this.element).hasText('Details Tags');
});
});
@@ -0,0 +1,21 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupTest } from 'admin/tests/helpers';
module(
'Unit | Controller | scopes/scope/workers/worker/tags',
function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
let controller = this.owner.lookup(
'controller:scopes/scope/workers/worker/tags',
);
assert.ok(controller);
});
},
);
@@ -0,0 +1,16 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupTest } from 'admin/tests/helpers';
module('Unit | Route | scopes/scope/workers/worker/tags', function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
let route = this.owner.lookup('route:scopes/scope/workers/worker/tags');
assert.ok(route);
});
});