Merge branch 'master' into mob/scaffolder-frontend
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -24,5 +24,6 @@ export {
|
||||
catalogPlugin as plugin,
|
||||
CatalogIndexPage,
|
||||
CatalogEntityPage,
|
||||
EntityAboutCard,
|
||||
EntityLinksCard,
|
||||
} from './plugin';
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user