diff --git a/addons/rose/package.json b/addons/rose/package.json index dde5784b5..6e1f5fadb 100644 --- a/addons/rose/package.json +++ b/addons/rose/package.json @@ -83,7 +83,7 @@ "prettier": "^2.2.1", "qunit": "^2.13.0", "qunit-dom": "^1.6.0", - "sass": "^1.26.5", + "sass": "^1.36.0", "sass-lint": "^1.13.1" }, "engines": { diff --git a/ui/admin/app/routes/scopes/scope/targets/target/credential-libraries.js b/ui/admin/app/routes/scopes/scope/targets/target/credential-libraries.js index 17e3e2692..66ce6f8ec 100644 --- a/ui/admin/app/routes/scopes/scope/targets/target/credential-libraries.js +++ b/ui/admin/app/routes/scopes/scope/targets/target/credential-libraries.js @@ -1,5 +1,9 @@ import Route from '@ember/routing/route'; -import { hash, all } from 'rsvp'; +import { all } from 'rsvp'; +import { action } from '@ember/object'; +import loading from 'ember-loading/decorator'; +import { confirm } from 'core/decorators/confirm'; +import { notifySuccess, notifyError } from 'core/decorators/notify'; export default class ScopesScopeTargetsTargetCredentialLibrariesRoute extends Route { // =methods @@ -11,17 +15,13 @@ export default class ScopesScopeTargetsTargetCredentialLibrariesRoute extends Ro beforeModel() { const { application_credential_library_ids: credential_libraries } = this.modelFor('scopes.scope.targets.target'); - return hash({ - credentialLibraries: all( - credential_libraries.map((credential_library) => - this.store.findRecord( - 'credential-library', - credential_library.value, - { reload: true } - ) - ) - ), - }); + return all( + credential_libraries.map((credential_library) => + this.store.findRecord('credential-library', credential_library.value, { + reload: true, + }) + ) + ); } /** @@ -31,4 +31,21 @@ export default class ScopesScopeTargetsTargetCredentialLibrariesRoute extends Ro model() { return this.modelFor('scopes.scope.targets.target'); } + + // =actions + + /** + * Remove a credential library from the current target. + * @param {TargetModel} target + * @param {CredentialLibraryModel} credentialLibrary + */ + @action + @loading + @confirm('questions.remove-confirm') + @notifyError(({ message }) => message, { catch: true }) + @notifySuccess('notifications.remove-success') + async removeCredentialLibrary(target, credentialLibrary) { + await target.removeCredentialLibrary(credentialLibrary.id); + this.refresh(); + } } diff --git a/ui/admin/app/templates/scopes/scope/targets/target/credential-libraries.hbs b/ui/admin/app/templates/scopes/scope/targets/target/credential-libraries.hbs index 003992205..f92cd3715 100644 --- a/ui/admin/app/templates/scopes/scope/targets/target/credential-libraries.hbs +++ b/ui/admin/app/templates/scopes/scope/targets/target/credential-libraries.hbs @@ -8,6 +8,7 @@ {{t 'form.name.label'}} {{t 'form.type.label'}} {{t 'form.id.label'}} + {{t 'titles.actions'}} @@ -41,6 +42,28 @@ {{credentialLibrary.id}} + + + + {{t 'actions.remove'}} + + + {{/each}} diff --git a/ui/admin/package.json b/ui/admin/package.json index 6c2390fe6..bc9e5b2b6 100644 --- a/ui/admin/package.json +++ b/ui/admin/package.json @@ -89,7 +89,7 @@ "qunit": "^2.13.0", "qunit-dom": "^1.6.0", "rose": "*", - "sass": "^1.29.0", + "sass": "^1.36.0", "sass-lint": "^1.13.1", "sinon": "^11.1.1" }, diff --git a/ui/admin/tests/acceptance/credential-library/credential-library-test.js b/ui/admin/tests/acceptance/credential-library/credential-library-test.js index ea3568c45..103c6d608 100644 --- a/ui/admin/tests/acceptance/credential-library/credential-library-test.js +++ b/ui/admin/tests/acceptance/credential-library/credential-library-test.js @@ -2,6 +2,7 @@ import { module, test } from 'qunit'; import { visit, click, fillIn, currentURL, find } 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'; import { Response } from 'miragejs'; import { resolve, reject } from 'rsvp'; @@ -30,6 +31,7 @@ module('Acceptance | credential-library', function (hooks) { credentialLibrary: null, credentialLibraries: null, newCredentialLibrary: null, + unknownCredentialLibrary: null, }; hooks.beforeEach(function () { @@ -58,12 +60,31 @@ module('Acceptance | credential-library', function (hooks) { urls.credentialLibraries = `${urls.credentialStore}/credential-libraries`; urls.credentialLibrary = `${urls.credentialLibraries}/${instances.credentialLibrary.id}`; urls.newCredentialLibrary = `${urls.credentialLibraries}/new`; - // Generate resource couner + urls.unknownCredentialLibrary = `${urls.credentialLibraries}/foo`; + // Generate resource counter getCredentialLibraryCount = () => this.server.schema.credentialLibraries.all().models.length; authenticateSession({}); }); + test('visiting credential libraries', async function (assert) { + assert.expect(2); + await visit(urls.credentialLibraries); + await a11yAudit(); + assert.equal(currentURL(), urls.credentialLibraries); + await visit(urls.credentialLibrary); + await a11yAudit(); + assert.equal(currentURL(), urls.credentialLibrary); + }); + + test('visiting an unknown credential library displays 404 message', async function (assert) { + assert.expect(1); + await visit(urls.unknownCredentialLibrary); + await a11yAudit(); + console.debug(find('.rose-message-subtitle')); + assert.ok(find('.rose-message-subtitle').textContent.trim(), 'Error 404'); + }); + test('can create a credential library', async function (assert) { assert.expect(1); const count = getCredentialLibraryCount(); @@ -147,4 +168,22 @@ module('Acceptance | credential-library', function (hooks) { assert.equal(getCredentialLibraryCount(), count); assert.ok(confirmService.confirm.calledOnce); }); + + test('deleting a credential library which errors displays error messages', async function (assert) { + assert.expect(1); + this.server.del('/credential-libraries/:id', () => { + return new Response( + 490, + {}, + { + status: 490, + code: 'error', + message: 'Oops.', + } + ); + }); + await visit(urls.credentialLibrary); + await click('.rose-layout-page-actions .rose-dropdown-button-danger'); + assert.ok(find('[role="alert"]').textContent.trim(), 'Oops.'); + }); }); diff --git a/ui/admin/tests/acceptance/targets/credential-libraries-test.js b/ui/admin/tests/acceptance/targets/credential-libraries-test.js new file mode 100644 index 000000000..b8e2d5233 --- /dev/null +++ b/ui/admin/tests/acceptance/targets/credential-libraries-test.js @@ -0,0 +1,98 @@ +import { module, test } from 'qunit'; +import { visit, find, findAll, click } from '@ember/test-helpers'; +import { setupApplicationTest } from 'ember-qunit'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { Response } from 'miragejs'; +import { + authenticateSession, + // These are left here intentionally for future reference. + //currentSession, + //invalidateSession, +} from 'ember-simple-auth/test-support'; + +module('Acceptance | targets | credential-libraries', function (hooks) { + setupApplicationTest(hooks); + setupMirage(hooks); + + let getCredentialLibraryCount; + + const instances = { + scopes: { + global: null, + org: null, + project: null, + }, + credentialLibraries: null, + }; + const urls = { + globalScope: null, + orgScope: null, + projectScope: null, + targets: null, + target: null, + targetCredentialLibraries: null, + }; + + hooks.beforeEach(function () { + // Generate resources + instances.scopes.global = this.server.create('scope', { id: 'global' }); + instances.scopes.org = this.server.create('scope', { + type: 'org', + scope: { id: 'global', type: 'global' }, + }); + instances.scopes.project = this.server.create('scope', { + type: 'project', + scope: { id: instances.scopes.org.id, type: 'org' }, + }); + instances.credentialLibraries = this.server.createList( + 'credential-library', + 2, + { scope: instances.scopes.project } + ); + instances.target = this.server.create('target', { + scope: instances.scopes.project, + credentialLibraries: instances.credentialLibraries, + }); + // Generate route URLs for resources + urls.globalScope = `/scopes/global/scopes`; + urls.orgScope = `/scopes/${instances.scopes.org.id}/scopes`; + urls.projectScope = `/scopes/${instances.scopes.project.id}`; + urls.targets = `/scopes/${instances.scopes.project.id}/targets`; + urls.target = `${urls.targets}/${instances.target.id}`; + urls.targetCredentialLibraries = `${urls.target}/credential-libraries`; + // Generate resource counter + getCredentialLibraryCount = () => + this.server.schema.credentialLibraries.all().models.length; + authenticateSession({}); + }); + + test('can remove a credential library', async function (assert) { + assert.expect(2); + const count = getCredentialLibraryCount(); + await visit(urls.targetCredentialLibraries); + assert.equal(findAll('tbody tr').length, count); + await click('tbody tr .rose-dropdown-button-danger'); + assert.equal(findAll('tbody tr').length, count - 1); + }); + + test('removing a target credential library which errors displays error messages', async function (assert) { + assert.expect(2); + this.server.post('/targets/:idMethod', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: {}, + } + ); + }); + const count = getCredentialLibraryCount(); + await visit(urls.targetCredentialLibraries); + assert.equal(findAll('tbody tr').length, count); + await click('tbody tr .rose-dropdown-button-danger'); + assert.ok(find('[role="alert"]')); + }); +}); diff --git a/ui/desktop/package.json b/ui/desktop/package.json index 6ed9cd93e..1327bccd6 100755 --- a/ui/desktop/package.json +++ b/ui/desktop/package.json @@ -104,7 +104,7 @@ "qunit": "^2.13.0", "qunit-dom": "^1.6.0", "rose": "*", - "sass": "^1.29.0", + "sass": "^1.36.0", "sinon": "^11.1.1", "stylelint": "^13.8.0", "stylelint-config-sass-guidelines": "^7.1.0" diff --git a/yarn.lock b/yarn.lock index e2f00c012..80808da5a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18934,10 +18934,10 @@ sass-lint@^1.13.1: path-is-absolute "^1.0.0" util "^0.10.3" -sass@^1.26.5, sass@^1.29.0: - version "1.34.1" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.34.1.tgz#30f45c606c483d47b634f1e7371e13ff773c96ef" - integrity sha512-scLA7EIZM+MmYlej6sdVr0HRbZX5caX5ofDT9asWnUJj21oqgsC+1LuNfm0eg+vM0fCTZHhwImTiCU0sx9h9CQ== +sass@^1.36.0: + version "1.36.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.36.0.tgz#5912ef9d5d16714171ba11cb17edb274c4bbc07e" + integrity sha512-fQzEjipfOv5kh930nu3Imzq3ie/sGDc/4KtQMJlt7RRdrkQSfe37Bwi/Rf/gfuYHsIuE1fIlDMvpyMcEwjnPvg== dependencies: chokidar ">=3.0.0 <4.0.0"