Merge pull request #30231 from backstage/blam/actions-definition-change

`ActionsRegistry`: Allow providing attributes to the `Actions` that are registered
This commit is contained in:
Ben Lambert
2025-06-16 15:19:44 +02:00
committed by GitHub
6 changed files with 142 additions and 2 deletions
@@ -70,6 +70,11 @@ describe('actionsServiceFactory', () => {
input: {},
output: {},
},
attributes: {
destructive: false,
idempotent: false,
readOnly: false,
},
};
beforeEach(() => {
@@ -306,6 +311,11 @@ describe('actionsServiceFactory', () => {
type: 'object',
},
},
attributes: {
destructive: true,
idempotent: false,
readOnly: false,
},
title: 'Test',
},
],
@@ -67,13 +67,20 @@ export class DefaultActionsRegistryService implements ActionsRegistryService {
actions: Array.from(this.actions.entries()).map(([id, action]) => ({
id,
...action,
attributes: {
// Inspired by the @modelcontextprotocol/sdk defaults for the hints.
// https://github.com/modelcontextprotocol/typescript-sdk/blob/dd69efa1de8646bb6b195ff8d5f52e13739f4550/src/types.ts#L777-L812
destructive: action.attributes?.destructive ?? true,
idempotent: action.attributes?.idempotent ?? false,
readOnly: action.attributes?.readOnly ?? false,
},
schema: {
input: action.schema?.input
? zodToJsonSchema(action.schema.input(z))
: zodToJsonSchema(z.any()),
: zodToJsonSchema(z.object({})),
output: action.schema?.output
? zodToJsonSchema(action.schema.output(z))
: zodToJsonSchema(z.any()),
: zodToJsonSchema(z.object({})),
},
})),
});
@@ -181,6 +181,109 @@ describe('actionsRegistryServiceFactory', () => {
});
});
it('should set default attributes', async () => {
const pluginSubject = createBackendPlugin({
pluginId: 'my-plugin',
register(reg) {
reg.registerInit({
deps: {
actionsRegistry: coreServices.actionsRegistry,
},
async init({ actionsRegistry }) {
actionsRegistry.register({
name: 'test',
title: 'Test',
description: 'Test',
schema: {
input: z => z.object({}),
output: z => z.object({}),
},
action: async () => ({ output: { ok: true } }),
});
},
});
},
});
const { server } = await startTestBackend({
features: [pluginSubject, ...defaultServices],
});
const { body, status } = await request(server).get(
'/api/my-plugin/.backstage/actions/v1/actions',
);
expect(status).toBe(200);
expect(body).toMatchObject({
actions: [
{
name: 'test',
attributes: {
destructive: true,
idempotent: false,
readOnly: false,
},
},
],
});
});
it('should allow setting attributes', async () => {
const pluginSubject = createBackendPlugin({
pluginId: 'my-plugin',
register(reg) {
reg.registerInit({
deps: {
actionsRegistry: coreServices.actionsRegistry,
},
async init({ actionsRegistry }) {
actionsRegistry.register({
name: 'test',
title: 'Test',
description: 'Test',
attributes: {
destructive: false,
idempotent: true,
readOnly: true,
},
schema: {
input: z => z.object({}),
output: z => z.object({}),
},
action: async () => ({ output: { ok: true } }),
});
},
});
},
});
const { server } = await startTestBackend({
features: [pluginSubject, ...defaultServices],
});
const { body, status } = await request(server).get(
'/api/my-plugin/.backstage/actions/v1/actions',
);
expect(status).toBe(200);
expect(body).toMatchObject({
actions: [
{
name: 'test',
title: 'Test',
description: 'Test',
attributes: {
destructive: false,
idempotent: true,
readOnly: true,
},
},
],
});
});
it('should forces registration of input and output schema as objects', async () => {
const pluginSubject = createBackendPlugin({
pluginId: 'my-plugin',
+10
View File
@@ -48,6 +48,11 @@ export type ActionsRegistryActionOptions<
input: (zod: typeof z) => TInputSchema;
output: (zod: typeof z) => TOutputSchema;
};
attributes?: {
destructive?: boolean;
idempotent?: boolean;
readOnly?: boolean;
};
action: (context: ActionsRegistryActionContext<TInputSchema>) => Promise<
z.infer<TOutputSchema> extends void
? void
@@ -94,6 +99,11 @@ export type ActionsServiceAction = {
input: JSONSchema7;
output: JSONSchema7;
};
attributes: {
readOnly: boolean;
destructive: boolean;
idempotent: boolean;
};
};
// @public
@@ -40,6 +40,11 @@ export type ActionsRegistryActionOptions<
input: (zod: typeof z) => TInputSchema;
output: (zod: typeof z) => TOutputSchema;
};
attributes?: {
destructive?: boolean;
idempotent?: boolean;
readOnly?: boolean;
};
action: (
context: ActionsRegistryActionContext<TInputSchema>,
) => Promise<
@@ -29,6 +29,11 @@ export type ActionsServiceAction = {
input: JSONSchema7;
output: JSONSchema7;
};
attributes: {
readOnly: boolean;
destructive: boolean;
idempotent: boolean;
};
};
/**