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"