diff --git a/.changeset/silly-readers-build.md b/.changeset/silly-readers-build.md
new file mode 100644
index 0000000000..4797c2b036
--- /dev/null
+++ b/.changeset/silly-readers-build.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder': patch
+---
+
+Add an actions filter on the list actions page and drawer.
diff --git a/plugins/scaffolder/report-alpha.api.md b/plugins/scaffolder/report-alpha.api.md
index 8d0abc262f..8d581cacba 100644
--- a/plugins/scaffolder/report-alpha.api.md
+++ b/plugins/scaffolder/report-alpha.api.md
@@ -235,6 +235,7 @@ export const scaffolderTranslationRef: TranslationRef<
readonly 'actionsPage.content.tableCell.name': 'Name';
readonly 'actionsPage.content.tableCell.title': 'Title';
readonly 'actionsPage.content.tableCell.description': 'Description';
+ readonly 'actionsPage.content.searchFieldPlaceholder': 'Search for an action';
readonly 'actionsPage.content.noRowsDescription': 'No schema defined';
readonly 'actionsPage.title': 'Installed actions';
readonly 'actionsPage.action.input': 'Input';
diff --git a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx
index b548bcbad4..bf056dd3b3 100644
--- a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx
+++ b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx
@@ -509,4 +509,81 @@ describe('TemplatePage', () => {
expect(rendered.getByText('array(unknown)')).toBeInTheDocument();
});
+
+ it('should filter an action', async () => {
+ scaffolderApiMock.listActions.mockResolvedValue([
+ {
+ id: 'githut:repo:create',
+ description: 'Create a new Github repository',
+ schema: {
+ input: {
+ type: 'object',
+ required: ['name'],
+ properties: {
+ name: {
+ title: 'Repository name',
+ type: 'string',
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'githut:repo:push',
+ description: 'Push to a Github repository',
+ schema: {
+ input: {
+ type: 'object',
+ required: ['url'],
+ properties: {
+ url: {
+ title: 'Repository url',
+ type: 'string',
+ },
+ },
+ },
+ },
+ },
+ ]);
+
+ const rendered = await renderInTestApp(
+
+
+ ,
+ {
+ mountedRoutes: {
+ '/create/actions': rootRouteRef,
+ },
+ },
+ );
+
+ expect(
+ rendered.getByRole('heading', { name: 'githut:repo:create' }),
+ ).toBeInTheDocument();
+ expect(
+ rendered.getByRole('heading', { name: 'githut:repo:push' }),
+ ).toBeInTheDocument();
+
+ // should filter actions when searching
+ await userEvent.type(
+ rendered.getByPlaceholderText('Search for an action'),
+ 'create',
+ );
+ await userEvent.keyboard('[ArrowDown][Enter]');
+ expect(
+ rendered.getByRole('heading', { name: 'githut:repo:create' }),
+ ).toBeInTheDocument();
+ expect(
+ rendered.queryByRole('heading', { name: 'githut:repo:push' }),
+ ).not.toBeInTheDocument();
+
+ // should show all actions when clearing the search
+ await userEvent.click(rendered.getByTitle('Clear'));
+ expect(
+ rendered.getByRole('heading', { name: 'githut:repo:create' }),
+ ).toBeInTheDocument();
+ expect(
+ rendered.getByRole('heading', { name: 'githut:repo:push' }),
+ ).toBeInTheDocument();
+ });
});
diff --git a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx
index 0949d948fc..539a5697d3 100644
--- a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx
+++ b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx
@@ -16,6 +16,7 @@
import React, { Fragment, useEffect, useState } from 'react';
import useAsync from 'react-use/esm/useAsync';
import {
+ Action,
ActionExample,
scaffolderApiRef,
} from '@backstage/plugin-scaffolder-react';
@@ -39,6 +40,10 @@ import classNames from 'classnames';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import ExpandLessIcon from '@material-ui/icons/ExpandLess';
import LinkIcon from '@material-ui/icons/Link';
+import Autocomplete from '@material-ui/lab/Autocomplete';
+import TextField from '@material-ui/core/TextField';
+import InputAdornment from '@material-ui/core/InputAdornment';
+import SearchIcon from '@material-ui/icons/Search';
import { useApi, useRouteRef } from '@backstage/core-plugin-api';
import {
@@ -125,14 +130,19 @@ export const ActionPageContent = () => {
const { t } = useTranslationRef(scaffolderTranslationRef);
const classes = useStyles();
- const { loading, value, error } = useAsync(async () => {
+ const {
+ loading,
+ value = [],
+ error,
+ } = useAsync(async () => {
return api.listActions();
}, [api]);
+ const [selectedAction, setSelectedAction] = useState(null);
const [isExpanded, setIsExpanded] = useState<{ [key: string]: boolean }>({});
useEffect(() => {
- if (value && window.location.hash) {
+ if (value.length && window.location.hash) {
document.querySelector(window.location.hash)?.scrollIntoView();
}
}, [value]);
@@ -295,71 +305,107 @@ export const ActionPageContent = () => {
);
};
- return value?.map(action => {
- if (action.id.startsWith('legacy:')) {
- return undefined;
- }
-
- const oneOf = renderTables(
- 'oneOf',
- `${action.id}.input`,
- action.schema?.input?.oneOf,
- );
- return (
-
-
- {action.id}
-
-
-
-
- {action.description && }
- {action.schema?.input && (
-
-
- {t('actionsPage.action.input')}
-
- {renderTable(
- formatRows(`${action.id}.input`, action?.schema?.input),
- )}
- {oneOf}
-
- )}
- {action.schema?.output && (
-
-
- {t('actionsPage.action.output')}
-
- {renderTable(
- formatRows(`${action.id}.output`, action?.schema?.output),
- )}
-
- )}
- {action.examples && (
-
- }>
-
- {t('actionsPage.action.examples')}
-
-
-
-
-
-
-
-
- )}
+ return (
+ <>
+
+ option.id}
+ renderInput={params => (
+
+
+
+ ),
+ }}
+ />
+ )}
+ onChange={(_event, option) => {
+ setSelectedAction(option);
+ }}
+ fullWidth
+ />
- );
- });
+ {(selectedAction ? [selectedAction] : value).map(action => {
+ if (action.id.startsWith('legacy:')) {
+ return undefined;
+ }
+
+ const oneOf = renderTables(
+ 'oneOf',
+ `${action.id}.input`,
+ action.schema?.input?.oneOf,
+ );
+ return (
+
+
+
+ {action.id}
+
+
+
+
+
+ {action.description && (
+
+ )}
+ {action.schema?.input && (
+
+
+ {t('actionsPage.action.input')}
+
+ {renderTable(
+ formatRows(`${action.id}.input`, action?.schema?.input),
+ )}
+ {oneOf}
+
+ )}
+ {action.schema?.output && (
+
+
+ {t('actionsPage.action.output')}
+
+ {renderTable(
+ formatRows(`${action.id}.output`, action?.schema?.output),
+ )}
+
+ )}
+ {action.examples && (
+
+ }>
+
+ {t('actionsPage.action.examples')}
+
+
+
+
+
+
+
+
+ )}
+
+ );
+ })}
+ >
+ );
};
export const ActionsPage = () => {
const navigate = useNavigate();
diff --git a/plugins/scaffolder/src/translation.ts b/plugins/scaffolder/src/translation.ts
index 4399678948..ba59b90c38 100644
--- a/plugins/scaffolder/src/translation.ts
+++ b/plugins/scaffolder/src/translation.ts
@@ -29,6 +29,7 @@ export const scaffolderTranslationRef = createTranslationRef({
description:
'There are no actions installed or there was an issue communicating with backend.',
},
+ searchFieldPlaceholder: 'Search for an action',
tableCell: {
name: 'Name',
title: 'Title',