>Merge branch 'master' of github.com:backstage/backstage into ndudnik/fix-component-creation-modal

* 'master' of github.com:backstage/backstage: (888 commits)
  Feat: update Firebase logo
  register-component: Remove link to catalog item on validation popup (#3359)
  changeset
  fix icon configuration
  Update Governance
  Feat: Add Buildkite plugin, update plugin logotypes
  chore: fixing issue with resolutions for new versions of backstage
  Version Packages
  Add changeset
  Use the new catalogInfoUrl in TemplatePage
  Generate catatalog-info.yaml URL in the publishers
  Start using the extracted types
  Extract types
  fix(search): return EmptyState/Progress bar/Alert depending on result of api call (#3350)
  fix: runned prettier
  Replace broken icon for the api-docs plugin
  Feat: update Lighthouse plugin to use new MarkdownComponent (#3328)
  docs: Correct URL of techdocs-container
  fix: conditional getuid and getgid for Windows environment
  create-app: add app-backend plugin to template
  ...
This commit is contained in:
blam
2020-11-20 11:39:26 +01:00
1375 changed files with 37437 additions and 15830 deletions
+98
View File
@@ -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:
![successfully-created-component](https://user-images.githubusercontent.com/33940798/94339294-8bd1e000-0016-11eb-885b-7936fcc23b63.gif)
### If the component creation failed:
![failed-to-create-component](https://user-images.githubusercontent.com/33940798/94339296-90969400-0016-11eb-9a74-ce16b3dd8d88.gif)
- 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
+1 -1
View File
@@ -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)
+12 -13
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder",
"version": "0.1.1-alpha.24",
"version": "0.3.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,15 +21,15 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.1.1-alpha.24",
"@backstage/core": "^0.1.1-alpha.24",
"@backstage/plugin-catalog": "^0.1.1-alpha.24",
"@backstage/theme": "^0.1.1-alpha.24",
"@backstage/catalog-model": "^0.2.0",
"@backstage/core": "^0.3.1",
"@backstage/plugin-catalog": "^0.2.2",
"@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",
"@rjsf/core": "^2.1.0",
"@rjsf/material-ui": "^2.1.0",
"@rjsf/core": "^2.4.0",
"@rjsf/material-ui": "^2.4.0",
"classnames": "^2.2.6",
"moment": "^2.26.0",
"react": "^16.13.1",
@@ -41,17 +41,16 @@
"swr": "^0.3.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.24",
"@backstage/dev-utils": "^0.1.1-alpha.24",
"@backstage/test-utils": "^0.1.1-alpha.24",
"@backstage/cli": "^0.3.0",
"@backstage/dev-utils": "^0.1.4",
"@backstage/test-utils": "^0.1.3",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
"@types/jest": "^26.0.7",
"@types/node": "^12.0.0",
"jest-fetch-mock": "^3.0.3",
"msw": "^0.20.5",
"node-fetch": "^2.6.1"
"cross-fetch": "^3.0.6",
"msw": "^0.21.2"
},
"files": [
"dist"
+3 -7
View File
@@ -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) {
@@ -73,6 +73,18 @@ const useStyles = makeStyles(theme => ({
boxShadow: `inset 4px 0px 0px ${theme.palette.success.main}`,
},
},
jobStatusTitle: {
display: 'flex',
width: '100%',
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'space-between',
[theme.breakpoints.down('xs')]: {
flexDirection: 'column',
alignItems: 'flex-start',
justifyContent: 'flex-start',
},
},
}));
type Props = {
@@ -118,14 +130,16 @@ export const JobStage = ({ endedAt, startedAt, name, log, status }: Props) => {
className: classes.button,
}}
>
<Typography variant="button">
<Typography variant="button" className={classes.jobStatusTitle}>
{name} {timeElapsed && `(${timeElapsed})`}{' '}
{startedAt && !endedAt && <CircularProgress size="1em" />}
</Typography>
</AccordionSummary>
<AccordionDetails className={classes.accordionDetails}>
{log.length === 0 ? (
<Box px={4}>No logs available for this step</Box>
<Box px={9} pb={2} width="100%">
No logs available for this step
</Box>
) : (
<Suspense fallback={<LinearProgress />}>
<div style={{ height: '20vh', width: '100%' }}>
@@ -13,21 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useState, useEffect } from 'react';
import {
Dialog,
LinearProgress,
DialogTitle,
DialogContent,
DialogActions,
} from '@material-ui/core';
import { JobStage } from '../JobStage/JobStage';
import { useJobPolling } from './useJobPolling';
import { Job } from '../../types';
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
import { Button } from '@backstage/core';
import { entityRoute } from '@backstage/plugin-catalog';
import { entityRoute, entityRouteParams } from '@backstage/plugin-catalog';
import {
Dialog,
DialogActions,
DialogContent,
DialogTitle,
LinearProgress,
} from '@material-ui/core';
import React, { useEffect, useState } from 'react';
import { generatePath } from 'react-router-dom';
import { Job } from '../../types';
import { JobStage } from '../JobStage/JobStage';
import { useJobPolling } from './useJobPolling';
type Props = {
onComplete: (job: Job) => void;
@@ -69,15 +69,10 @@ export const JobStatusModal = ({ jobId, onComplete, entity }: Props) => {
{entity && (
<DialogActions>
<Button
to={generatePath(`/catalog/${entityRoute.path}`, {
kind: entity.kind,
optionalNamespaceAndName: [
entity.metadata.namespace,
entity.metadata.name,
]
.filter(Boolean)
.join(':'),
})}
to={generatePath(
`/catalog/${entityRoute.path}`,
entityRouteParams(entity),
)}
>
View in catalog
</Button>
@@ -22,7 +22,6 @@ import {
Header,
Lifecycle,
Page,
pageTheme,
Progress,
SupportButton,
useApi,
@@ -54,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(() => {
@@ -66,7 +67,7 @@ export const ScaffolderPage = () => {
}, [error, errorApi]);
return (
<Page theme={pageTheme.home}>
<Page themeId="home">
<Header
pageTitleOverride="Create a New Component"
title={
@@ -13,8 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Button, pageTheme } from '@backstage/core';
import { Card, Chip, makeStyles, Typography } from '@material-ui/core';
import { Button } from '@backstage/core';
import { BackstageTheme, pageTheme } from '@backstage/theme';
import {
Card,
Chip,
makeStyles,
Typography,
useTheme,
} from '@material-ui/core';
import React from 'react';
import { generatePath } from 'react-router-dom';
import { templateRoute } from '../../routes';
@@ -25,6 +32,7 @@ const useStyles = makeStyles(theme => ({
padding: theme.spacing(2, 2, 6),
backgroundImage: (props: { backgroundImage: string }) =>
props.backgroundImage,
backgroundPosition: 0,
},
content: {
padding: theme.spacing(2),
@@ -55,7 +63,10 @@ export const TemplateCard = ({
type,
name,
}: TemplateCardProps) => {
const theme = pageTheme[type] ?? pageTheme.other;
const backstageTheme = useTheme<BackstageTheme>();
const themeId = pageTheme[type] ? type : 'other';
const theme = backstageTheme.getPageTheme({ themeId });
const classes = useStyles({ backgroundImage: theme.backgroundImage });
const href = generatePath(templateRoute.path, { templateName: name });
@@ -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}>
@@ -22,34 +22,31 @@ import {
Lifecycle,
Page,
useApi,
pageTheme,
} from '@backstage/core';
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 +93,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);
@@ -108,15 +105,10 @@ export const TemplatePage = () => {
);
const handleCreateComplete = async (job: Job) => {
const componentYaml = job.metadata.remoteUrl?.replace(
/\.git$/,
'/blob/master/component-info.yaml',
);
if (!componentYaml) {
if (!job.metadata.catalogInfoUrl) {
errorApi.post(
new Error(
`Failed to find component-info.yaml file in ${job.metadata.remoteUrl}.`,
`Failed to find catalog-info.yaml file in ${job.metadata.remoteUrl}.`,
),
);
return;
@@ -124,7 +116,7 @@ export const TemplatePage = () => {
const {
entities: [createdEntity],
} = await catalogApi.addLocation('github', componentYaml);
} = await catalogApi.addLocation({ target: job.metadata.catalogInfoUrl });
setEntity((createdEntity as any) as TemplateEntityV1alpha1);
};
@@ -144,7 +136,7 @@ export const TemplatePage = () => {
}
return (
<Page theme={pageTheme.home}>
<Page themeId="home">
<Header
pageTitleOverride="Create a new component"
title={
-2
View File
@@ -15,5 +15,3 @@
*/
import '@testing-library/jest-dom';
require('jest-fetch-mock').enableMocks();
+1
View File
@@ -19,6 +19,7 @@ export type Job = {
entity: any;
values: any;
remoteUrl?: string;
catalogInfoUrl?: string;
};
status: 'PENDING' | 'STARTED' | 'COMPLETED' | 'FAILED';
stages: Stage[];