Merge branch 'master' into mcalus3/add-catalog-import-plugin
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
# @backstage/plugin-scaffolder
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 59166e5ec: `createRouter` of scaffolder backend will now require additional option as `entityClient` which could be generated by `CatalogEntityClient` in `plugin-scaffolder-backend` package. Here is example to generate `entityClient`.
|
||||
|
||||
```js
|
||||
import { CatalogEntityClient } from '@backstage/plugin-scaffolder-backend';
|
||||
import { SingleHostDiscovery } from '@backstage/backend-common';
|
||||
|
||||
const discovery = SingleHostDiscovery.fromConfig(config);
|
||||
const entityClient = new CatalogEntityClient({ discovery });
|
||||
```
|
||||
|
||||
- Scaffolder's API `/v1/jobs` will accept `templateName` instead of `template` Entity.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [7b37d65fd]
|
||||
- Updated dependencies [4aca74e08]
|
||||
- Updated dependencies [e8f69ba93]
|
||||
- Updated dependencies [0c0798f08]
|
||||
- Updated dependencies [0c0798f08]
|
||||
- Updated dependencies [199237d2f]
|
||||
- Updated dependencies [6627b626f]
|
||||
- Updated dependencies [4577e377b]
|
||||
- Updated dependencies [2d0bd1be7]
|
||||
- @backstage/core@0.3.0
|
||||
- @backstage/theme@0.2.1
|
||||
- @backstage/plugin-catalog@0.2.1
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 28edd7d29: Create backend plugin through CLI
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- fb74f1db6: Make title meaningful after component creation
|
||||
|
||||
Fixes #2458.
|
||||
|
||||
After the change, the UX should look like this:
|
||||
|
||||
### If the component creation was successful:
|
||||
|
||||

|
||||
|
||||
### If the component creation failed:
|
||||
|
||||

|
||||
|
||||
- c5ef12926: fix the accordion details design when job stage fail
|
||||
- 1c8c43756: The new `scaffolder.github.baseUrl` config property allows to specify a custom base url for GitHub enterprise instances
|
||||
- Updated dependencies [28edd7d29]
|
||||
- Updated dependencies [819a70229]
|
||||
- Updated dependencies [3a4236570]
|
||||
- Updated dependencies [ae5983387]
|
||||
- Updated dependencies [0d4459c08]
|
||||
- Updated dependencies [482b6313d]
|
||||
- Updated dependencies [e0be86b6f]
|
||||
- Updated dependencies [f70a52868]
|
||||
- Updated dependencies [12b5fe940]
|
||||
- Updated dependencies [368fd8243]
|
||||
- Updated dependencies [1c60f716e]
|
||||
- Updated dependencies [144c66d50]
|
||||
- Updated dependencies [a768a07fb]
|
||||
- Updated dependencies [b79017fd3]
|
||||
- Updated dependencies [6d97d2d6f]
|
||||
- Updated dependencies [5adfc005e]
|
||||
- Updated dependencies [f0aa01bcc]
|
||||
- Updated dependencies [0aecfded0]
|
||||
- Updated dependencies [93a3fa3ae]
|
||||
- Updated dependencies [782f3b354]
|
||||
- Updated dependencies [8b9c8196f]
|
||||
- Updated dependencies [2713f28f4]
|
||||
- Updated dependencies [406015b0d]
|
||||
- Updated dependencies [82759d3e4]
|
||||
- Updated dependencies [60d40892c]
|
||||
- Updated dependencies [ac8d5d5c7]
|
||||
- Updated dependencies [2ebcfac8d]
|
||||
- Updated dependencies [fa56f4615]
|
||||
- Updated dependencies [ebca83d48]
|
||||
- Updated dependencies [aca79334f]
|
||||
- Updated dependencies [c0d5242a0]
|
||||
- Updated dependencies [b3d57961c]
|
||||
- Updated dependencies [0b956f21b]
|
||||
- Updated dependencies [97c2cb19b]
|
||||
- Updated dependencies [3beb5c9fc]
|
||||
- Updated dependencies [754e31db5]
|
||||
- Updated dependencies [1611c6dbc]
|
||||
- @backstage/plugin-catalog@0.2.0
|
||||
- @backstage/core@0.2.0
|
||||
- @backstage/catalog-model@0.2.0
|
||||
- @backstage/theme@0.2.0
|
||||
@@ -6,5 +6,5 @@ This is the frontend part of the default scaffolder plugin.
|
||||
|
||||
## Links
|
||||
|
||||
- [Backend part of the plugin](https://github.com/spotify/backstage/tree/master/plugins/scaffolder-backend)
|
||||
- [Backend part of the plugin](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend)
|
||||
- [The Backstage homepage](https://backstage.io)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-scaffolder",
|
||||
"version": "0.1.1-alpha.26",
|
||||
"version": "0.3.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -21,10 +21,10 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.26",
|
||||
"@backstage/core": "^0.1.1-alpha.26",
|
||||
"@backstage/plugin-catalog": "^0.1.1-alpha.26",
|
||||
"@backstage/theme": "^0.1.1-alpha.26",
|
||||
"@backstage/catalog-model": "^0.2.0",
|
||||
"@backstage/core": "^0.3.0",
|
||||
"@backstage/plugin-catalog": "^0.2.1",
|
||||
"@backstage/theme": "^0.2.1",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
@@ -41,9 +41,9 @@
|
||||
"swr": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.26",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.26",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.26",
|
||||
"@backstage/cli": "^0.2.0",
|
||||
"@backstage/dev-utils": "^0.1.3",
|
||||
"@backstage/test-utils": "^0.1.2",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
"@testing-library/user-event": "^12.0.7",
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
import { createApiRef, DiscoveryApi } from '@backstage/core';
|
||||
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
|
||||
export const scaffolderApiRef = createApiRef<ScaffolderApi>({
|
||||
id: 'plugin.scaffolder.service',
|
||||
@@ -33,20 +32,17 @@ export class ScaffolderApi {
|
||||
* Executes the scaffolding of a component, given a template and its
|
||||
* parameter values.
|
||||
*
|
||||
* @param template Template entity for the scaffolder to use. New project is going to be created out of this template.
|
||||
* @param templateName Template name for the scaffolder to use. New project is going to be created out of this template.
|
||||
* @param values Parameters for the template, e.g. name, description
|
||||
*/
|
||||
async scaffold(
|
||||
template: TemplateEntityV1alpha1,
|
||||
values: Record<string, any>,
|
||||
) {
|
||||
async scaffold(templateName: string, values: Record<string, any>) {
|
||||
const url = `${await this.discoveryApi.getBaseUrl('scaffolder')}/v1/jobs`;
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ template, values: { ...values } }),
|
||||
body: JSON.stringify({ templateName, values: { ...values } }),
|
||||
});
|
||||
|
||||
if (response.status !== 201) {
|
||||
|
||||
@@ -53,10 +53,12 @@ export const ScaffolderPage = () => {
|
||||
|
||||
const { data: templates, isValidating, error } = useStaleWhileRevalidate(
|
||||
'templates/all',
|
||||
async () =>
|
||||
catalogApi.getEntities({ kind: 'Template' }) as Promise<
|
||||
TemplateEntityV1alpha1[]
|
||||
>,
|
||||
async () => {
|
||||
const response = await catalogApi.getEntities({
|
||||
filter: { kind: 'Template' },
|
||||
});
|
||||
return response.items as TemplateEntityV1alpha1[];
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -13,18 +13,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { TemplatePage } from './TemplatePage';
|
||||
import { wrapInTestApp, renderWithEffects } from '@backstage/test-utils';
|
||||
import { ApiRegistry, errorApiRef, ApiProvider } from '@backstage/core';
|
||||
import { scaffolderApiRef, ScaffolderApi } from '../../api';
|
||||
import { catalogApiRef, CatalogApi } from '@backstage/plugin-catalog';
|
||||
import { mutate } from 'swr';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Route, MemoryRouter } from 'react-router';
|
||||
import { rootRoute } from '../../routes';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import { ApiProvider, ApiRegistry, errorApiRef } from '@backstage/core';
|
||||
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog';
|
||||
import { renderInTestApp, renderWithEffects } from '@backstage/test-utils';
|
||||
import { lightTheme } from '@backstage/theme';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { MemoryRouter, Route } from 'react-router';
|
||||
import { ScaffolderApi, scaffolderApiRef } from '../../api';
|
||||
import { rootRoute } from '../../routes';
|
||||
import { TemplatePage } from './TemplatePage';
|
||||
|
||||
const templateMock = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
@@ -90,48 +89,43 @@ const apis = ApiRegistry.from([
|
||||
]);
|
||||
|
||||
describe('TemplatePage', () => {
|
||||
afterEach(async () => {
|
||||
// Cleaning up swr's cache
|
||||
await act(async () => {
|
||||
await mutate('templates/test');
|
||||
});
|
||||
});
|
||||
beforeEach(() => jest.resetAllMocks());
|
||||
|
||||
it('renders correctly', async () => {
|
||||
catalogApiMock.getEntities.mockResolvedValueOnce([templateMock]);
|
||||
const rendered = await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<TemplatePage />
|
||||
</ApiProvider>,
|
||||
),
|
||||
catalogApiMock.getEntities.mockResolvedValueOnce({ items: [templateMock] });
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<TemplatePage />
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
expect(rendered.queryByText('Create a new component')).toBeInTheDocument();
|
||||
expect(rendered.queryByText('React SSR Template')).toBeInTheDocument();
|
||||
// await act(async () => await mutate('templates/test'));
|
||||
});
|
||||
|
||||
it('renders spinner while loading', async () => {
|
||||
let resolve: Function;
|
||||
const promise = new Promise<any>(res => {
|
||||
resolve = res;
|
||||
});
|
||||
catalogApiMock.getEntities.mockResolvedValueOnce(promise);
|
||||
const rendered = await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<TemplatePage />
|
||||
</ApiProvider>,
|
||||
),
|
||||
catalogApiMock.getEntities.mockReturnValueOnce(promise);
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<TemplatePage />
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
expect(rendered.queryByText('Create a new component')).toBeInTheDocument();
|
||||
expect(rendered.queryByTestId('loading-progress')).toBeInTheDocument();
|
||||
// Need to cleanup the promise or will timeout
|
||||
resolve!();
|
||||
act(() => {
|
||||
resolve!({ items: [] });
|
||||
});
|
||||
});
|
||||
|
||||
it('navigates away if no template was loaded', async () => {
|
||||
catalogApiMock.getEntities.mockResolvedValueOnce([]);
|
||||
catalogApiMock.getEntities.mockResolvedValueOnce({ items: [] });
|
||||
|
||||
const rendered = await renderWithEffects(
|
||||
<ApiProvider apis={apis}>
|
||||
|
||||
@@ -27,28 +27,26 @@ import { catalogApiRef } from '@backstage/plugin-catalog';
|
||||
import { LinearProgress } from '@material-ui/core';
|
||||
import { IChangeEvent } from '@rjsf/core';
|
||||
import React, { useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import useStaleWhileRevalidate from 'swr';
|
||||
import { scaffolderApiRef } from '../../api';
|
||||
import { JobStatusModal } from '../JobStatusModal';
|
||||
import { Job } from '../../types';
|
||||
import { MultistepJsonForm } from '../MultistepJsonForm';
|
||||
import { Navigate } from 'react-router';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useAsync } from 'react-use';
|
||||
import { scaffolderApiRef } from '../../api';
|
||||
import { rootRoute } from '../../routes';
|
||||
import { Job } from '../../types';
|
||||
import { JobStatusModal } from '../JobStatusModal';
|
||||
import { MultistepJsonForm } from '../MultistepJsonForm';
|
||||
|
||||
const useTemplate = (
|
||||
templateName: string,
|
||||
catalogApi: typeof catalogApiRef.T,
|
||||
) => {
|
||||
const { data, error } = useStaleWhileRevalidate(
|
||||
`templates/${templateName}`,
|
||||
async () =>
|
||||
catalogApi.getEntities({
|
||||
kind: 'Template',
|
||||
'metadata.name': templateName,
|
||||
}) as Promise<TemplateEntityV1alpha1[]>,
|
||||
);
|
||||
return { template: data?.[0], loading: !error && !data, error };
|
||||
const { value, loading, error } = useAsync(async () => {
|
||||
const response = await catalogApi.getEntities({
|
||||
filter: { kind: 'Template', 'metadata.name': templateName },
|
||||
});
|
||||
return response.items as TemplateEntityV1alpha1[];
|
||||
});
|
||||
return { template: value?.[0], loading, error };
|
||||
};
|
||||
|
||||
const OWNER_REPO_SCHEMA = {
|
||||
@@ -96,7 +94,7 @@ export const TemplatePage = () => {
|
||||
|
||||
const handleCreate = async () => {
|
||||
try {
|
||||
const job = await scaffolderApi.scaffold(template!, formState);
|
||||
const job = await scaffolderApi.scaffold(templateName, formState);
|
||||
setJobId(job);
|
||||
} catch (e) {
|
||||
errorApi.post(e);
|
||||
@@ -110,7 +108,7 @@ export const TemplatePage = () => {
|
||||
const handleCreateComplete = async (job: Job) => {
|
||||
const target = job.metadata.remoteUrl?.replace(
|
||||
/\.git$/,
|
||||
// TODO(Rugvip): This is not the location we want. As part of scaffodler v2 we
|
||||
// TODO(Rugvip): This is not the location we want. As part of scaffolder v2 we
|
||||
// want this to be more flexible, but before that we might want
|
||||
// to update all templates to use catalog-info.yaml instead.
|
||||
'/blob/master/component-info.yaml',
|
||||
|
||||
Reference in New Issue
Block a user