Merge branch 'scaffolder/repo-url-picker-cleanup'

This commit is contained in:
Benjamin Janssens
2024-07-23 11:02:50 +02:00
872 changed files with 16284 additions and 3527 deletions
+46
View File
@@ -1,5 +1,51 @@
# @backstage/plugin-scaffolder
## 1.23.0
### Minor Changes
- 52b6db0: Use virtualization with `EntityPicker` as done earlier with `MultiEntityPicker` to fix performance issues with large data sets. `VirtualizedListbox` extracted into reusable component.
- 3583ce5: Use virtualization with `MultiEntityPicker`. Fixes performance issues with large data sets.
- b5deed0: Add support for `bitbucketCloud` autocomplete in `RepoUrlPicker`
### Patch Changes
- 4d7e11f: enable resizing of the task log stream viewer
- 661b354: Fixed a bug where the `RepoUrlPicker` would still require the `owner` field for `azure`
- cc81579: Updated dependency `@rjsf/utils` to `5.18.5`.
Updated dependency `@rjsf/core` to `5.18.5`.
Updated dependency `@rjsf/material-ui` to `5.18.5`.
Updated dependency `@rjsf/validator-ajv8` to `5.18.5`.
- 89c44b3: Support `catalogFilter` array on `OwnedEntityPicker`
- Updated dependencies
- @backstage/core-components@0.14.9
- @backstage/integration@1.13.0
- @backstage/plugin-catalog-react@1.12.2
- @backstage/plugin-scaffolder-react@1.10.0
- @backstage/plugin-permission-react@0.4.24
- @backstage/plugin-catalog-common@1.0.25
- @backstage/plugin-scaffolder-common@1.5.4
- @backstage/frontend-plugin-api@0.6.7
- @backstage/integration-react@1.1.29
- @backstage/catalog-client@1.6.5
- @backstage/catalog-model@1.5.0
- @backstage/core-compat-api@0.2.7
- @backstage/core-plugin-api@1.9.3
- @backstage/errors@1.2.4
- @backstage/types@1.1.1
## 1.22.1-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.14.9-next.1
- @backstage/frontend-plugin-api@0.6.7-next.1
- @backstage/integration-react@1.1.29-next.0
- @backstage/plugin-catalog-react@1.12.2-next.2
- @backstage/plugin-scaffolder-react@1.10.0-next.2
- @backstage/core-compat-api@0.2.7-next.1
## 1.22.1-next.1
### Patch Changes
+3 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder",
"version": "1.22.1-next.1",
"version": "1.23.0",
"description": "The Backstage plugin that helps you create new things",
"backstage": {
"role": "frontend-plugin",
@@ -96,6 +96,7 @@
"lodash": "^4.17.21",
"luxon": "^3.0.0",
"qs": "^6.9.4",
"react-resizable": "^3.0.5",
"react-use": "^17.2.4",
"react-window": "^1.8.10",
"yaml": "^2.0.0",
@@ -116,6 +117,7 @@
"@testing-library/user-event": "^14.0.0",
"@types/humanize-duration": "^3.18.1",
"@types/json-schema": "^7.0.9",
"@types/react-resizable": "^3.0.8",
"@types/react-window": "^1.8.8",
"msw": "^1.0.0",
"swr": "^2.0.0"
+2 -2
View File
@@ -16,7 +16,7 @@
import { ConfigReader } from '@backstage/core-app-api';
import { ScmIntegrations } from '@backstage/integration';
import { MockFetchApi, setupRequestMockHandlers } from '@backstage/test-utils';
import { MockFetchApi, registerMswTestHooks } from '@backstage/test-utils';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { ScaffolderClient } from './api';
@@ -30,7 +30,7 @@ const mockFetchEventSource = fetchEventSource as jest.MockedFunction<
const server = setupServer();
describe('api', () => {
setupRequestMockHandlers(server);
registerMswTestHooks(server);
const mockBaseUrl = 'http://backstage/api';
const discoveryApi = { getBaseUrl: async () => mockBaseUrl };
@@ -20,6 +20,7 @@ import Box from '@material-ui/core/Box';
import Button from '@material-ui/core/Button';
import Paper from '@material-ui/core/Paper';
import { makeStyles } from '@material-ui/core/styles';
import { ResizableBox } from 'react-resizable';
import {
ScaffolderTaskOutput,
scaffolderApiRef,
@@ -253,13 +254,13 @@ export const OngoingTask = (props: {
) : null}
{logsVisible ? (
<Box paddingBottom={2} height="100%">
<ResizableBox height={240} minConstraints={[0, 160]} axis="y">
<Paper style={{ height: '100%' }}>
<Box padding={2} height="100%">
<TaskLogStream logs={taskStream.stepLogs} />
</Box>
</Paper>
</Box>
</ResizableBox>
) : null}
</Content>
</Page>
@@ -33,6 +33,7 @@ import {
ScaffolderRJSFField,
} from '@backstage/plugin-scaffolder-react';
import { act, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
describe('RepoUrlPicker', () => {
const mockScaffolderApi: Partial<ScaffolderApi> = {
@@ -311,7 +312,7 @@ describe('RepoUrlPicker', () => {
});
});
it('should not call the scmAuthApi if secret is available in the state', async () => {
it('should call the scmAuthApi with the new host when the host is changed', async () => {
const secretsKey = 'testKey';
const SecretsComponent = () => {
@@ -319,7 +320,22 @@ describe('RepoUrlPicker', () => {
const secret = secrets[secretsKey];
return secret ? <div>{secret}</div> : null;
};
const { getByText } = await renderInTestApp(
const allowedHosts = ['github.com', 'gitlab.example.com'];
(mockScmAuthApi.getCredentials as jest.Mock).mockImplementation(
({ url }) => {
let token = '';
if (url === `https://${allowedHosts[0]}`) {
token = 'abc123';
} else if (url === `https://${allowedHosts[1]}`) {
token = 'def456';
}
return Promise.resolve({ token });
},
);
const secondHost = allowedHosts[1];
const { getAllByRole, getByText } = await renderInTestApp(
<TestApiProvider
apis={[
[scmIntegrationsApiRef, mockIntegrationsApi],
@@ -327,16 +343,16 @@ describe('RepoUrlPicker', () => {
[scaffolderApiRef, mockScaffolderApi],
]}
>
<SecretsContextProvider initialSecrets={{ [secretsKey]: 'abc123' }}>
<SecretsContextProvider>
<Form
validator={validator}
schema={{ type: 'string' }}
uiSchema={{
'ui:field': 'RepoUrlPicker',
'ui:options': {
allowedHosts,
requestUserCredentials: {
secretsKey,
additionalScopes: { github: ['workflow'] },
},
},
}}
@@ -350,14 +366,26 @@ describe('RepoUrlPicker', () => {
);
await act(async () => {
// need to wait for the debounce to finish to fetch credentials for the first selected host
await new Promise(resolve => setTimeout(resolve, 600));
});
expect(getByText('abc123')).toBeInTheDocument();
await act(async () => {
// Select the second host
const hostInput = getAllByRole('combobox')[0];
await userEvent.selectOptions(hostInput, secondHost);
// need to wait for the debounce to finish
await new Promise(resolve => setTimeout(resolve, 600));
});
// as we already have a secret in the state, getCredentials should not be called again.
expect(mockScmAuthApi.getCredentials).toHaveBeenCalledTimes(0);
expect(getByText('abc123')).toBeInTheDocument();
expect(getByText('def456')).toBeInTheDocument();
expect(mockScmAuthApi.getCredentials).toHaveBeenCalledWith({
url: `https://${secondHost}`,
additionalScope: {
repoWrite: true,
},
});
});
});
});
@@ -49,6 +49,9 @@ export const RepoUrlPicker = (props: RepoUrlPickerProps) => {
const [state, setState] = useState<RepoUrlPickerState>(
parseRepoPickerUrl(formData),
);
const [credentialsHost, setCredentialsHost] = useState<string | undefined>(
undefined,
);
const integrationApi = useApi(scmIntegrationsApiRef);
const scmAuthApi = useApi(scmAuthApiRef);
const { secrets, setSecrets } = useTemplateSecrets();
@@ -128,8 +131,11 @@ export const RepoUrlPicker = (props: RepoUrlPickerProps) => {
return;
}
// don't show login prompt if secret value is already in state
if (secrets[requestUserCredentials.secretsKey]) {
// don't show login prompt if secret value is already in state for selected host
if (
secrets[requestUserCredentials.secretsKey] &&
credentialsHost === state.host
) {
return;
}
@@ -147,6 +153,7 @@ export const RepoUrlPicker = (props: RepoUrlPickerProps) => {
// set the secret using the key provided in the ui:options for use
// in the templating the manifest with ${{ secrets[secretsKey] }}
setSecrets({ [requestUserCredentials.secretsKey]: token });
setCredentialsHost(state.host);
},
500,
[state, uiSchema],