feat: 🎸 Add discard modal for edit grants page (#3324)

This commit is contained in:
Zhihe Li authored and GitHub committed 2026-06-18 13:35:49 -04:00
1 parent 0dac4ccf47
commit 347107c109
4 files changed
+104 -9

No files matched your search

@@ -44,7 +44,7 @@
@color='secondary'
{{on 'click' @cancel}}
/>
{{#if this.grantStringsText}}
{{#if this.grantStrings.length}}
<Hds::Button
@text={{t 'actions.export'}}
@color='tertiary'
@@ -37,7 +37,6 @@ export default class FormRoleEditGrantsComponent extends Component {
exportOptionsMap = { terraform: 'terraform', nativeHCL: 'native-hcl' };
exportOptions = Object.values(this.exportOptionsMap);
@tracked grantStringsText = (this.args.model?.grant_strings ?? []).join('\n');
@tracked currentLineText = this.args.model?.grant_strings?.[0] ?? '';
@tracked showExportOptionsFlyout = false;
@tracked selectedExportOption = this.exportOptions[0];
@@ -182,7 +181,11 @@ export default class FormRoleEditGrantsComponent extends Component {
const line = update.state.doc.lineAt(update.state.selection.main.head);
this.currentLineText = line.text;
if (update.docChanged) {
this.grantStringsText = update.state.doc.toString();
const text = update.state.doc.toString();
this.args.model.grant_strings = text
.split('\n')
.map((s) => 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`;
@@ -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 };
}
}
@@ -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);
});
});