rename header action to plugin header action

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2026-02-16 22:22:41 +01:00
parent 0f90fda9cc
commit 92d77a98ff
19 changed files with 114 additions and 130 deletions
+24 -28
View File
@@ -469,34 +469,6 @@ const appPlugin: OverridableFrontendPlugin<
params: ApiFactory<TApi, TImpl, TDeps>,
) => ExtensionBlueprintParams<AnyApiFactory>;
}>;
'api:app/header-actions': OverridableExtensionDefinition<{
config: {};
configInput: {};
output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>;
inputs: {
actions: ExtensionInput<
ConfigurableExtensionDataRef<
() => Promise<JSX.Element>,
'core.header-action.loader',
{}
>,
{
singleton: false;
optional: false;
internal: false;
}
>;
};
kind: 'api';
name: 'header-actions';
params: <
TApi,
TImpl extends TApi,
TDeps extends { [name in string]: unknown },
>(
params: ApiFactory<TApi, TImpl, TDeps>,
) => ExtensionBlueprintParams<AnyApiFactory>;
}>;
'api:app/icons': OverridableExtensionDefinition<{
config: {};
configInput: {};
@@ -617,6 +589,30 @@ const appPlugin: OverridableFrontendPlugin<
params: ApiFactory<TApi, TImpl, TDeps>,
) => ExtensionBlueprintParams<AnyApiFactory>;
}>;
'api:app/plugin-header-actions': OverridableExtensionDefinition<{
config: {};
configInput: {};
output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>;
inputs: {
actions: ExtensionInput<
ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>,
{
singleton: false;
optional: false;
internal: false;
}
>;
};
kind: 'api';
name: 'plugin-header-actions';
params: <
TApi,
TImpl extends TApi,
TDeps extends { [name in string]: unknown },
>(
params: ApiFactory<TApi, TImpl, TDeps>,
) => ExtensionBlueprintParams<AnyApiFactory>;
}>;
'api:app/plugin-wrapper': OverridableExtensionDefinition<{
config: {};
configInput: {};
@@ -15,11 +15,11 @@
*/
import { render, screen } from '@testing-library/react';
import { DefaultHeaderActionsApi } from './DefaultHeaderActionsApi';
import { DefaultPluginHeaderActionsApi } from './DefaultPluginHeaderActionsApi';
describe('DefaultHeaderActionsApi', () => {
describe('DefaultPluginHeaderActionsApi', () => {
it('should return actions for a specific plugin', () => {
const api = DefaultHeaderActionsApi.fromActions([
const api = DefaultPluginHeaderActionsApi.fromActions([
{
element: <button>Action A</button>,
pluginId: 'plugin-a',
@@ -30,28 +30,28 @@ describe('DefaultHeaderActionsApi', () => {
},
]);
expect(api.getHeaderActions('plugin-a')).toHaveLength(1);
expect(api.getHeaderActions('plugin-b')).toHaveLength(1);
expect(api.getPluginHeaderActions('plugin-a')).toHaveLength(1);
expect(api.getPluginHeaderActions('plugin-b')).toHaveLength(1);
render(<>{api.getHeaderActions('plugin-a')}</>);
render(<>{api.getPluginHeaderActions('plugin-a')}</>);
expect(
screen.getByRole('button', { name: 'Action A' }),
).toBeInTheDocument();
});
it('should return an empty array for unknown plugins', () => {
const api = DefaultHeaderActionsApi.fromActions([
const api = DefaultPluginHeaderActionsApi.fromActions([
{
element: <span>Action</span>,
pluginId: 'plugin-a',
},
]);
expect(api.getHeaderActions('unknown-plugin')).toEqual([]);
expect(api.getPluginHeaderActions('unknown-plugin')).toEqual([]);
});
it('should group multiple actions by plugin', () => {
const api = DefaultHeaderActionsApi.fromActions([
const api = DefaultPluginHeaderActionsApi.fromActions([
{
element: <button>First</button>,
pluginId: 'plugin-a',
@@ -62,7 +62,7 @@ describe('DefaultHeaderActionsApi', () => {
},
]);
const actions = api.getHeaderActions('plugin-a');
const actions = api.getPluginHeaderActions('plugin-a');
expect(actions).toHaveLength(2);
render(<>{actions}</>);
@@ -15,7 +15,7 @@
*/
import { ReactNode } from 'react';
import { type HeaderActionsApi } from '@backstage/frontend-plugin-api';
import { type PluginHeaderActionsApi } from '@backstage/frontend-plugin-api';
type ActionInput = {
element: JSX.Element;
@@ -23,18 +23,20 @@ type ActionInput = {
};
/**
* Default implementation of HeaderActionsApi.
* Default implementation of PluginHeaderActionsApi.
*
* @internal
*/
export class DefaultHeaderActionsApi implements HeaderActionsApi {
export class DefaultPluginHeaderActionsApi implements PluginHeaderActionsApi {
constructor(private readonly actionsByPlugin: Map<string, ReactNode[]>) {}
getHeaderActions(pluginId: string): ReactNode[] {
getPluginHeaderActions(pluginId: string): ReactNode[] {
return this.actionsByPlugin.get(pluginId) ?? [];
}
static fromActions(actions: Array<ActionInput>): DefaultHeaderActionsApi {
static fromActions(
actions: Array<ActionInput>,
): DefaultPluginHeaderActionsApi {
const actionsByPlugin = new Map<string, ReactNode[]>();
for (const action of actions) {
@@ -47,6 +49,6 @@ export class DefaultHeaderActionsApi implements HeaderActionsApi {
pluginActions.push(action.element);
}
return new DefaultHeaderActionsApi(actionsByPlugin);
return new DefaultPluginHeaderActionsApi(actionsByPlugin);
}
}
@@ -14,4 +14,4 @@
* limitations under the License.
*/
export { DefaultHeaderActionsApi } from './DefaultHeaderActionsApi';
export { DefaultPluginHeaderActionsApi } from './DefaultPluginHeaderActionsApi';
@@ -16,27 +16,27 @@
import {
coreExtensionData,
headerActionsApiRef,
pluginHeaderActionsApiRef,
createExtensionInput,
ApiBlueprint,
} from '@backstage/frontend-plugin-api';
import { DefaultHeaderActionsApi } from '../apis/HeaderActionsApi';
import { DefaultPluginHeaderActionsApi } from '../apis/PluginHeaderActionsApi';
/**
* Contains the plugin-scoped header actions installed into the app.
*/
export const HeaderActionsApi = ApiBlueprint.makeWithOverrides({
name: 'header-actions',
export const PluginHeaderActionsApi = ApiBlueprint.makeWithOverrides({
name: 'plugin-header-actions',
inputs: {
actions: createExtensionInput([coreExtensionData.reactElement]),
},
factory: (originalFactory, { inputs }) => {
return originalFactory(defineParams =>
defineParams({
api: headerActionsApiRef,
api: pluginHeaderActionsApiRef,
deps: {},
factory: () => {
return DefaultHeaderActionsApi.fromActions(
return DefaultPluginHeaderActionsApi.fromActions(
inputs.actions.map(actionInput => ({
element: actionInput.get(coreExtensionData.reactElement),
pluginId: actionInput.node.spec.plugin.pluginId,
+1 -1
View File
@@ -38,4 +38,4 @@ export {
PageLayout,
} from './components';
export { PluginWrapperApi } from './PluginWrapperApi';
export { HeaderActionsApi } from './HeaderActionsApi';
export { PluginHeaderActionsApi } from './PluginHeaderActionsApi';
+2 -2
View File
@@ -29,7 +29,7 @@ import {
IconsApi,
FeatureFlagsApi,
PluginWrapperApi,
HeaderActionsApi,
PluginHeaderActionsApi,
TranslationsApi,
oauthRequestDialogAppRootElement,
alertDisplayAppRootElement,
@@ -62,7 +62,7 @@ export const appPlugin = createFrontendPlugin({
IconsApi,
FeatureFlagsApi,
PluginWrapperApi,
HeaderActionsApi,
PluginHeaderActionsApi,
TranslationsApi,
DefaultSignInPage,
oauthRequestDialogAppRootElement,