Merge branch 'master' into mob/scaffolder-frontend

This commit is contained in:
Johan Haals
2021-02-15 13:19:06 +01:00
78 changed files with 1515 additions and 378 deletions
@@ -39,7 +39,7 @@ type Options = {
const parseDate = (date: string | Date) => {
const parsedDate =
typeof date === 'string'
? DateTime.fromSQL(date, { locale: 'UTC' })
? DateTime.fromSQL(date, { zone: 'UTC' })
: DateTime.fromJSDate(date);
if (!parsedDate.isValid) {
@@ -14,8 +14,9 @@
* limitations under the License.
*/
import React from 'react';
import { EntityProvider } from '@backstage/plugin-catalog-react';
import { render } from '@testing-library/react';
import React from 'react';
import { AboutCard } from './AboutCard';
describe('<AboutCard /> GitHub', () => {
@@ -36,7 +37,11 @@ describe('<AboutCard /> GitHub', () => {
lifecycle: 'production',
},
};
const { getByText } = render(<AboutCard entity={entity} />);
const { getByText } = render(
<EntityProvider entity={entity}>
<AboutCard />
</EntityProvider>,
);
expect(getByText('service')).toBeInTheDocument();
expect(getByText('View Source').closest('a')).toHaveAttribute(
'href',
@@ -67,7 +72,11 @@ describe('<AboutCard /> GitLab', () => {
lifecycle: 'production',
},
};
const { getByText } = render(<AboutCard entity={entity} />);
const { getByText } = render(
<EntityProvider entity={entity}>
<AboutCard />
</EntityProvider>,
);
expect(getByText('service')).toBeInTheDocument();
expect(getByText('View Source').closest('a')).toHaveAttribute(
'href',
@@ -98,7 +107,11 @@ describe('<AboutCard /> BitBucket', () => {
lifecycle: 'production',
},
};
const { getByText } = render(<AboutCard entity={entity} />);
const { getByText } = render(
<EntityProvider entity={entity}>
<AboutCard />
</EntityProvider>,
);
expect(getByText('service')).toBeInTheDocument();
expect(getByText('View Source').closest('a')).toHaveAttribute(
'href',
@@ -19,6 +19,8 @@ import {
ENTITY_DEFAULT_NAMESPACE,
RELATION_PROVIDES_API,
} from '@backstage/catalog-model';
import { HeaderIconLinkRow } from '@backstage/core';
import { useEntity } from '@backstage/plugin-catalog-react';
import {
Card,
CardContent,
@@ -27,14 +29,13 @@ import {
IconButton,
makeStyles,
} from '@material-ui/core';
import ExtensionIcon from '@material-ui/icons/Extension';
import DocsIcon from '@material-ui/icons/Description';
import EditIcon from '@material-ui/icons/Edit';
import ExtensionIcon from '@material-ui/icons/Extension';
import GitHubIcon from '@material-ui/icons/GitHub';
import React from 'react';
import { findLocationForEntityMeta } from '../../data/utils';
import { createEditLink, determineUrlType } from '../createEditLink';
import { HeaderIconLinkRow } from '@backstage/core';
import { AboutContent } from './AboutContent';
const useStyles = makeStyles({
@@ -76,12 +77,14 @@ function getCodeLinkInfo(entity: Entity): CodeLinkInfo {
}
type AboutCardProps = {
entity: Entity;
/** @deprecated The entity is now grabbed from context instead */
entity?: Entity;
variant?: string;
};
export function AboutCard({ entity, variant }: AboutCardProps) {
export function AboutCard({ variant }: AboutCardProps) {
const classes = useStyles();
const { entity } = useEntity();
const codeLink = getCodeLinkInfo(entity);
// TODO: Also support RELATION_CONSUMES_API here
const hasApis = entity.relations?.some(r => r.type === RELATION_PROVIDES_API);
@@ -66,7 +66,7 @@ export const AboutContent = ({ entity }: Props) => {
</Typography>
</AboutField>
<AboutField label="Owner" gridSizes={{ xs: 12, sm: 6, lg: 4 }}>
<EntityRefLinks entityRefs={ownedByRelations} />
<EntityRefLinks entityRefs={ownedByRelations} defaultKind="group" />
</AboutField>
{isSystem && (
<AboutField
@@ -25,7 +25,6 @@ import {
import { makeStyles } from '@material-ui/core/styles';
import Cancel from '@material-ui/icons/Cancel';
import MoreVert from '@material-ui/icons/MoreVert';
import SwapHoriz from '@material-ui/icons/SwapHoriz';
import React, { useState } from 'react';
// TODO(freben): It should probably instead be the case that Header sets the theme text color to white inside itself unconditionally instead
@@ -82,12 +81,6 @@ export const EntityContextMenu = ({ onUnregisterEntity }: Props) => {
</ListItemIcon>
<Typography variant="inherit">Unregister entity</Typography>
</MenuItem>
<MenuItem>
<ListItemIcon>
<SwapHoriz fontSize="small" />
</ListItemIcon>
<Typography variant="inherit">Move repository</Typography>
</MenuItem>
</MenuList>
</Popover>
</>
@@ -15,7 +15,7 @@
*/
import { Entity } from '@backstage/catalog-model';
import { IconComponent, InfoCard, useApp } from '@backstage/core';
import { IconComponent, IconKey, InfoCard, useApp } from '@backstage/core';
import { useEntity } from '@backstage/plugin-catalog-react';
import LanguageIcon from '@material-ui/icons/Language';
import React from 'react';
@@ -33,9 +33,8 @@ export const EntityLinksCard = ({ cols = undefined }: Props) => {
const { entity } = useEntity();
const app = useApp();
// TODO: Refactor App.icons & App.getSystemIcon to support custom icons
const iconResolver = (key: string | undefined): IconComponent => {
return app.getSystemIcon(key as any) ?? LanguageIcon;
const iconResolver = (key: IconKey | undefined): IconComponent => {
return app.getSystemIcon(key ?? '') ?? LanguageIcon;
};
const links = entity?.metadata?.links;
+1
View File
@@ -24,5 +24,6 @@ export {
catalogPlugin as plugin,
CatalogIndexPage,
CatalogEntityPage,
EntityAboutCard,
EntityLinksCard,
} from './plugin';
+8
View File
@@ -71,6 +71,14 @@ export const CatalogEntityPage = catalogPlugin.provide(
}),
);
export const EntityAboutCard = catalogPlugin.provide(
createComponentExtension({
component: {
lazy: () => import('./components/AboutCard').then(m => m.AboutCard),
},
}),
);
export const EntityLinksCard = catalogPlugin.provide(
createComponentExtension({
component: {
@@ -43,7 +43,7 @@ export class BitbucketPreparer implements PreparerBase {
const targetPath = path.join(workspacePath, 'template');
const fullPathToTemplate = path.resolve(
checkoutPath,
parsedGitUrl.filepath,
parsedGitUrl.filepath ?? '',
);
const git = Git.fromAuth({ logger, ...this.getAuth() });
@@ -28,7 +28,7 @@ describe('File preparer', () => {
const preparer = new FilePreparer();
const root = os.platform() === 'win32' ? 'C:\\' : '/';
const workspacePath = path.join(root, 'tmp');
const checkoutPath = path.resolve(workspacePath, 'checkout');
const targetPath = path.resolve(workspacePath, 'template');
await preparer.prepare({
url: `file:///${root}path/to/template`,
@@ -37,12 +37,12 @@ describe('File preparer', () => {
});
expect(fs.copy).toHaveBeenCalledWith(
path.join(root, 'path', 'to', 'template'),
checkoutPath,
targetPath,
{
recursive: true,
},
);
expect(fs.ensureDir).toHaveBeenCalledWith(checkoutPath);
expect(fs.ensureDir).toHaveBeenCalledWith(targetPath);
await expect(
preparer.prepare({
@@ -25,12 +25,12 @@ export class FilePreparer implements PreparerBase {
throw new InputError(`Wrong location protocol, should be 'file', ${url}`);
}
const checkoutDir = path.join(workspacePath, 'checkout');
await fs.ensureDir(checkoutDir);
const targetDir = path.join(workspacePath, 'template');
await fs.ensureDir(targetDir);
const templatePath = fileURLToPath(url);
await fs.copy(templatePath, checkoutDir, {
await fs.copy(templatePath, targetDir, {
recursive: true,
});
}
@@ -33,7 +33,7 @@ export class GithubPreparer implements PreparerBase {
const targetPath = path.join(workspacePath, 'template');
const fullPathToTemplate = path.resolve(
checkoutPath,
parsedGitUrl.filepath,
parsedGitUrl.filepath ?? '',
);
const git = this.config.token
@@ -33,7 +33,7 @@ export class GitlabPreparer implements PreparerBase {
const targetPath = path.join(workspacePath, 'template');
const fullPathToTemplate = path.resolve(
checkoutPath,
parsedGitUrl.filepath,
parsedGitUrl.filepath ?? '',
);
parsedGitUrl.git_suffix = true;
@@ -17,7 +17,7 @@
import { Config } from '@backstage/config';
import Docker from 'dockerode';
import express from 'express';
import { resolve as resolvePath } from 'path';
import { resolve as resolvePath, dirname } from 'path';
import Router from 'express-promise-router';
import { Logger } from 'winston';
import {
@@ -171,7 +171,7 @@ export async function createRouter(
const preparer = new FilePreparer();
const path = resolvePath(
templateEntityLocation,
dirname(templateEntityLocation),
template.spec.path || '.',
);
@@ -17,6 +17,8 @@ import { Button, useRouteRef } from '@backstage/core';
import { BackstageTheme, pageTheme } from '@backstage/theme';
import {
Card,
CardActions,
CardContent,
Chip,
makeStyles,
Typography,
@@ -33,18 +35,11 @@ const useStyles = makeStyles(theme => ({
props.backgroundImage,
backgroundPosition: 0,
},
content: {
padding: theme.spacing(2),
},
description: {
height: 175,
overflow: 'hidden',
textOverflow: 'ellipsis',
},
footer: {
display: 'flex',
flexDirection: 'row-reverse',
},
}));
export type TemplateCardProps = {
@@ -75,19 +70,19 @@ export const TemplateCard = ({
<Typography variant="subtitle2">{type}</Typography>
<Typography variant="h6">{title}</Typography>
</div>
<div className={classes.content}>
<CardContent>
{tags?.map(tag => (
<Chip label={tag} key={tag} />
))}
<Typography variant="body2" paragraph className={classes.description}>
{description}
</Typography>
<div className={classes.footer}>
<Button color="primary" to={templateLink({ templateName: name })}>
Choose
</Button>
</div>
</div>
</CardContent>
<CardActions>
<Button color="primary" to={templateLink({ templateName: name })}>
Choose
</Button>
</CardActions>
</Card>
);
};
@@ -98,7 +98,7 @@ describe('TemplatePage', () => {
</ApiProvider>,
);
expect(rendered.queryByText('Create a new component')).toBeInTheDocument();
expect(rendered.queryByText('Create a New Component')).toBeInTheDocument();
expect(rendered.queryByText('React SSR Template')).toBeInTheDocument();
// await act(async () => await mutate('templates/test'));
});
@@ -115,7 +115,7 @@ describe('TemplatePage', () => {
</ApiProvider>,
);
expect(rendered.queryByText('Create a new component')).toBeInTheDocument();
expect(rendered.queryByText('Create a New Component')).toBeInTheDocument();
expect(rendered.queryByTestId('loading-progress')).toBeInTheDocument();
// Need to cleanup the promise or will timeout
act(() => {
@@ -140,7 +140,7 @@ describe('TemplatePage', () => {
);
expect(
rendered.queryByText('Create a new component'),
rendered.queryByText('Create a New Component'),
).not.toBeInTheDocument();
expect(rendered.queryByText('This is root')).toBeInTheDocument();
});
@@ -117,10 +117,10 @@ export const TemplatePage = () => {
return (
<Page themeId="home">
<Header
pageTitleOverride="Create a new component"
pageTitleOverride="Create a New Component"
title={
<>
Create a new component <Lifecycle alpha shorthand />
Create a New Component <Lifecycle alpha shorthand />
</>
}
subtitle="Create new software components using standard templates"
@@ -14,39 +14,35 @@
* limitations under the License.
*/
import React, { useState } from 'react';
import { Content, Header, HeaderTabs, Page } from '@backstage/core';
import { General } from './General';
import { Header, Page, TabbedLayout } from '@backstage/core';
import React from 'react';
import { AuthProviders } from './AuthProviders';
import { FeatureFlags } from './FeatureFlags';
import { General } from './General';
type Props = {
providerSettings?: JSX.Element;
};
export const SettingsPage = ({ providerSettings }: Props) => {
const [activeTab, setActiveTab] = useState<number>(0);
const onTabChange = (index: number) => {
setActiveTab(index);
};
const tabs = [
{ id: 'general', label: 'General' },
{ id: 'auth-providers', label: 'Authentication Providers' },
{ id: 'feature-flags', label: 'Feature Flags' },
];
const content = [
<General />,
<AuthProviders providerSettings={providerSettings} />,
<FeatureFlags />,
];
return (
<Page themeId="home">
<Header title="Settings" />
<HeaderTabs tabs={tabs} onChange={onTabChange} />
<Content>{content[activeTab]}</Content>
<TabbedLayout>
<TabbedLayout.Route path="general" title="General">
<General />
</TabbedLayout.Route>
<TabbedLayout.Route
path="auth-providers"
title="Authentication Providers"
>
<AuthProviders providerSettings={providerSettings} />
</TabbedLayout.Route>
<TabbedLayout.Route path="feature-flags" title="Feature Flags">
<FeatureFlags />
</TabbedLayout.Route>
</TabbedLayout>
</Page>
);
};