Merge branch 'scaffolder/repo-url-picker-cleanup'
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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],
|
||||
|
||||
Reference in New Issue
Block a user