fix: 🐛 actions not displaying for templates in grants editor (#3296)

* fix: 🐛 actions not displaying for templates in grants editor

* fix: 🐛 invalid ids check

* fix: 🐛 update check for ids type

* test: 💍 fix broken strings

* test: 💍 fix test case

* test: 💍 remove log
This commit is contained in:
Lisbet Alvarez authored and GitHub committed 2026-06-03 12:46:34 -07:00
1 parent 4a206c82dc
commit ecefa39b98
5 files changed
+76 -75

No files matched your search

@@ -758,8 +758,8 @@ role:
label: User template label: User template
help: Substitutes the user ID from the token used to perform the request. help: Substitutes the user ID from the token used to perform the request.
insert-actions: insert actions insert-actions: insert actions
insert-resource-types: insert resource types insert-resource-type: insert resource type
insert-id: insert id insert-ids: insert ids
insert-resource-ids: insert resource ids insert-resource-ids: insert resource ids
insert-output-fields: insert output fields insert-output-fields: insert output fields
export: export:
@@ -28,7 +28,7 @@ export default class GrantStringFormatsIndex extends Component {
stringFormats = { stringFormats = {
[this.stringFormatTypes.resourceType]: { [this.stringFormatTypes.resourceType]: {
text: `type=<${this.intl.t('resources.role.edit-grants.string-formats.insert-resource-types')}>;actions=<${this.intl.t('resources.role.edit-grants.string-formats.insert-actions')}>`, text: `type=<${this.intl.t('resources.role.edit-grants.string-formats.insert-resource-type')}>;actions=<${this.intl.t('resources.role.edit-grants.string-formats.insert-actions')}>`,
link: 'role.grant-string-format.resource-type', link: 'role.grant-string-format.resource-type',
}, },
[this.stringFormatTypes.resource]: { [this.stringFormatTypes.resource]: {
@@ -36,7 +36,7 @@ export default class GrantStringFormatsIndex extends Component {
link: 'role.grant-string-format.resource', link: 'role.grant-string-format.resource',
}, },
[this.stringFormatTypes.pinnedId]: { [this.stringFormatTypes.pinnedId]: {
text: `ids=<${this.intl.t('resources.role.edit-grants.string-formats.insert-id')}>;type=<${this.intl.t('resources.role.edit-grants.string-formats.insert-resource-types')}>;actions=<${this.intl.t('resources.role.edit-grants.string-formats.insert-actions')}>`, text: `ids=<${this.intl.t('resources.role.edit-grants.string-formats.insert-ids')}>;type=<${this.intl.t('resources.role.edit-grants.string-formats.insert-resource-type')}>;actions=<${this.intl.t('resources.role.edit-grants.string-formats.insert-actions')}>`,
link: 'role.grant-string-format.pinned-id', link: 'role.grant-string-format.pinned-id',
}, },
[this.stringFormatTypes.outputFields]: { [this.stringFormatTypes.outputFields]: {
+2 -8
View File
@@ -101,18 +101,12 @@ export const analyzeGrantString = (grantsSchema, grantString = '') => {
const hasSpecificIds = Boolean(idsValue) && !idsValue.includes('*'); const hasSpecificIds = Boolean(idsValue) && !idsValue.includes('*');
const hasExplicitType = Boolean(typeValue) && typeValue !== '*'; const hasExplicitType = Boolean(typeValue) && typeValue !== '*';
const hasTemplateIds = const compatibleIdsResourceType = hasSpecificIds
hasSpecificIds &&
parseIds(idsValue).some((id) => id.startsWith('{{') && id.endsWith('}}'));
const hasLiteralIds = hasSpecificIds && !hasTemplateIds;
const compatibleIdsResourceType = hasLiteralIds
? getCompatibleResourceTypeForIds(schema, idsValue) ? getCompatibleResourceTypeForIds(schema, idsValue)
: null; : null;
const hasInvalidType = hasExplicitType && !schema.resourcesByType[typeValue]; const hasInvalidType = hasExplicitType && !schema.resourcesByType[typeValue];
const hasInvalidIds = hasLiteralIds && !compatibleIdsResourceType; const hasInvalidIds = hasSpecificIds && !compatibleIdsResourceType;
const hasInvalidPinnedIdTypeCombination = const hasInvalidPinnedIdTypeCombination =
hasSpecificIds && hasSpecificIds &&
@@ -6,6 +6,7 @@
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { setupRenderingTest } from 'admin/tests/helpers'; import { setupRenderingTest } from 'admin/tests/helpers';
import { setupIntl } from 'ember-intl/test-support'; import { setupIntl } from 'ember-intl/test-support';
import { setupMirage } from 'admin/tests/helpers/mirage';
import { render } from '@ember/test-helpers'; import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars'; import { hbs } from 'ember-cli-htmlbars';
import * as commonSelectors from 'admin/tests/helpers/selectors'; import * as commonSelectors from 'admin/tests/helpers/selectors';
@@ -19,40 +20,18 @@ const GRANT_ACTIONS_INVALID_ID_OR_TYPE =
module('Integration | Component | grant-actions/index', function (hooks) { module('Integration | Component | grant-actions/index', function (hooks) {
setupRenderingTest(hooks); setupRenderingTest(hooks);
setupMirage(hooks);
setupIntl(hooks, 'en-us'); setupIntl(hooks, 'en-us');
hooks.beforeEach(function () { hooks.beforeEach(async function () {
this.grantsSchema = { const response = await fetch('/grants-schema.json');
resource_types: [
{ if (!response.ok) {
type: 'host-catalog', throw new Error(`Failed to fetch grants schema: ${response.status}`);
collection_actions: ['create', 'list'], }
id_actions: ['read', 'update', 'delete'],
id_prefixes: ['hcst'], const grantsSchemaData = await response.json();
}, this.grantsSchema = grantsSchemaData;
{
type: 'host',
parent_type: 'host-catalog',
collection_actions: ['create', 'list'],
id_actions: ['read', 'update', 'delete'],
id_prefixes: ['h'],
},
{
type: 'host-set',
parent_type: 'host-catalog',
collection_actions: ['create', 'list'],
id_actions: [
'delete',
'add-hosts',
'set-hosts',
'remove-hosts',
'read',
'update',
],
id_prefixes: ['hs'],
},
],
};
}); });
test('it renders the actions relevant to the current grant line', async function (assert) { test('it renders the actions relevant to the current grant line', async function (assert) {
@@ -68,16 +47,13 @@ module('Integration | Component | grant-actions/index', function (hooks) {
assert.dom(GRANT_ACTIONS_TABLE).isVisible(); assert.dom(GRANT_ACTIONS_TABLE).isVisible();
assert.dom(commonSelectors.TABLE_ROWS).isVisible({ count: 8 }); assert.dom(commonSelectors.TABLE_ROWS).isVisible({ count: 8 });
const rows = [...this.element.querySelectorAll(commonSelectors.TABLE_ROWS)]; const rowData = [
const rowData = rows.map((row) => { ...this.element.querySelectorAll(commonSelectors.TABLE_ROWS),
const cells = [...row.querySelectorAll('td')].map((cell) => ].map((row) => {
const [name, description] = [...row.querySelectorAll('td')].map((cell) =>
cell.textContent.trim(), cell.textContent.trim(),
); );
return { name, description };
return {
name: cells[0],
description: cells[1],
};
}); });
assert.deepEqual( assert.deepEqual(
@@ -98,12 +74,10 @@ module('Integration | Component | grant-actions/index', function (hooks) {
name: 'add-hosts', name: 'add-hosts',
description: 'Add hosts to a host set', description: 'Add hosts to a host set',
}); });
assert.deepEqual(rowData[1], { assert.deepEqual(rowData[1], {
name: 'create', name: 'create',
description: 'Create a new host-set', description: 'Create a new host-set',
}); });
assert.deepEqual(rowData.at(-1), { assert.deepEqual(rowData.at(-1), {
name: 'update', name: 'update',
description: 'Update a host-set', description: 'Update a host-set',
@@ -113,6 +87,58 @@ module('Integration | Component | grant-actions/index', function (hooks) {
assert.dom(GRANT_ACTIONS_NO_TYPE_DETECTED).doesNotExist(); assert.dom(GRANT_ACTIONS_NO_TYPE_DETECTED).doesNotExist();
}); });
test('it renders relevant actions for template IDs', async function (assert) {
this.grantString = 'ids={{.User.Id}}';
await render(hbs`
<GrantActions
@grantsSchema={{this.grantsSchema}}
@grantString={{this.grantString}}
/>
`);
assert.dom(GRANT_ACTIONS_TABLE).isVisible();
assert.dom(commonSelectors.TABLE_ROWS).isVisible({ count: 7 });
const rowData = [
...this.element.querySelectorAll(commonSelectors.TABLE_ROWS),
].map((row) => {
const [name, description] = [...row.querySelectorAll('td')].map((cell) =>
cell.textContent.trim(),
);
return { name, description };
});
assert.deepEqual(
rowData.map(({ name }) => name),
[
'add-accounts',
'delete',
'list-resolvable-aliases',
'read',
'remove-accounts',
'set-accounts',
'update',
],
);
assert.deepEqual(rowData[0], {
name: 'add-accounts',
description: 'Add accounts to a user',
});
assert.deepEqual(rowData[1], {
name: 'delete',
description: 'Delete a user',
});
assert.deepEqual(rowData.at(-1), {
name: 'update',
description: 'Update a user',
});
assert.dom(GRANT_ACTIONS_EMPTY_STATE).doesNotExist();
assert.dom(GRANT_ACTIONS_NO_TYPE_DETECTED).doesNotExist();
});
test('it renders the no resource type detected state when the current line has no detectable type', async function (assert) { test('it renders the no resource type detected state when the current line has no detectable type', async function (assert) {
this.grantString = 'output_fields=*'; this.grantString = 'output_fields=*';
@@ -201,24 +227,6 @@ module('Integration | Component | grant-actions/index', function (hooks) {
assert.dom(GRANT_ACTIONS_EMPTY_STATE).doesNotExist(); assert.dom(GRANT_ACTIONS_EMPTY_STATE).doesNotExist();
}); });
test('it renders the no-type-detected state for template IDs', async function (assert) {
this.grantString = 'ids={{.User.Id}}';
await render(hbs`
<GrantActions
@grantsSchema={{this.grantsSchema}}
@grantString={{this.grantString}}
/>
`);
assert.dom(GRANT_ACTIONS_TABLE).doesNotExist();
assert.dom(GRANT_ACTIONS_INVALID_ID_OR_TYPE).doesNotExist();
assert
.dom(GRANT_ACTIONS_NO_TYPE_DETECTED)
.hasText('No resource type detected.');
assert.dom(GRANT_ACTIONS_EMPTY_STATE).doesNotExist();
});
test('it renders the invalid state for a pinned id paired with its own type instead of a child type', async function (assert) { test('it renders the invalid state for a pinned id paired with its own type instead of a child type', async function (assert) {
this.grantString = 'ids=hcst_1234567890;type=host-catalog'; this.grantString = 'ids=hcst_1234567890;type=host-catalog';
@@ -39,8 +39,7 @@ module(
[ [
{ {
selectedOption: 'resourceType', selectedOption: 'resourceType',
expectedValue: expectedValue: 'type=<insert resource type>;actions=<insert actions>',
'type=<insert resource types>;actions=<insert actions>',
}, },
{ {
optionToClick: RESOURCE_OPTION, optionToClick: RESOURCE_OPTION,
@@ -51,7 +50,7 @@ module(
optionToClick: PINNED_ID_OPTION, optionToClick: PINNED_ID_OPTION,
selectedOption: 'pinnedId', selectedOption: 'pinnedId',
expectedValue: expectedValue:
'ids=<insert id>;type=<insert resource types>;actions=<insert actions>', 'ids=<insert ids>;type=<insert resource type>;actions=<insert actions>',
}, },
{ {
optionToClick: OUTPUT_FIELDS_OPTION, optionToClick: OUTPUT_FIELDS_OPTION,