diff --git a/ui/admin/app/components/form/role/manage-custom-scopes/index.js b/ui/admin/app/components/form/role/manage-custom-scopes/index.js
index c8db41f32..dc67d0b91 100644
--- a/ui/admin/app/components/form/role/manage-custom-scopes/index.js
+++ b/ui/admin/app/components/form/role/manage-custom-scopes/index.js
@@ -5,21 +5,8 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
-import { tracked } from '@glimmer/tracking';
export default class FormRoleManageCustomScopesIndexComponent extends Component {
- // =attributes
-
- @tracked selectedItems = [...this.args.model.role.grantScopeOrgIDs];
-
- get allGrantScopes() {
- return [
- ...this.args.model.role.grantScopeKeywords,
- ...this.args.model.role.grantScopeProjectIDs,
- ...this.selectedItems,
- ];
- }
-
// =actions
/**
@@ -28,15 +15,16 @@ export default class FormRoleManageCustomScopesIndexComponent extends Component
*/
@action
selectionChange({ selectableRowsStates }) {
+ const { role } = this.args.model;
selectableRowsStates.forEach((row) => {
const { isSelected, selectionKey: key } = row;
- const includesId = this.selectedItems.includes(key);
- if (isSelected) {
- if (!includesId) this.selectedItems = [...this.selectedItems, key];
- } else if (includesId) {
- this.selectedItems = this.selectedItems.filter(
- (item) => item !== key,
- );
+ const includesId = role.grant_scope_ids.includes(key);
+ if (isSelected && !includesId) {
+ role.grant_scope_ids = [...role.grant_scope_ids, key];
+ } else if (!isSelected && includesId) {
+ role.grant_scope_ids = role.grant_scope_ids.filter(
+ (item) => item !== key,
+ );
}
});
}
diff --git a/ui/admin/app/components/form/role/manage-org-projects/index.hbs b/ui/admin/app/components/form/role/manage-org-projects/index.hbs
index 743c71398..8e078fbce 100644
--- a/ui/admin/app/components/form/role/manage-org-projects/index.hbs
+++ b/ui/admin/app/components/form/role/manage-org-projects/index.hbs
@@ -6,7 +6,7 @@
{{#if @model.totalItemsCount}}
{{#if @model.projectScopes}}
- {{#if this.selectedItems.length}}
+ {{#if this.selectedProjectsCount}}
{{t
'resources.role.scope.titles.selected'
- items=this.selectedItems.length
+ items=this.selectedProjectsCount
total=@model.totalItemsCount
}}
@@ -45,7 +45,7 @@
<:body as |B|>
diff --git a/ui/admin/app/components/form/role/manage-org-projects/index.js b/ui/admin/app/components/form/role/manage-org-projects/index.js
index 2ef0c9d93..6fcc16138 100644
--- a/ui/admin/app/components/form/role/manage-org-projects/index.js
+++ b/ui/admin/app/components/form/role/manage-org-projects/index.js
@@ -5,20 +5,13 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
-import { tracked } from '@glimmer/tracking';
export default class FormRoleManageOrgProjectsIndexComponent extends Component {
// =attributes
- @tracked selectedItems = [...this.args.model.selectedProjectIDs];
-
- get allGrantScopes() {
- return [
- ...this.args.model.role.grantScopeKeywords,
- ...this.args.model.role.grantScopeOrgIDs,
- ...this.args.model.remainingProjectIDs,
- ...this.selectedItems,
- ];
+ get selectedProjectsCount() {
+ const { role, remainingProjectIDs } = this.args.model;
+ return role.grantScopeProjectIDs.length - remainingProjectIDs.length;
}
// =actions
@@ -29,16 +22,16 @@ export default class FormRoleManageOrgProjectsIndexComponent extends Component {
*/
@action
selectionChange({ selectableRowsStates }) {
+ const { role } = this.args.model;
selectableRowsStates.forEach((row) => {
const { isSelected, selectionKey: key } = row;
- const includesId = this.selectedItems.includes(key);
- if (isSelected) {
- if (!includesId) this.selectedItems = [...this.selectedItems, key];
- } else {
- if (includesId)
- this.selectedItems = this.selectedItems.filter(
- (item) => item !== key,
- );
+ const includesId = role.grant_scope_ids.includes(key);
+ if (isSelected && !includesId) {
+ role.grant_scope_ids = [...role.grant_scope_ids, key];
+ } else if (!isSelected && includesId) {
+ role.grant_scope_ids = role.grant_scope_ids.filter(
+ (item) => item !== key,
+ );
}
});
}
diff --git a/ui/admin/app/components/form/role/manage-scopes/index.hbs b/ui/admin/app/components/form/role/manage-scopes/index.hbs
index fb745d512..3eca81364 100644
--- a/ui/admin/app/components/form/role/manage-scopes/index.hbs
+++ b/ui/admin/app/components/form/role/manage-scopes/index.hbs
@@ -5,7 +5,7 @@
{
- this.selectedItems = this.selectedItems.filter((item) => item !== value);
+ this.args.model.grant_scope_ids = this.args.model.grant_scope_ids.filter(
+ (item) => item !== value,
+ );
};
if (checked) {
- this.selectedItems = [...this.selectedItems, value];
+ this.args.model.grant_scope_ids = [
+ ...this.args.model.grant_scope_ids,
+ value,
+ ];
if (value === GRANT_SCOPE_CHILDREN) {
removeValue(GRANT_SCOPE_DESCENDANTS);
}
diff --git a/ui/admin/app/controllers/scopes/scope/roles/role/manage-scopes/index.js b/ui/admin/app/controllers/scopes/scope/roles/role/manage-scopes/index.js
index ef7f9f2b4..9b52238ad 100644
--- a/ui/admin/app/controllers/scopes/scope/roles/role/manage-scopes/index.js
+++ b/ui/admin/app/controllers/scopes/scope/roles/role/manage-scopes/index.js
@@ -23,22 +23,23 @@ export default class ScopesScopeRolesRoleManageScopesIndexController extends Con
/**
* Save grant scope IDs to current role via the API.
* @param {RoleModel} role
- * @param {[string]} grantScopeIDs
*/
@action
@loading
@notifyError(({ message }) => message, { catch: true })
@notifySuccess('resources.role.scope.messages.manage-scopes.success')
- async setGrantScopes(role, grantScopeIDs) {
- await role.setGrantScopes(grantScopeIDs);
+ async setGrantScopes(role) {
+ await role.setGrantScopes(role.grant_scope_ids);
this.router.replaceWith('scopes.scope.roles.role.scopes');
}
/**
* Redirect to role scopes as if nothing ever happened.
+ * @param {RoleModel} role
*/
@action
- cancel() {
+ cancel(role) {
+ role.rollbackAttributes();
this.router.replaceWith('scopes.scope.roles.role.scopes');
}
}
diff --git a/ui/admin/app/controllers/scopes/scope/roles/role/manage-scopes/manage-custom-scopes.js b/ui/admin/app/controllers/scopes/scope/roles/role/manage-scopes/manage-custom-scopes.js
index 66940700a..598bc2645 100644
--- a/ui/admin/app/controllers/scopes/scope/roles/role/manage-scopes/manage-custom-scopes.js
+++ b/ui/admin/app/controllers/scopes/scope/roles/role/manage-scopes/manage-custom-scopes.js
@@ -43,23 +43,24 @@ export default class ScopesScopeRolesRoleManageScopesManageCustomScopesControlle
/**
* Save grant scope IDs to current role via the API.
* @param {RoleModel} role
- * @param {[string]} grantScopeIDs
*/
@action
@loading
@notifyError(({ message }) => message, { catch: true })
@notifySuccess('resources.role.scope.messages.manage-custom-scopes.success')
- async setGrantScopes(role, grantScopeIDs) {
- await role.setGrantScopes(grantScopeIDs);
+ async setGrantScopes(role) {
+ await role.setGrantScopes(role.grant_scope_ids);
this.manageScopes.showCheckIcon = true;
- await this.router.replaceWith('scopes.scope.roles.role.manage-scopes');
+ this.router.replaceWith('scopes.scope.roles.role.manage-scopes');
}
/**
* Redirect to manage scopes as if nothing ever happened.
+ * @param {RoleModel} role
*/
@action
- async cancel() {
- await this.router.replaceWith('scopes.scope.roles.role.manage-scopes');
+ cancel(role) {
+ role.rollbackAttributes();
+ this.router.replaceWith('scopes.scope.roles.role.manage-scopes');
}
}
diff --git a/ui/admin/app/controllers/scopes/scope/roles/role/manage-scopes/manage-org-projects.js b/ui/admin/app/controllers/scopes/scope/roles/role/manage-scopes/manage-org-projects.js
index 18c179154..466d3d17e 100644
--- a/ui/admin/app/controllers/scopes/scope/roles/role/manage-scopes/manage-org-projects.js
+++ b/ui/admin/app/controllers/scopes/scope/roles/role/manage-scopes/manage-org-projects.js
@@ -43,14 +43,13 @@ export default class ScopesScopeRolesRoleManageScopesManageOrgProjectsController
/**
* Save grant scope IDs to current role via the API.
* @param {RoleModel} role
- * @param {[string]} grantScopeIDs
*/
@action
@loading
@notifyError(({ message }) => message, { catch: true })
@notifySuccess('resources.role.scope.messages.manage-org-projects.success')
- async setGrantScopes(role, grantScopeIDs) {
- await role.setGrantScopes(grantScopeIDs);
+ async setGrantScopes(role) {
+ await role.setGrantScopes(role.grant_scope_ids);
this.manageScopes.showCheckIcon = true;
if (role.scope.isGlobal) {
this.router.replaceWith(
@@ -63,9 +62,11 @@ export default class ScopesScopeRolesRoleManageScopesManageOrgProjectsController
/**
* Redirect to previous as if nothing ever happened.
+ * @param {RoleModel} role
*/
@action
cancel(role) {
+ role.rollbackAttributes();
if (role.scope.isGlobal) {
this.router.replaceWith(
'scopes.scope.roles.role.manage-scopes.manage-custom-scopes',
diff --git a/ui/admin/app/routes/application.js b/ui/admin/app/routes/application.js
index 4187b03c4..4cb0dc6ac 100644
--- a/ui/admin/app/routes/application.js
+++ b/ui/admin/app/routes/application.js
@@ -43,7 +43,8 @@ export default class ApplicationRoute extends Route {
this.router.on('routeWillChange', async (transition) => {
const fromName = transition?.from?.name;
const toName = transition?.to?.name;
- const maybeModel = transition?.from?.attributes;
+ const maybeModel =
+ transition?.data?.model ?? transition?.from?.attributes;
if (fromName !== toName && maybeModel?.hasDirtyAttributes) {
transition.abort();
try {
diff --git a/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes/manage-custom-scopes.js b/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes/manage-custom-scopes.js
index c6821407b..075c785d5 100644
--- a/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes/manage-custom-scopes.js
+++ b/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes/manage-custom-scopes.js
@@ -4,6 +4,7 @@
*/
import Route from '@ember/routing/route';
+import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import { TYPE_SCOPE_PROJECT } from 'api/models/scope';
@@ -26,6 +27,8 @@ export default class ScopesScopeRolesRoleManageScopesManageCustomScopesRoute ext
// =services
@service store;
+ @service intl;
+ @service confirm;
// =methods
@@ -128,4 +131,17 @@ export default class ScopesScopeRolesRoleManageScopesManageCustomScopesRoute ext
);
return scopes.meta?.totalItems;
}
+
+ // =actions
+
+ /**
+ * Stores the role model in the transition data property so that the application level hook
+ * can check for dirty attributes and trigger the confirm service.
+ * @param {object} transition
+ */
+ @action
+ async willTransition(transition) {
+ const { role } = transition.from.attributes;
+ transition.data = { model: role };
+ }
}
diff --git a/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes/manage-org-projects.js b/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes/manage-org-projects.js
index 64e89984c..66978e5b8 100644
--- a/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes/manage-org-projects.js
+++ b/ui/admin/app/routes/scopes/scope/roles/role/manage-scopes/manage-org-projects.js
@@ -4,6 +4,7 @@
*/
import Route from '@ember/routing/route';
+import { action } from '@ember/object';
import { inject as service } from '@ember/service';
export default class ScopesScopeRolesRoleManageScopesManageOrgProjectsRoute extends Route {
@@ -25,12 +26,14 @@ export default class ScopesScopeRolesRoleManageScopesManageOrgProjectsRoute exte
// =services
@service store;
+ @service intl;
+ @service confirm;
// =methods
/**
* Loads projects for current org scope.
- * @return {Promise<{role: RoleModel, orgScope: ScopeModel, projectScopes: [ScopeModel], totalItems: number, totalItemsCount: number, selectedProjectIDs: [string], remainingProjectIDs: [string]}> }
+ * @return {Promise<{role: RoleModel, orgScope: ScopeModel, projectScopes: [ScopeModel], totalItems: number, totalItemsCount: number, remainingProjectIDs: [string]}> }
*/
async model({ org_id, search, page, pageSize }) {
const role = this.modelFor('scopes.scope.roles.role');
@@ -53,8 +56,7 @@ export default class ScopesScopeRolesRoleManageScopesManageOrgProjectsRoute exte
totalItems,
);
- const { selectedProjectIDs, remainingProjectIDs } =
- await this.getSelectedProjects(role, org_id);
+ const remainingProjectIDs = await this.getRemainingProjects(role, org_id);
return {
role,
@@ -62,7 +64,6 @@ export default class ScopesScopeRolesRoleManageScopesManageOrgProjectsRoute exte
projectScopes,
totalItems,
totalItemsCount,
- selectedProjectIDs,
remainingProjectIDs,
};
}
@@ -98,12 +99,12 @@ export default class ScopesScopeRolesRoleManageScopesManageOrgProjectsRoute exte
}
/**
- * Extract project type grant scope ids that belong to this org scope.
+ * Extract project type grant scope ids that don't belong to this org scope.
* @param {RoleModel} role
* @param {string} org_id
- * @returns {object}
+ * @returns {Promise<[string]>} remainingProjectIDs
*/
- async getSelectedProjects(role, org_id) {
+ async getRemainingProjects(role, org_id) {
let selectedProjectIDs = role.grantScopeProjectIDs;
let remainingProjectIDs = [];
if (role.scope.isGlobal && selectedProjectIDs.length) {
@@ -115,15 +116,25 @@ export default class ScopesScopeRolesRoleManageScopesManageOrgProjectsRoute exte
{ scope_id: 'global', query: { filters: { id } } },
options,
);
- selectedProjectIDs = projects.reduce((selectedIDs, project) => {
- if (project.scope.id === org_id) {
- selectedIDs.push(project.id);
- } else {
+ projects.forEach((project) => {
+ if (project.scope.id !== org_id) {
remainingProjectIDs.push(project.id);
}
- return selectedIDs;
- }, []);
+ });
}
- return { selectedProjectIDs, remainingProjectIDs };
+ return remainingProjectIDs;
+ }
+
+ // =actions
+
+ /**
+ * Stores the role model in the transition data property so that the application level hook
+ * can check for dirty attributes and trigger the confirm service.
+ * @param {object} transition
+ */
+ @action
+ async willTransition(transition) {
+ const { role } = transition.from.attributes;
+ transition.data = { model: role };
}
}
diff --git a/ui/admin/app/templates/scopes/scope/roles/role/manage-scopes/index.hbs b/ui/admin/app/templates/scopes/scope/roles/role/manage-scopes/index.hbs
index a6bd4fd8a..88215f172 100644
--- a/ui/admin/app/templates/scopes/scope/roles/role/manage-scopes/index.hbs
+++ b/ui/admin/app/templates/scopes/scope/roles/role/manage-scopes/index.hbs
@@ -22,7 +22,7 @@
@model={{@model}}
@showCheckIcon={{this.showCheckIcon}}
@submit={{fn this.setGrantScopes @model}}
- @cancel={{this.cancel}}
+ @cancel={{fn this.cancel @model}}
/>
diff --git a/ui/admin/app/templates/scopes/scope/roles/role/manage-scopes/manage-custom-scopes.hbs b/ui/admin/app/templates/scopes/scope/roles/role/manage-scopes/manage-custom-scopes.hbs
index d4e46fa69..041f8ef6a 100644
--- a/ui/admin/app/templates/scopes/scope/roles/role/manage-scopes/manage-custom-scopes.hbs
+++ b/ui/admin/app/templates/scopes/scope/roles/role/manage-scopes/manage-custom-scopes.hbs
@@ -33,7 +33,7 @@
@pageSize={{this.pageSize}}
@handleSearchInput={{this.handleSearchInput}}
@submit={{fn this.setGrantScopes @model.role}}
- @cancel={{this.cancel}}
+ @cancel={{fn this.cancel @model.role}}
/>
diff --git a/ui/admin/app/templates/scopes/scope/roles/role/manage-scopes/manage-org-projects.hbs b/ui/admin/app/templates/scopes/scope/roles/role/manage-scopes/manage-org-projects.hbs
index c4b221a15..baed52292 100644
--- a/ui/admin/app/templates/scopes/scope/roles/role/manage-scopes/manage-org-projects.hbs
+++ b/ui/admin/app/templates/scopes/scope/roles/role/manage-scopes/manage-org-projects.hbs
@@ -47,6 +47,7 @@
@search={{this.search}}
@page={{this.page}}
@pageSize={{this.pageSize}}
+ @selectedItems={{this.selectedItems}}
@handleSearchInput={{this.handleSearchInput}}
@submit={{fn this.setGrantScopes @model.role}}
@cancel={{fn this.cancel @model.role}}
diff --git a/ui/admin/tests/acceptance/roles/global-scope-test.js b/ui/admin/tests/acceptance/roles/global-scope-test.js
index a05d829ce..47ea17e27 100644
--- a/ui/admin/tests/acceptance/roles/global-scope-test.js
+++ b/ui/admin/tests/acceptance/roles/global-scope-test.js
@@ -29,6 +29,8 @@ module('Acceptance | roles | global-scope', function (hooks) {
setupMirage(hooks);
setupIndexedDb(hooks);
+ let confirmService;
+
const SCOPE_TOGGLE_SELECTOR = (name) =>
`.hds-form-toggle input[name="${name}"]`;
const SCOPE_CHECKBOX_SELECTOR = (type, id) =>
@@ -55,6 +57,11 @@ module('Acceptance | roles | global-scope', function (hooks) {
const BUTTON_ICON_SELECTOR =
'.hds-button__icon [data-test-icon="check-circle"]';
const PAGINATION_SELECTOR = '.hds-pagination';
+ const DISCARD_CHANGES_DIALOG = '.rose-dialog';
+ const DISCARD_CHANGES_DISCARD_BUTTON =
+ '.rose-dialog-footer .rose-button-primary';
+ const DISCARD_CHANGES_CANCEL_BUTTON =
+ '.rose-dialog-footer .rose-button-secondary';
const instances = {
scopes: {
@@ -75,6 +82,8 @@ module('Acceptance | roles | global-scope', function (hooks) {
hooks.beforeEach(function () {
authenticateSession({});
+ confirmService = this.owner.lookup('service:confirm');
+
instances.scopes.global = this.server.create('scope', { id: 'global' });
instances.scopes.org = this.server.create('scope', {
type: 'org',
@@ -345,6 +354,40 @@ module('Acceptance | roles | global-scope', function (hooks) {
assert.dom(TOAST_SELECTOR).isVisible();
});
+ test('user is prompted to confirm exit when there are unsaved changes on manage scopes page', async function (assert) {
+ instances.role.update({ grant_scope_ids: [] });
+ confirmService.enabled = true;
+ await visit(urls.role);
+
+ await click(MANAGE_DROPDOWN_SELECTOR);
+ await click(MANAGE_SCOPES_SELECTOR);
+ await click(SCOPE_TOGGLE_SELECTOR(GRANT_SCOPE_THIS));
+ await click(`[href="${urls.roles}"]`);
+
+ assert.dom(DISCARD_CHANGES_DIALOG).isVisible();
+
+ await click(DISCARD_CHANGES_DISCARD_BUTTON);
+
+ assert.strictEqual(currentURL(), urls.roles);
+ });
+
+ test('user user can cancel transition when there are unsaved changes on manage scopes page', async function (assert) {
+ instances.role.update({ grant_scope_ids: [] });
+ confirmService.enabled = true;
+ await visit(urls.role);
+
+ await click(MANAGE_DROPDOWN_SELECTOR);
+ await click(MANAGE_SCOPES_SELECTOR);
+ await click(SCOPE_TOGGLE_SELECTOR(GRANT_SCOPE_THIS));
+ await click(`[href="${urls.roles}"]`);
+
+ assert.dom(DISCARD_CHANGES_DIALOG).isVisible();
+
+ await click(DISCARD_CHANGES_CANCEL_BUTTON);
+
+ assert.strictEqual(currentURL(), urls.manageScopes);
+ });
+
test('user can save custom scopes to add on manage custom scopes page', async function (assert) {
instances.role.update({ grant_scope_ids: [] });
await visit(urls.role);
@@ -432,6 +475,40 @@ module('Acceptance | roles | global-scope', function (hooks) {
assert.dom(TOAST_SELECTOR).isVisible();
});
+ test('user is prompted to confirm exit when there are unsaved changes on manage custom scopes page', async function (assert) {
+ instances.role.update({ grant_scope_ids: [] });
+ confirmService.enabled = true;
+ await visit(urls.manageScopes);
+
+ await click(`[href="${urls.manageCustomScopes}"]`);
+ await click(SCOPE_CHECKBOX_SELECTOR('org', instances.scopes.org.id));
+
+ await click(`[href="${urls.roles}"]`);
+
+ assert.dom(DISCARD_CHANGES_DIALOG).isVisible();
+
+ await click(DISCARD_CHANGES_DISCARD_BUTTON);
+
+ assert.strictEqual(currentURL(), urls.roles);
+ });
+
+ test('user user can cancel transition when there are unsaved changes on manage custom scopes page', async function (assert) {
+ instances.role.update({ grant_scope_ids: [] });
+ confirmService.enabled = true;
+ await visit(urls.manageScopes);
+
+ await click(`[href="${urls.manageCustomScopes}"]`);
+ await click(SCOPE_CHECKBOX_SELECTOR('org', instances.scopes.org.id));
+
+ await click(`[href="${urls.roles}"]`);
+
+ assert.dom(DISCARD_CHANGES_DIALOG).isVisible();
+
+ await click(DISCARD_CHANGES_CANCEL_BUTTON);
+
+ assert.strictEqual(currentURL(), urls.manageCustomScopes);
+ });
+
test('user can search for a specific org scope by id on manage custom scopes page', async function (assert) {
const anotherOrg = this.server.create('scope', {
type: 'org',
@@ -591,6 +668,51 @@ module('Acceptance | roles | global-scope', function (hooks) {
assert.dom(TOAST_SELECTOR).isVisible();
});
+ test('user is prompted to confirm exit when there are unsaved changes on manage org projects page', async function (assert) {
+ instances.role.update({ grant_scope_ids: [] });
+ confirmService.enabled = true;
+ await visit(urls.manageScopes);
+
+ await click(`[href="${urls.manageCustomScopes}"]`);
+ await click(
+ `tbody [href="${urls.manageScopes}/${instances.scopes.org.id}"]`,
+ );
+ await click(
+ SCOPE_CHECKBOX_SELECTOR('project', instances.scopes.project.id),
+ );
+ await click(`[href="${urls.roles}"]`);
+
+ assert.dom(DISCARD_CHANGES_DIALOG).isVisible();
+
+ await click(DISCARD_CHANGES_DISCARD_BUTTON);
+
+ assert.strictEqual(currentURL(), urls.roles);
+ });
+
+ test('user user can cancel transition when there are unsaved changes on manage org projects page', async function (assert) {
+ instances.role.update({ grant_scope_ids: [] });
+ confirmService.enabled = true;
+ await visit(urls.manageScopes);
+
+ await click(`[href="${urls.manageCustomScopes}"]`);
+ await click(
+ `tbody [href="${urls.manageScopes}/${instances.scopes.org.id}"]`,
+ );
+ await click(
+ SCOPE_CHECKBOX_SELECTOR('project', instances.scopes.project.id),
+ );
+ await click(`[href="${urls.roles}"]`);
+
+ assert.dom(DISCARD_CHANGES_DIALOG).isVisible();
+
+ await click(DISCARD_CHANGES_CANCEL_BUTTON);
+
+ assert.strictEqual(
+ currentURL(),
+ `${urls.manageScopes}/${instances.scopes.org.id}`,
+ );
+ });
+
test('user can search for a specific project scope by id on manage org projects page', async function (assert) {
const anotherProject = this.server.create('scope', {
type: 'project',
diff --git a/ui/admin/tests/acceptance/roles/org-scope-test.js b/ui/admin/tests/acceptance/roles/org-scope-test.js
index 451dafc8d..b3c236365 100644
--- a/ui/admin/tests/acceptance/roles/org-scope-test.js
+++ b/ui/admin/tests/acceptance/roles/org-scope-test.js
@@ -28,6 +28,8 @@ module('Acceptance | roles | org-scope', function (hooks) {
setupMirage(hooks);
setupIndexedDb(hooks);
+ let confirmService;
+
const SCOPE_TOGGLE_SELECTOR = (name) =>
`.hds-form-toggle input[name="${name}"]`;
const SCOPE_CHECKBOX_SELECTOR = (type, id) =>
@@ -52,6 +54,11 @@ module('Acceptance | roles | org-scope', function (hooks) {
const BUTTON_ICON_SELECTOR =
'.hds-button__icon [data-test-icon="check-circle"]';
const PAGINATION_SELECTOR = '.hds-pagination';
+ const DISCARD_CHANGES_DIALOG = '.rose-dialog';
+ const DISCARD_CHANGES_DISCARD_BUTTON =
+ '.rose-dialog-footer .rose-button-primary';
+ const DISCARD_CHANGES_CANCEL_BUTTON =
+ '.rose-dialog-footer .rose-button-secondary';
const instances = {
scopes: {
@@ -61,6 +68,7 @@ module('Acceptance | roles | org-scope', function (hooks) {
},
role: null,
};
+
const urls = {
orgScope: null,
roles: null,
@@ -72,6 +80,8 @@ module('Acceptance | roles | org-scope', function (hooks) {
hooks.beforeEach(function () {
authenticateSession({});
+ confirmService = this.owner.lookup('service:confirm');
+
instances.scopes.global = this.server.create('scope', { id: 'global' });
instances.scopes.org = this.server.create('scope', {
type: 'org',
@@ -300,6 +310,40 @@ module('Acceptance | roles | org-scope', function (hooks) {
assert.dom(TOAST_SELECTOR).isVisible();
});
+ test('user is prompted to confirm exit when there are unsaved changes on manage scopes page', async function (assert) {
+ instances.role.update({ grant_scope_ids: [] });
+ confirmService.enabled = true;
+ await visit(urls.role);
+
+ await click(MANAGE_DROPDOWN_SELECTOR);
+ await click(MANAGE_SCOPES_SELECTOR);
+ await click(SCOPE_TOGGLE_SELECTOR(GRANT_SCOPE_THIS));
+ await click(`[href="${urls.roles}"]`);
+
+ assert.dom(DISCARD_CHANGES_DIALOG).isVisible();
+
+ await click(DISCARD_CHANGES_DISCARD_BUTTON);
+
+ assert.strictEqual(currentURL(), urls.roles);
+ });
+
+ test('user user can cancel transition when there are unsaved changes on manage scopes page', async function (assert) {
+ instances.role.update({ grant_scope_ids: [] });
+ confirmService.enabled = true;
+ await visit(urls.role);
+
+ await click(MANAGE_DROPDOWN_SELECTOR);
+ await click(MANAGE_SCOPES_SELECTOR);
+ await click(SCOPE_TOGGLE_SELECTOR(GRANT_SCOPE_THIS));
+ await click(`[href="${urls.roles}"]`);
+
+ assert.dom(DISCARD_CHANGES_DIALOG).isVisible();
+
+ await click(DISCARD_CHANGES_CANCEL_BUTTON);
+
+ assert.strictEqual(currentURL(), urls.manageScopes);
+ });
+
test('user can save custom scopes to add on manage org projects page', async function (assert) {
instances.role.update({ grant_scope_ids: [] });
await visit(urls.role);
@@ -394,6 +438,42 @@ module('Acceptance | roles | org-scope', function (hooks) {
assert.dom(TOAST_SELECTOR).isVisible();
});
+ test('user is prompted to confirm exit when there are unsaved changes on manage org projects page', async function (assert) {
+ instances.role.update({ grant_scope_ids: [] });
+ confirmService.enabled = true;
+ await visit(urls.manageScopes);
+
+ await click(`[href="${urls.manageOrgProjects}"]`);
+ await click(
+ SCOPE_CHECKBOX_SELECTOR('project', instances.scopes.project.id),
+ );
+ await click(`[href="${urls.roles}"]`);
+
+ assert.dom(DISCARD_CHANGES_DIALOG).isVisible();
+
+ await click(DISCARD_CHANGES_DISCARD_BUTTON);
+
+ assert.strictEqual(currentURL(), urls.roles);
+ });
+
+ test('user user can cancel transition when there are unsaved changes on manage org projects page', async function (assert) {
+ instances.role.update({ grant_scope_ids: [] });
+ confirmService.enabled = true;
+ await visit(urls.manageScopes);
+
+ await click(`[href="${urls.manageOrgProjects}"]`);
+ await click(
+ SCOPE_CHECKBOX_SELECTOR('project', instances.scopes.project.id),
+ );
+ await click(`[href="${urls.roles}"]`);
+
+ assert.dom(DISCARD_CHANGES_DIALOG).isVisible();
+
+ await click(DISCARD_CHANGES_CANCEL_BUTTON);
+
+ assert.strictEqual(currentURL(), urls.manageOrgProjects);
+ });
+
test('user can search for a specific project scope by id on manage org projects page', async function (assert) {
const anotherProject = this.server.create('scope', {
type: 'project',
diff --git a/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/index-test.js
index b056556bc..91b5b55cc 100644
--- a/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/index-test.js
+++ b/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/index-test.js
@@ -8,6 +8,7 @@ import { setupTest } from 'ember-qunit';
import { visit, currentURL, waitUntil } from '@ember/test-helpers';
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
import { authenticateSession } from 'ember-simple-auth/test-support';
+import { GRANT_SCOPE_THIS } from 'api/models/role';
module(
'Unit | Controller | scopes/scope/roles/role/manage-scopes/index',
@@ -59,17 +60,23 @@ module(
assert.deepEqual(role.grant_scope_ids, []);
- await controller.setGrantScopes(role, ['this']);
+ role.grant_scope_ids = [GRANT_SCOPE_THIS];
+ await controller.setGrantScopes(role);
- assert.deepEqual(role.grant_scope_ids, ['this']);
+ assert.deepEqual(role.grant_scope_ids, [GRANT_SCOPE_THIS]);
});
test('cancel action causes transition to expected route', async function (assert) {
await visit(urls.manageScopes);
+ const role = await store.findRecord('role', instances.role.id);
- controller.cancel();
+ assert.deepEqual(role.grant_scope_ids, []);
+
+ role.grant_scope_ids = [GRANT_SCOPE_THIS];
+ controller.cancel(role);
await waitUntil(() => currentURL() === urls.scopes);
+ assert.deepEqual(role.grant_scope_ids, []);
assert.strictEqual(currentURL(), urls.scopes);
});
},
diff --git a/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/manage-custom-scopes-test.js b/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/manage-custom-scopes-test.js
index 76d398462..ac0501ad0 100644
--- a/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/manage-custom-scopes-test.js
+++ b/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/manage-custom-scopes-test.js
@@ -75,16 +75,22 @@ module(
assert.deepEqual(role.grant_scope_ids, []);
- await controller.setGrantScopes(role, [instances.scopes.org.id]);
+ role.grant_scope_ids = [instances.scopes.org.id];
+ await controller.setGrantScopes(role);
assert.deepEqual(role.grant_scope_ids, [instances.scopes.org.id]);
});
- test('cancel action causes transition to expected route', async function (assert) {
+ test('cancel action rolls back changes causes transition to expected route', async function (assert) {
await visit(urls.manageCustomScopes);
+ const role = await store.findRecord('role', instances.role.id);
- await controller.cancel();
+ assert.deepEqual(role.grant_scope_ids, []);
+ role.grant_scope_ids = [instances.scopes.org.id];
+ await controller.cancel(role);
+
+ assert.deepEqual(role.grant_scope_ids, []);
assert.strictEqual(currentURL(), urls.manageScopes);
});
},
diff --git a/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/manage-org-projects-test.js b/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/manage-org-projects-test.js
index 3390e78d7..8e8402a19 100644
--- a/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/manage-org-projects-test.js
+++ b/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/manage-org-projects-test.js
@@ -83,22 +83,25 @@ module(
assert.deepEqual(role.grant_scope_ids, []);
- await controller.setGrantScopes(role, [instances.scopes.project.id]);
-
- assert.deepEqual(role.grant_scope_ids, [instances.scopes.project.id]);
-
+ role.grant_scope_ids = [instances.scopes.project.id];
+ await controller.setGrantScopes(role);
await waitUntil(() => currentURL() === urls.manageCustomScopes);
+ assert.deepEqual(role.grant_scope_ids, [instances.scopes.project.id]);
assert.strictEqual(currentURL(), urls.manageCustomScopes);
});
- test('cancel action causes transition to expected route', async function (assert) {
+ test('cancel action rolls back changes causes transition to expected route', async function (assert) {
await visit(urls.manageOrgProjects);
const role = await store.findRecord('role', instances.role.id);
+ assert.deepEqual(role.grant_scope_ids, []);
+
+ role.grant_scope_ids = [instances.scopes.project.id];
await controller.cancel(role);
await waitUntil(() => currentURL() === urls.manageCustomScopes);
+ assert.deepEqual(role.grant_scope_ids, []);
assert.strictEqual(currentURL(), urls.manageCustomScopes);
});
},