diff --git a/ui/admin/app/components/form/role/edit-grants/index.hbs b/ui/admin/app/components/form/role/edit-grants/index.hbs index ef66b7d51..5d98f1bc6 100644 --- a/ui/admin/app/components/form/role/edit-grants/index.hbs +++ b/ui/admin/app/components/form/role/edit-grants/index.hbs @@ -44,7 +44,7 @@ @color='secondary' {{on 'click' @cancel}} /> - {{#if this.grantStringsText}} + {{#if this.grantStrings.length}} s.trim()) + .filter(Boolean); } // Trigger autocomplete when we're on an empty line if (line.text === '') { @@ -200,11 +203,12 @@ export default class FormRoleEditGrantsComponent extends Component { this.#editorView?.destroy(); } + get grantStringsText() { + return this.grantStrings.join('\n'); + } + get grantStrings() { - return this.grantStringsText - .split('\n') - .map((grantString) => grantString.trim()) - .filter(Boolean); + return this.args.model?.grant_strings ?? []; } /** @@ -224,7 +228,7 @@ export default class FormRoleEditGrantsComponent extends Component { */ get terraformFormattedExport() { let formatted = `grant_strings = [ \n`; - this.grantStringsText.split('\n').forEach((line) => { + this.grantStrings.forEach((line) => { formatted += ` "${line}",\n`; }); formatted += `]\n`; @@ -237,7 +241,7 @@ export default class FormRoleEditGrantsComponent extends Component { */ get nativeHclFormattedExport() { let formatted = `[ \n`; - this.grantStringsText.split('\n').forEach((line) => { + this.grantStrings.forEach((line) => { formatted += ` "${line}",\n`; }); formatted += `]\n`; diff --git a/ui/admin/app/routes/scopes/scope/roles/role/edit-grants.js b/ui/admin/app/routes/scopes/scope/roles/role/edit-grants.js index 65a1d05d4..88a3fbe15 100644 --- a/ui/admin/app/routes/scopes/scope/roles/role/edit-grants.js +++ b/ui/admin/app/routes/scopes/scope/roles/role/edit-grants.js @@ -4,6 +4,7 @@ */ import Route from '@ember/routing/route'; +import { action } from '@ember/object'; import { service } from '@ember/service'; import { dropTask } from 'ember-concurrency'; import { modelMapping } from 'api/services/sqlite'; @@ -60,4 +61,15 @@ export default class ScopesScopeRolesRoleEditGrantsRoute extends Route { return response.json(); } + + /** + * 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/tests/acceptance/roles/edit-grants-test.js b/ui/admin/tests/acceptance/roles/edit-grants-test.js index ce4c87a47..e64340fdb 100644 --- a/ui/admin/tests/acceptance/roles/edit-grants-test.js +++ b/ui/admin/tests/acceptance/roles/edit-grants-test.js @@ -348,4 +348,83 @@ module('Acceptance | roles/edit grants', function (hooks) { assert.dom(selectors.AUTOCOMPLETE_TOOLTIP).isVisible(); }); + + test('can discard unsaved grant changes via dialog', async function (assert) { + setRunOptions({ + rules: { + 'color-contrast': { + // [ember-a11y-ignore]: axe rule "color-contrast" automatically ignored on 2026-06-17 + enabled: false, + }, + }, + }); + + assert.expect(3); + const confirmService = this.owner.lookup('service:confirm'); + confirmService.enabled = true; + + await visit(urls.editGrants); + await waitFor(commonSelectors.CODE_EDITOR_CM); + + const newGrantString = 'ids=*;type=session;actions=list'; + const editorElement = find(commonSelectors.CODE_EDITOR_CODE); + const editorView = editorElement.editor; + editorView.dispatch({ + changes: { + from: editorView.state.doc.length, + insert: `\n${newGrantString}`, + }, + }); + await click(commonSelectors.HREF(urls.role)); + + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); + + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); + + assert.strictEqual(currentURL(), urls.role); + console.log(this.server.schema.roles.find(instances.role.id)); + assert.false( + this.server.schema.roles + .find(instances.role.id) + .grant_strings.includes(newGrantString), + ); + }); + + test('can cancel discard grant changes via dialog', async function (assert) { + setRunOptions({ + rules: { + 'color-contrast': { + // [ember-a11y-ignore]: axe rule "color-contrast" automatically ignored on 2026-06-17 + enabled: false, + }, + }, + }); + + assert.expect(3); + const confirmService = this.owner.lookup('service:confirm'); + confirmService.enabled = true; + + await visit(urls.editGrants); + await waitFor(commonSelectors.CODE_EDITOR_CM); + + const newGrantString = 'ids=*;type=session;actions=list'; + const editorElement = find(commonSelectors.CODE_EDITOR_CODE); + const editorView = editorElement.editor; + editorView.dispatch({ + changes: { + from: editorView.state.doc.length, + insert: `\n${newGrantString}`, + }, + }); + await click(commonSelectors.HREF(urls.role)); + + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); + + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); + + assert.strictEqual(currentURL(), urls.editGrants); + assert + .dom(commonSelectors.CODE_EDITOR_CONTENT) + .includesText(newGrantString); + }); });