Merge remote-tracking branch 'upstream/master' into feat/relative-ref-tmp
This commit is contained in:
@@ -59,15 +59,17 @@ const apiPage = (
|
||||
<EntityLayout>
|
||||
<EntityLayout.Route path="/" title="Overview">
|
||||
<Grid container spacing={3}>
|
||||
<Grid item md={6}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<EntityAboutCard />
|
||||
</Grid>
|
||||
<Grid container item md={12}>
|
||||
<Grid item md={6}>
|
||||
<EntityProvidingComponentsCard />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<EntityConsumingComponentsCard />
|
||||
<Grid container>
|
||||
<Grid item md={12}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<EntityProvidingComponentsCard />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<EntityConsumingComponentsCard />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -52,14 +52,16 @@ const apiDocsPlugin: BackstagePlugin<
|
||||
export { apiDocsPlugin };
|
||||
export { apiDocsPlugin as plugin };
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "ApiExplorerPageProps" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "ApiExplorerPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const ApiExplorerPage: ({
|
||||
initiallySelectedFilter,
|
||||
columns,
|
||||
}: ApiExplorerPageProps) => JSX.Element;
|
||||
}: {
|
||||
initiallySelectedFilter?: UserListFilterKind | undefined;
|
||||
columns?: TableColumn<CatalogTableRow>[] | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "ApiTypeTitle" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Header, Page } from '@backstage/core-components';
|
||||
import { useApi, configApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
type Props = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
export const ApiExplorerLayout = ({ children }: Props) => {
|
||||
const configApi = useApi(configApiRef);
|
||||
const generatedSubtitle = `${
|
||||
configApi.getOptionalString('organization.name') ?? 'Backstage'
|
||||
} API Explorer`;
|
||||
return (
|
||||
<Page themeId="apis">
|
||||
<Header
|
||||
title="APIs"
|
||||
subtitle={generatedSubtitle}
|
||||
pageTitleOverride="APIs"
|
||||
/>
|
||||
{children}
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
@@ -14,6 +14,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
PageWithHeader,
|
||||
SupportButton,
|
||||
TableColumn,
|
||||
} from '@backstage/core-components';
|
||||
import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
CatalogTable,
|
||||
CatalogTableRow,
|
||||
FilteredEntityLayout,
|
||||
EntityListContainer,
|
||||
FilterContainer,
|
||||
} from '@backstage/plugin-catalog';
|
||||
import {
|
||||
EntityKindPicker,
|
||||
EntityLifecyclePicker,
|
||||
@@ -24,29 +39,10 @@ import {
|
||||
UserListFilterKind,
|
||||
UserListPicker,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { CatalogTable, CatalogTableRow } from '@backstage/plugin-catalog';
|
||||
import { Button, makeStyles } from '@material-ui/core';
|
||||
import { Button } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { createComponentRouteRef } from '../../routes';
|
||||
import { ApiExplorerLayout } from './ApiExplorerLayout';
|
||||
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
SupportButton,
|
||||
TableColumn,
|
||||
} from '@backstage/core-components';
|
||||
import { useRouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
contentWrapper: {
|
||||
display: 'grid',
|
||||
gridTemplateAreas: "'filters' 'table'",
|
||||
gridTemplateColumns: '250px 1fr',
|
||||
gridColumnGap: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
|
||||
const defaultColumns: TableColumn<CatalogTableRow>[] = [
|
||||
CatalogTable.columns.createNameColumn({ defaultKind: 'API' }),
|
||||
@@ -58,7 +54,7 @@ const defaultColumns: TableColumn<CatalogTableRow>[] = [
|
||||
CatalogTable.columns.createTagsColumn(),
|
||||
];
|
||||
|
||||
export type ApiExplorerPageProps = {
|
||||
type ApiExplorerPageProps = {
|
||||
initiallySelectedFilter?: UserListFilterKind;
|
||||
columns?: TableColumn<CatalogTableRow>[];
|
||||
};
|
||||
@@ -67,11 +63,19 @@ export const ApiExplorerPage = ({
|
||||
initiallySelectedFilter = 'all',
|
||||
columns,
|
||||
}: ApiExplorerPageProps) => {
|
||||
const styles = useStyles();
|
||||
const createComponentLink = useRouteRef(createComponentRouteRef);
|
||||
const configApi = useApi(configApiRef);
|
||||
const generatedSubtitle = `${
|
||||
configApi.getOptionalString('organization.name') ?? 'Backstage'
|
||||
} API Explorer`;
|
||||
|
||||
return (
|
||||
<ApiExplorerLayout>
|
||||
<PageWithHeader
|
||||
themeId="apis"
|
||||
title="APIs"
|
||||
subtitle={generatedSubtitle}
|
||||
pageTitleOverride="APIs"
|
||||
>
|
||||
<Content>
|
||||
<ContentHeader title="">
|
||||
{createComponentLink && (
|
||||
@@ -86,20 +90,22 @@ export const ApiExplorerPage = ({
|
||||
)}
|
||||
<SupportButton>All your APIs</SupportButton>
|
||||
</ContentHeader>
|
||||
<div className={styles.contentWrapper}>
|
||||
<EntityListProvider>
|
||||
<div>
|
||||
<EntityListProvider>
|
||||
<FilteredEntityLayout>
|
||||
<FilterContainer>
|
||||
<EntityKindPicker initialFilter="api" hidden />
|
||||
<EntityTypePicker />
|
||||
<UserListPicker initialFilter={initiallySelectedFilter} />
|
||||
<EntityOwnerPicker />
|
||||
<EntityLifecyclePicker />
|
||||
<EntityTagPicker />
|
||||
</div>
|
||||
<CatalogTable columns={columns || defaultColumns} />
|
||||
</EntityListProvider>
|
||||
</div>
|
||||
</FilterContainer>
|
||||
<EntityListContainer>
|
||||
<CatalogTable columns={columns || defaultColumns} />
|
||||
</EntityListContainer>
|
||||
</FilteredEntityLayout>
|
||||
</EntityListProvider>
|
||||
</Content>
|
||||
</ApiExplorerLayout>
|
||||
</PageWithHeader>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -346,6 +346,16 @@ export interface RouterOptions {
|
||||
providerFactories?: ProviderFactories;
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "TokenIssuer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
export type TokenIssuer = {
|
||||
issueToken(params: TokenParams): Promise<string>;
|
||||
listPublicKeys(): Promise<{
|
||||
keys: AnyJWK[];
|
||||
}>;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "verifyNonce" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
@@ -366,9 +376,10 @@ export type WebMessageResponse =
|
||||
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
// src/identity/types.d.ts:25:5 - (ae-forgotten-export) The symbol "TokenParams" needs to be exported by the entry point index.d.ts
|
||||
// src/identity/types.d.ts:31:9 - (ae-forgotten-export) The symbol "AnyJWK" needs to be exported by the entry point index.d.ts
|
||||
// src/providers/google/provider.d.ts:36:5 - (ae-forgotten-export) The symbol "AuthHandler" needs to be exported by the entry point index.d.ts
|
||||
// src/providers/types.d.ts:105:5 - (ae-forgotten-export) The symbol "AuthProviderConfig" needs to be exported by the entry point index.d.ts
|
||||
// src/providers/types.d.ts:108:5 - (ae-forgotten-export) The symbol "TokenIssuer" needs to be exported by the entry point index.d.ts
|
||||
// src/providers/types.d.ts:111:5 - (ae-forgotten-export) The symbol "ExperimentalIdentityResolver" needs to be exported by the entry point index.d.ts
|
||||
// src/providers/types.d.ts:128:8 - (tsdoc-missing-deprecation-message) The @deprecated block must include a deprecation message, e.g. describing the recommended alternative
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
export * from './service/router';
|
||||
export { IdentityClient } from './identity';
|
||||
export type { TokenIssuer } from './identity';
|
||||
export * from './providers';
|
||||
|
||||
// flow package provides 2 functions
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Grid, Typography } from '@material-ui/core';
|
||||
import { Chip, Grid, Typography } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { ImportStepper } from './ImportStepper';
|
||||
import { StepperProviderOpts } from './ImportStepper/defaults';
|
||||
|
||||
import { ConfigApi, configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
@@ -29,30 +29,12 @@ import {
|
||||
SupportButton,
|
||||
} from '@backstage/core-components';
|
||||
|
||||
function repositories(configApi: ConfigApi): string[] {
|
||||
const integrations = configApi.getConfig('integrations');
|
||||
const repos = [];
|
||||
if (integrations.has('github')) {
|
||||
repos.push('GitHub');
|
||||
}
|
||||
if (integrations.has('bitbucket')) {
|
||||
repos.push('Bitbucket');
|
||||
}
|
||||
if (integrations.has('gitlab')) {
|
||||
repos.push('GitLab');
|
||||
}
|
||||
if (integrations.has('azure')) {
|
||||
repos.push('Azure');
|
||||
}
|
||||
return repos;
|
||||
}
|
||||
|
||||
export const ImportComponentPage = (opts: StepperProviderOpts) => {
|
||||
const configApi = useApi(configApiRef);
|
||||
const appTitle = configApi.getOptional('app.title') || 'Backstage';
|
||||
|
||||
const repos = repositories(configApi);
|
||||
const repositoryString = repos.join(', ').replace(/, (\w*)$/, ' or $1');
|
||||
const integrations = configApi.getConfig('integrations');
|
||||
const hasGithubIntegration = integrations.has('github');
|
||||
|
||||
return (
|
||||
<Page themeId="home">
|
||||
@@ -76,7 +58,8 @@ export const ImportComponentPage = (opts: StepperProviderOpts) => {
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" paragraph>
|
||||
Enter the URL to your SCM repository to add it to {appTitle}.
|
||||
Enter the URL to your source code repository to add it to{' '}
|
||||
{appTitle}.
|
||||
</Typography>
|
||||
<Typography variant="h6">
|
||||
Link to an existing entity file
|
||||
@@ -91,10 +74,11 @@ export const ImportComponentPage = (opts: StepperProviderOpts) => {
|
||||
The wizard analyzes the file, previews the entities, and adds
|
||||
them to the {appTitle} catalog.
|
||||
</Typography>
|
||||
{repos.length > 0 && (
|
||||
{hasGithubIntegration && (
|
||||
<>
|
||||
<Typography variant="h6">
|
||||
Link to a {repositoryString} repository
|
||||
Link to a repository{' '}
|
||||
<Chip label="GitHub only" variant="outlined" size="small" />
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
|
||||
@@ -52,8 +52,8 @@ export const MockEntityListContextProvider = ({
|
||||
const defaultContext: EntityListContextProps = {
|
||||
entities: [],
|
||||
backendEntities: [],
|
||||
updateFilters: updateFilters,
|
||||
filters: filters,
|
||||
updateFilters,
|
||||
filters,
|
||||
loading: false,
|
||||
queryParameters: {},
|
||||
};
|
||||
|
||||
@@ -5,12 +5,21 @@
|
||||
```ts
|
||||
/// <reference types="react" />
|
||||
|
||||
import { AddLocationRequest } from '@backstage/catalog-client';
|
||||
import { AddLocationResponse } from '@backstage/catalog-client';
|
||||
import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { CatalogClient } from '@backstage/catalog-client';
|
||||
import { CatalogEntitiesRequest } from '@backstage/catalog-client';
|
||||
import { CatalogListResponse } from '@backstage/catalog-client';
|
||||
import { CatalogRequestOptions } from '@backstage/catalog-client';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityName } from '@backstage/catalog-model';
|
||||
import { ExternalRouteRef } from '@backstage/core-plugin-api';
|
||||
import { IconComponent } from '@backstage/core-plugin-api';
|
||||
import { IdentityApi } from '@backstage/core-plugin-api';
|
||||
import { InfoCardVariants } from '@backstage/core-components';
|
||||
import { Location as Location_2 } from '@backstage/catalog-model';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { default as React_2 } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
@@ -30,7 +39,7 @@ export function AboutCard({ variant }: AboutCardProps): JSX.Element;
|
||||
// Warning: (ae-missing-release-tag) "AboutContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const AboutContent: ({ entity }: Props_2) => JSX.Element;
|
||||
export const AboutContent: ({ entity }: Props) => JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "AboutField" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
@@ -41,7 +50,54 @@ export const AboutField: ({
|
||||
value,
|
||||
gridSizes,
|
||||
children,
|
||||
}: Props_3) => JSX.Element;
|
||||
}: Props_2) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "CatalogClientWrapper" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
export class CatalogClientWrapper implements CatalogApi {
|
||||
constructor(options: { client: CatalogClient; identityApi: IdentityApi });
|
||||
// (undocumented)
|
||||
addLocation(
|
||||
request: AddLocationRequest,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<AddLocationResponse>;
|
||||
// (undocumented)
|
||||
getEntities(
|
||||
request?: CatalogEntitiesRequest,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<CatalogListResponse<Entity>>;
|
||||
// (undocumented)
|
||||
getEntityByName(
|
||||
compoundName: EntityName,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<Entity | undefined>;
|
||||
// (undocumented)
|
||||
getLocationByEntity(
|
||||
entity: Entity,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<Location_2 | undefined>;
|
||||
// (undocumented)
|
||||
getLocationById(
|
||||
id: string,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<Location_2 | undefined>;
|
||||
// (undocumented)
|
||||
getOriginLocationByEntity(
|
||||
entity: Entity,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<Location_2 | undefined>;
|
||||
// (undocumented)
|
||||
removeEntityByUid(
|
||||
uid: string,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<void>;
|
||||
// (undocumented)
|
||||
removeLocationById(
|
||||
id: string,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "CatalogEntityPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -53,17 +109,11 @@ export const CatalogEntityPage: () => JSX.Element;
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const CatalogIndexPage: ({
|
||||
initiallySelectedFilter,
|
||||
columns,
|
||||
actions,
|
||||
initiallySelectedFilter,
|
||||
}: CatalogPageProps) => JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "CatalogLayout" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const CatalogLayout: ({ children }: Props) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "catalogPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
@@ -249,6 +299,13 @@ export const EntityLinksCard: ({
|
||||
variant?: 'gridItem' | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "EntityListContainer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const EntityListContainer: ({
|
||||
children,
|
||||
}: PropsWithChildren<{}>) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "EntityOrphanWarning" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
@@ -287,6 +344,20 @@ export const EntitySwitch: {
|
||||
// @public (undocumented)
|
||||
export const EntitySystemDiagramCard: SystemDiagramCard;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "FilterContainer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const FilterContainer: ({
|
||||
children,
|
||||
}: PropsWithChildren<{}>) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "FilteredEntityLayout" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const FilteredEntityLayout: ({
|
||||
children,
|
||||
}: PropsWithChildren<{}>) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "isComponentType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -19,16 +19,13 @@ import {
|
||||
AddLocationRequest,
|
||||
AddLocationResponse,
|
||||
CatalogApi,
|
||||
CatalogClient,
|
||||
CatalogEntitiesRequest,
|
||||
CatalogListResponse,
|
||||
CatalogClient,
|
||||
CatalogRequestOptions,
|
||||
} from '@backstage/catalog-client';
|
||||
import { IdentityApi } from '@backstage/core-plugin-api';
|
||||
|
||||
type CatalogRequestOptions = {
|
||||
token?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* CatalogClient wrapper that injects identity token for all requests
|
||||
*/
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { Header, Page } from '@backstage/core-components';
|
||||
|
||||
type Props = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
export const CatalogLayout = ({ children }: Props) => {
|
||||
const orgName =
|
||||
useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage';
|
||||
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
title={`${orgName} Catalog`}
|
||||
subtitle={`Catalog of software components at ${orgName}`}
|
||||
pageTitleOverride="Home"
|
||||
/>
|
||||
{children}
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default CatalogLayout;
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
MockStorageApi,
|
||||
renderWithEffects,
|
||||
wrapInTestApp,
|
||||
mockBreakpoint,
|
||||
} from '@backstage/test-utils';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
@@ -244,4 +245,13 @@ describe('CatalogPage', () => {
|
||||
screen.findByText(/Starred \(1\)/),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should wrap filter in drawer on smaller screens', async () => {
|
||||
mockBreakpoint({ matches: true });
|
||||
const { getByRole } = await renderWrapped(<CatalogPage />);
|
||||
const button = getByRole('button', { name: 'Filters' });
|
||||
expect(getByRole('presentation', { hidden: true })).toBeInTheDocument();
|
||||
fireEvent.click(button);
|
||||
expect(getByRole('presentation')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,8 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
PageWithHeader,
|
||||
SupportButton,
|
||||
TableColumn,
|
||||
TableProps,
|
||||
} from '@backstage/core-components';
|
||||
import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
EntityKindPicker,
|
||||
EntityLifecyclePicker,
|
||||
@@ -26,18 +33,15 @@ import {
|
||||
UserListFilterKind,
|
||||
UserListPicker,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import React from 'react';
|
||||
import { CatalogTable } from '../CatalogTable';
|
||||
|
||||
import { EntityRow } from '../CatalogTable/types';
|
||||
import CatalogLayout from './CatalogLayout';
|
||||
import { CreateComponentButton } from '../CreateComponentButton';
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
SupportButton,
|
||||
TableColumn,
|
||||
TableProps,
|
||||
} from '@backstage/core-components';
|
||||
FilteredEntityLayout,
|
||||
EntityListContainer,
|
||||
FilterContainer,
|
||||
} from '../FilteredEntityLayout';
|
||||
|
||||
export type CatalogPageProps = {
|
||||
initiallySelectedFilter?: UserListFilterKind;
|
||||
@@ -46,39 +50,36 @@ export type CatalogPageProps = {
|
||||
};
|
||||
|
||||
export const CatalogPage = ({
|
||||
initiallySelectedFilter = 'owned',
|
||||
columns,
|
||||
actions,
|
||||
}: CatalogPageProps) => (
|
||||
<CatalogLayout>
|
||||
<Content>
|
||||
<ContentHeader title="Components">
|
||||
<CreateComponentButton />
|
||||
<SupportButton>All your software catalog entities</SupportButton>
|
||||
</ContentHeader>
|
||||
<Grid container spacing={2}>
|
||||
initiallySelectedFilter = 'owned',
|
||||
}: CatalogPageProps) => {
|
||||
const orgName =
|
||||
useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage';
|
||||
|
||||
return (
|
||||
<PageWithHeader title={`${orgName} Catalog`} themeId="home">
|
||||
<Content>
|
||||
<ContentHeader title="Components">
|
||||
<CreateComponentButton />
|
||||
<SupportButton>All your software catalog entities</SupportButton>
|
||||
</ContentHeader>
|
||||
<EntityListProvider>
|
||||
<Grid item sm={12} lg={2} alignContent="flex-start">
|
||||
<Grid container>
|
||||
<Grid item xs={12} sm={4} lg={12}>
|
||||
<EntityKindPicker initialFilter="component" hidden />
|
||||
<EntityTypePicker />
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={4} lg={12}>
|
||||
<UserListPicker initialFilter={initiallySelectedFilter} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={4} lg={12}>
|
||||
<EntityOwnerPicker />
|
||||
<EntityLifecyclePicker />
|
||||
<EntityTagPicker />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={12} lg={10}>
|
||||
<CatalogTable columns={columns} actions={actions} />
|
||||
</Grid>
|
||||
<FilteredEntityLayout>
|
||||
<FilterContainer>
|
||||
<EntityKindPicker initialFilter="component" hidden />
|
||||
<EntityTypePicker />
|
||||
<UserListPicker initialFilter={initiallySelectedFilter} />
|
||||
<EntityOwnerPicker />
|
||||
<EntityLifecyclePicker />
|
||||
<EntityTagPicker />
|
||||
</FilterContainer>
|
||||
<EntityListContainer>
|
||||
<CatalogTable columns={columns} actions={actions} />
|
||||
</EntityListContainer>
|
||||
</FilteredEntityLayout>
|
||||
</EntityListProvider>
|
||||
</Grid>
|
||||
</Content>
|
||||
</CatalogLayout>
|
||||
);
|
||||
</Content>
|
||||
</PageWithHeader>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,5 +13,4 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { CatalogLayout } from './CatalogLayout';
|
||||
export { CatalogPage } from './CatalogPage';
|
||||
|
||||
@@ -23,9 +23,7 @@ import { useRouteRef } from '@backstage/core-plugin-api';
|
||||
export const CreateComponentButton = () => {
|
||||
const createComponentLink = useRouteRef(createComponentRouteRef);
|
||||
|
||||
if (!createComponentLink) return null;
|
||||
|
||||
return (
|
||||
return createComponentLink ? (
|
||||
<Button
|
||||
component={RouterLink}
|
||||
variant="contained"
|
||||
@@ -34,5 +32,5 @@ export const CreateComponentButton = () => {
|
||||
>
|
||||
Create Component
|
||||
</Button>
|
||||
);
|
||||
) : null;
|
||||
};
|
||||
|
||||
@@ -13,4 +13,5 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { CreateComponentButton } from './CreateComponentButton';
|
||||
|
||||
@@ -20,9 +20,17 @@ import {
|
||||
RELATION_OWNED_BY,
|
||||
} from '@backstage/catalog-model';
|
||||
import {
|
||||
useElementFilter,
|
||||
Content,
|
||||
Header,
|
||||
HeaderLabel,
|
||||
Page,
|
||||
Progress,
|
||||
RoutedTabs,
|
||||
} from '@backstage/core-components';
|
||||
import {
|
||||
attachComponentData,
|
||||
IconComponent,
|
||||
useElementFilter,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import {
|
||||
EntityContext,
|
||||
@@ -37,14 +45,6 @@ import { useNavigate } from 'react-router';
|
||||
import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu';
|
||||
import { FavouriteEntity } from '../FavouriteEntity/FavouriteEntity';
|
||||
import { UnregisterEntityDialog } from '../UnregisterEntityDialog/UnregisterEntityDialog';
|
||||
import {
|
||||
Content,
|
||||
Header,
|
||||
HeaderLabel,
|
||||
Page,
|
||||
Progress,
|
||||
RoutedTabs,
|
||||
} from '@backstage/core-components';
|
||||
|
||||
type SubRoute = {
|
||||
path: string;
|
||||
@@ -68,12 +68,21 @@ const EntityLayoutTitle = ({
|
||||
}: {
|
||||
title: string;
|
||||
entity: Entity | undefined;
|
||||
}) => (
|
||||
<Box display="inline-flex" alignItems="center" height="1em">
|
||||
{title}
|
||||
{entity && <FavouriteEntity entity={entity} />}
|
||||
</Box>
|
||||
);
|
||||
}) => {
|
||||
return (
|
||||
<Box display="inline-flex" alignItems="center" height="1em" maxWidth="100%">
|
||||
<Box
|
||||
component="span"
|
||||
textOverflow="ellipsis"
|
||||
whiteSpace="nowrap"
|
||||
overflow="hidden"
|
||||
>
|
||||
{title}
|
||||
</Box>
|
||||
{entity && <FavouriteEntity entity={entity} />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const headerProps = (
|
||||
paramKind: string | undefined,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Grid } from '@material-ui/core';
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
|
||||
export const EntityListContainer = ({ children }: PropsWithChildren<{}>) => (
|
||||
<Grid item xs={12} lg={10}>
|
||||
{children}
|
||||
</Grid>
|
||||
);
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Drawer,
|
||||
Grid,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from '@material-ui/core';
|
||||
import FilterListIcon from '@material-ui/icons/FilterList';
|
||||
import React, { useState, PropsWithChildren } from 'react';
|
||||
|
||||
export const FilterContainer = ({ children }: PropsWithChildren<{}>) => {
|
||||
const isMidSizeScreen = useMediaQuery<BackstageTheme>(theme =>
|
||||
theme.breakpoints.down('md'),
|
||||
);
|
||||
const theme = useTheme<BackstageTheme>();
|
||||
const [filterDrawerOpen, setFilterDrawerOpen] = useState<boolean>(false);
|
||||
|
||||
return isMidSizeScreen ? (
|
||||
<>
|
||||
<Button
|
||||
style={{ marginTop: theme.spacing(1), marginLeft: theme.spacing(1) }}
|
||||
onClick={() => setFilterDrawerOpen(true)}
|
||||
startIcon={<FilterListIcon />}
|
||||
>
|
||||
Filters
|
||||
</Button>
|
||||
<Drawer
|
||||
open={filterDrawerOpen}
|
||||
onClose={() => setFilterDrawerOpen(false)}
|
||||
anchor="left"
|
||||
disableAutoFocus
|
||||
keepMounted
|
||||
variant="temporary"
|
||||
>
|
||||
<Box m={2}>
|
||||
<Typography
|
||||
variant="h6"
|
||||
component="h2"
|
||||
style={{ marginBottom: theme.spacing(1) }}
|
||||
>
|
||||
Filters
|
||||
</Typography>
|
||||
{children}
|
||||
</Box>
|
||||
</Drawer>
|
||||
</>
|
||||
) : (
|
||||
<Grid item lg={2}>
|
||||
{children}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Grid } from '@material-ui/core';
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
|
||||
export const FilteredEntityLayout = ({ children }: PropsWithChildren<{}>) => (
|
||||
<Grid container style={{ position: 'relative' }}>
|
||||
{children}
|
||||
</Grid>
|
||||
);
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { FilteredEntityLayout } from './FilteredEntityLayout';
|
||||
export { FilterContainer } from './FilterContainer';
|
||||
export { EntityListContainer } from './EntityListContainer';
|
||||
@@ -14,16 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { CatalogClientWrapper } from './CatalogClientWrapper';
|
||||
export * from './components/AboutCard';
|
||||
export { CatalogLayout } from './components/CatalogPage';
|
||||
export { CatalogResultListItem } from './components/CatalogResultListItem';
|
||||
export * from './components/CatalogResultListItem';
|
||||
export { CatalogTable } from './components/CatalogTable';
|
||||
export type { EntityRow as CatalogTableRow } from './components/CatalogTable';
|
||||
export { CreateComponentButton } from './components/CreateComponentButton';
|
||||
export { EntityLayout } from './components/EntityLayout';
|
||||
export * from './components/CatalogTable/columns';
|
||||
export * from './components/CreateComponentButton';
|
||||
export * from './components/EntityLayout';
|
||||
export * from './components/EntityOrphanWarning';
|
||||
export { EntityPageLayout } from './components/EntityPageLayout';
|
||||
export * from './components/EntityPageLayout';
|
||||
export * from './components/EntitySwitch';
|
||||
export * from './components/FilteredEntityLayout';
|
||||
export { Router } from './components/Router';
|
||||
export {
|
||||
CatalogEntityPage,
|
||||
@@ -31,8 +33,8 @@ export {
|
||||
catalogPlugin,
|
||||
catalogPlugin as plugin,
|
||||
EntityAboutCard,
|
||||
EntityDependsOnComponentsCard,
|
||||
EntityDependencyOfComponentsCard,
|
||||
EntityDependsOnComponentsCard,
|
||||
EntityDependsOnResourcesCard,
|
||||
EntityHasComponentsCard,
|
||||
EntityHasResourcesCard,
|
||||
@@ -41,4 +43,3 @@ export {
|
||||
EntityLinksCard,
|
||||
EntitySystemDiagramCard,
|
||||
} from './plugin';
|
||||
export * from './components/CatalogTable/columns';
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import { ApiRef } from '@backstage/core-plugin-api';
|
||||
import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { default as React_2 } from 'react';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "GitReleaseApi" needs to be exported by the entry point index.d.ts
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { createDevApp } from '@backstage/dev-utils';
|
||||
import { Box, Typography } from '@material-ui/core';
|
||||
import { Box, Button, Typography } from '@material-ui/core';
|
||||
|
||||
import { gitReleaseManagerPlugin, GitReleaseManagerPage } from '../src/plugin';
|
||||
import { InfoCardPlus } from '../src/components/InfoCardPlus';
|
||||
@@ -44,10 +44,16 @@ createDevApp()
|
||||
<Box padding={4}>
|
||||
<InfoCardPlus>
|
||||
<Typography variant="h4">Dev notes</Typography>
|
||||
|
||||
<Typography>
|
||||
Configure plugin statically by passing props to the
|
||||
`GitHubReleaseManagerPage` component
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2">
|
||||
Note that the static configuration points towards private
|
||||
repositories and will thus not work for everyone.
|
||||
</Typography>
|
||||
</InfoCardPlus>
|
||||
|
||||
<GitReleaseManagerPage
|
||||
@@ -67,8 +73,10 @@ createDevApp()
|
||||
<Box padding={4}>
|
||||
<InfoCardPlus>
|
||||
<Typography variant="h4">Dev notes</Typography>
|
||||
<Typography>Each feature can be omitted</Typography>
|
||||
<Typography>Success callbacks can also be added</Typography>
|
||||
<Typography>
|
||||
Each feature can be individually omitted as well as have success
|
||||
callback attached to them
|
||||
</Typography>
|
||||
</InfoCardPlus>
|
||||
|
||||
<GitReleaseManagerPage
|
||||
@@ -79,22 +87,12 @@ createDevApp()
|
||||
}}
|
||||
features={{
|
||||
createRc: {
|
||||
onSuccess: ({
|
||||
comparisonUrl,
|
||||
createdTag,
|
||||
gitReleaseName,
|
||||
gitReleaseUrl,
|
||||
previousTag,
|
||||
}) => {
|
||||
onSuccess: args => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
'Custom success callback for Create RC',
|
||||
comparisonUrl,
|
||||
createdTag,
|
||||
gitReleaseName,
|
||||
gitReleaseUrl,
|
||||
previousTag,
|
||||
'Custom success callback for Create RC with the following args',
|
||||
);
|
||||
console.log(JSON.stringify(args, null, 2)); // eslint-disable-line no-console
|
||||
},
|
||||
},
|
||||
promoteRc: {
|
||||
@@ -108,4 +106,50 @@ createDevApp()
|
||||
</Box>
|
||||
),
|
||||
})
|
||||
.addPage({
|
||||
title: 'Custom',
|
||||
path: '/custom',
|
||||
element: (
|
||||
<Box padding={4}>
|
||||
<InfoCardPlus>
|
||||
<Typography variant="h4">Dev notes</Typography>
|
||||
<Typography>
|
||||
The custom feature's return value can either be a React Element or
|
||||
an array of React Elements.
|
||||
</Typography>
|
||||
</InfoCardPlus>
|
||||
|
||||
<GitReleaseManagerPage
|
||||
project={{
|
||||
owner: 'eengervall-playground',
|
||||
repo: 'playground-semver',
|
||||
versioningStrategy: 'semver',
|
||||
}}
|
||||
features={{
|
||||
custom: {
|
||||
factory: args => {
|
||||
return (
|
||||
<InfoCardPlus>
|
||||
<Typography variant="h4">I'm a custom feature</Typography>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
console.log(`Here's my args 🚀`); // eslint-disable-line no-console
|
||||
console.log(JSON.stringify(args, null, 2)); // eslint-disable-line no-console
|
||||
}}
|
||||
>
|
||||
View the arguments for this feature in the console by
|
||||
pressing this button
|
||||
</Button>
|
||||
</InfoCardPlus>
|
||||
);
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
),
|
||||
})
|
||||
.render();
|
||||
|
||||
@@ -28,12 +28,13 @@
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"@octokit/rest": "^18.5.3",
|
||||
"@types/react": "^16.9",
|
||||
"luxon": "^1.26.0",
|
||||
"qs": "^6.10.1",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router": "6.0.0-beta.0",
|
||||
"react-use": "^17.2.4",
|
||||
"react": "^16.13.1",
|
||||
"recharts": "^1.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -42,8 +43,8 @@
|
||||
"@backstage/dev-utils": "^0.2.2",
|
||||
"@backstage/test-utils": "^0.1.14",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@testing-library/react-hooks": "^3.4.2",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@testing-library/user-event": "^13.1.8",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/node": "^14.14.32",
|
||||
|
||||
@@ -18,12 +18,14 @@ import React from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import { Box } from '@material-ui/core';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { ContentHeader, Progress } from '@backstage/core-components';
|
||||
|
||||
import {
|
||||
ComponentConfig,
|
||||
ComponentConfigCreateRc,
|
||||
ComponentConfigPatch,
|
||||
ComponentConfigPromoteRc,
|
||||
CreateRcOnSuccessArgs,
|
||||
PatchOnSuccessArgs,
|
||||
PromoteRcOnSuccessArgs,
|
||||
} from './types/types';
|
||||
import { Features } from './features/Features';
|
||||
import { gitReleaseManagerApiRef } from './api/serviceApiRef';
|
||||
@@ -33,18 +35,33 @@ import { ProjectContext, Project } from './contexts/ProjectContext';
|
||||
import { RepoDetailsForm } from './features/RepoDetailsForm/RepoDetailsForm';
|
||||
import { useQueryHandler } from './hooks/useQueryHandler';
|
||||
import { UserContext } from './contexts/UserContext';
|
||||
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { ContentHeader, Progress } from '@backstage/core-components';
|
||||
import {
|
||||
GetBranchResult,
|
||||
GetLatestReleaseResult,
|
||||
GetRepositoryResult,
|
||||
} from './api/GitReleaseClient';
|
||||
|
||||
interface GitReleaseManagerProps {
|
||||
project?: Omit<Project, 'isProvidedViaProps'>;
|
||||
features?: {
|
||||
info?: Pick<ComponentConfig<void>, 'omit'>;
|
||||
stats?: Pick<ComponentConfig<void>, 'omit'>;
|
||||
createRc?: ComponentConfigCreateRc;
|
||||
promoteRc?: ComponentConfigPromoteRc;
|
||||
patch?: ComponentConfigPatch;
|
||||
createRc?: ComponentConfig<CreateRcOnSuccessArgs>;
|
||||
promoteRc?: ComponentConfig<PromoteRcOnSuccessArgs>;
|
||||
patch?: ComponentConfig<PatchOnSuccessArgs>;
|
||||
custom?: {
|
||||
factory: ({
|
||||
latestRelease,
|
||||
project,
|
||||
releaseBranch,
|
||||
repository,
|
||||
}: {
|
||||
latestRelease: GetLatestReleaseResult['latestRelease'] | null;
|
||||
project: Project;
|
||||
releaseBranch: GetBranchResult['branch'] | null;
|
||||
repository: GetRepositoryResult['repository'];
|
||||
}) => React.ReactElement | React.ReactElement[];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -31,7 +31,7 @@ import {
|
||||
GetLatestReleaseResult,
|
||||
GetRepositoryResult,
|
||||
} from '../../api/GitReleaseClient';
|
||||
import { ComponentConfigCreateRc } from '../../types/types';
|
||||
import { ComponentConfig, CreateRcOnSuccessArgs } from '../../types/types';
|
||||
import { Differ } from '../../components/Differ';
|
||||
import { getReleaseCandidateGitInfo } from '../../helpers/getReleaseCandidateGitInfo';
|
||||
import { InfoCardPlus } from '../../components/InfoCardPlus';
|
||||
@@ -45,7 +45,7 @@ interface CreateReleaseCandidateProps {
|
||||
defaultBranch: GetRepositoryResult['repository']['defaultBranch'];
|
||||
latestRelease: GetLatestReleaseResult['latestRelease'];
|
||||
releaseBranch: GetBranchResult['branch'] | null;
|
||||
onSuccess?: ComponentConfigCreateRc['onSuccess'];
|
||||
onSuccess?: ComponentConfig<CreateRcOnSuccessArgs>['onSuccess'];
|
||||
}
|
||||
|
||||
const InfoCardPlusWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||
|
||||
+13
-3
@@ -21,7 +21,11 @@ import {
|
||||
GetRepositoryResult,
|
||||
} from '../../../api/GitReleaseClient';
|
||||
|
||||
import { CardHook, ComponentConfigCreateRc } from '../../../types/types';
|
||||
import {
|
||||
CardHook,
|
||||
ComponentConfig,
|
||||
CreateRcOnSuccessArgs,
|
||||
} from '../../../types/types';
|
||||
import { getReleaseCandidateGitInfo } from '../../../helpers/getReleaseCandidateGitInfo';
|
||||
import { gitReleaseManagerApiRef } from '../../../api/serviceApiRef';
|
||||
import { GitReleaseManagerError } from '../../../errors/GitReleaseManagerError';
|
||||
@@ -31,12 +35,12 @@ import { useResponseSteps } from '../../../hooks/useResponseSteps';
|
||||
import { useUserContext } from '../../../contexts/UserContext';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
|
||||
interface UseCreateReleaseCandidate {
|
||||
export interface UseCreateReleaseCandidate {
|
||||
defaultBranch: GetRepositoryResult['repository']['defaultBranch'];
|
||||
latestRelease: GetLatestReleaseResult['latestRelease'];
|
||||
releaseCandidateGitInfo: ReturnType<typeof getReleaseCandidateGitInfo>;
|
||||
project: Project;
|
||||
onSuccess?: ComponentConfigCreateRc['onSuccess'];
|
||||
onSuccess?: ComponentConfig<CreateRcOnSuccessArgs>['onSuccess'];
|
||||
}
|
||||
|
||||
export function useCreateReleaseCandidate({
|
||||
@@ -266,6 +270,12 @@ export function useCreateReleaseCandidate({
|
||||
|
||||
try {
|
||||
await onSuccess({
|
||||
input: {
|
||||
defaultBranch,
|
||||
latestRelease,
|
||||
releaseCandidateGitInfo,
|
||||
project,
|
||||
},
|
||||
comparisonUrl: getComparisonRes.value.htmlUrl,
|
||||
createdTag: createReleaseRes.value.tagName,
|
||||
gitReleaseName: createReleaseRes.value.name,
|
||||
|
||||
@@ -142,6 +142,14 @@ export function Features({
|
||||
onSuccess={features?.patch?.onSuccess}
|
||||
/>
|
||||
)}
|
||||
|
||||
{features?.custom?.factory &&
|
||||
features.custom.factory({
|
||||
latestRelease: gitBatchInfo.value.latestRelease,
|
||||
project,
|
||||
releaseBranch: gitBatchInfo.value.releaseBranch,
|
||||
repository: gitBatchInfo.value.repository,
|
||||
})}
|
||||
</ErrorBoundary>
|
||||
</RefetchContext.Provider>
|
||||
);
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
GetBranchResult,
|
||||
GetLatestReleaseResult,
|
||||
} from '../../api/GitReleaseClient';
|
||||
import { ComponentConfigPatch } from '../../types/types';
|
||||
import { ComponentConfig, PatchOnSuccessArgs } from '../../types/types';
|
||||
import { getBumpedTag } from '../../helpers/getBumpedTag';
|
||||
import { InfoCardPlus } from '../../components/InfoCardPlus';
|
||||
import { NoLatestRelease } from '../../components/NoLatestRelease';
|
||||
@@ -32,7 +32,7 @@ import { useProjectContext } from '../../contexts/ProjectContext';
|
||||
interface PatchProps {
|
||||
latestRelease: GetLatestReleaseResult['latestRelease'];
|
||||
releaseBranch: GetBranchResult['branch'] | null;
|
||||
onSuccess?: ComponentConfigPatch['onSuccess'];
|
||||
onSuccess?: ComponentConfig<PatchOnSuccessArgs>['onSuccess'];
|
||||
}
|
||||
|
||||
export const Patch = ({
|
||||
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
GetLatestReleaseResult,
|
||||
} from '../../api/GitReleaseClient';
|
||||
import { CalverTagParts } from '../../helpers/tagParts/getCalverTagParts';
|
||||
import { ComponentConfigPatch } from '../../types/types';
|
||||
import { ComponentConfig, PatchOnSuccessArgs } from '../../types/types';
|
||||
import { Differ } from '../../components/Differ';
|
||||
import { getPatchCommitSuffix } from './helpers/getPatchCommitSuffix';
|
||||
import { gitReleaseManagerApiRef } from '../../api/serviceApiRef';
|
||||
@@ -56,7 +56,7 @@ interface PatchBodyProps {
|
||||
bumpedTag: string;
|
||||
latestRelease: NonNullable<GetLatestReleaseResult['latestRelease']>;
|
||||
releaseBranch: GetBranchResult['branch'];
|
||||
onSuccess?: ComponentConfigPatch['onSuccess'];
|
||||
onSuccess?: ComponentConfig<PatchOnSuccessArgs>['onSuccess'];
|
||||
tagParts: NonNullable<CalverTagParts | SemverTagParts>;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,11 @@ import {
|
||||
} from '../../../api/GitReleaseClient';
|
||||
|
||||
import { CalverTagParts } from '../../../helpers/tagParts/getCalverTagParts';
|
||||
import { ComponentConfigPatch, CardHook } from '../../../types/types';
|
||||
import {
|
||||
CardHook,
|
||||
ComponentConfig,
|
||||
PatchOnSuccessArgs,
|
||||
} from '../../../types/types';
|
||||
import { getPatchCommitSuffix } from '../helpers/getPatchCommitSuffix';
|
||||
import { gitReleaseManagerApiRef } from '../../../api/serviceApiRef';
|
||||
import { Project } from '../../../contexts/ProjectContext';
|
||||
@@ -32,12 +36,12 @@ import { useResponseSteps } from '../../../hooks/useResponseSteps';
|
||||
import { useUserContext } from '../../../contexts/UserContext';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
|
||||
interface Patch {
|
||||
export interface UsePatch {
|
||||
bumpedTag: string;
|
||||
latestRelease: NonNullable<GetLatestReleaseResult['latestRelease']>;
|
||||
project: Project;
|
||||
tagParts: NonNullable<CalverTagParts | SemverTagParts>;
|
||||
onSuccess?: ComponentConfigPatch['onSuccess'];
|
||||
onSuccess?: ComponentConfig<PatchOnSuccessArgs>['onSuccess'];
|
||||
}
|
||||
|
||||
// Inspiration: https://stackoverflow.com/questions/53859199/how-to-cherry-pick-through-githubs-api
|
||||
@@ -47,7 +51,7 @@ export function usePatch({
|
||||
project,
|
||||
tagParts,
|
||||
onSuccess,
|
||||
}: Patch): CardHook<GetRecentCommitsResultSingle> {
|
||||
}: UsePatch): CardHook<GetRecentCommitsResultSingle> {
|
||||
const pluginApiClient = useApi(gitReleaseManagerApiRef);
|
||||
const { user } = useUserContext();
|
||||
const {
|
||||
@@ -337,13 +341,19 @@ ${selectedPatchCommit.commit.message}`,
|
||||
|
||||
try {
|
||||
await onSuccess?.({
|
||||
updatedReleaseUrl: updatedReleaseRes.value.htmlUrl,
|
||||
updatedReleaseName: updatedReleaseRes.value.name,
|
||||
previousTag: latestRelease.tagName,
|
||||
patchedTag: updatedReleaseRes.value.tagName,
|
||||
patchCommitUrl: releaseBranchRes.value.selectedPatchCommit.htmlUrl,
|
||||
input: {
|
||||
bumpedTag,
|
||||
latestRelease,
|
||||
project,
|
||||
tagParts,
|
||||
},
|
||||
patchCommitMessage:
|
||||
releaseBranchRes.value.selectedPatchCommit.commit.message,
|
||||
patchCommitUrl: releaseBranchRes.value.selectedPatchCommit.htmlUrl,
|
||||
patchedTag: updatedReleaseRes.value.tagName,
|
||||
previousTag: latestRelease.tagName,
|
||||
updatedReleaseName: updatedReleaseRes.value.name,
|
||||
updatedReleaseUrl: updatedReleaseRes.value.htmlUrl,
|
||||
});
|
||||
} catch (error) {
|
||||
asyncCatcher(error);
|
||||
|
||||
@@ -18,7 +18,7 @@ import React from 'react';
|
||||
import { Alert, AlertTitle } from '@material-ui/lab';
|
||||
import { Box, Typography } from '@material-ui/core';
|
||||
|
||||
import { ComponentConfigPromoteRc } from '../../types/types';
|
||||
import { ComponentConfig, PromoteRcOnSuccessArgs } from '../../types/types';
|
||||
import { GetLatestReleaseResult } from '../../api/GitReleaseClient';
|
||||
import { InfoCardPlus } from '../../components/InfoCardPlus';
|
||||
import { NoLatestRelease } from '../../components/NoLatestRelease';
|
||||
@@ -27,7 +27,7 @@ import { TEST_IDS } from '../../test-helpers/test-ids';
|
||||
|
||||
interface PromoteRcProps {
|
||||
latestRelease: GetLatestReleaseResult['latestRelease'];
|
||||
onSuccess?: ComponentConfigPromoteRc['onSuccess'];
|
||||
onSuccess?: ComponentConfig<PromoteRcOnSuccessArgs>['onSuccess'];
|
||||
}
|
||||
|
||||
export const PromoteRc = ({ latestRelease, onSuccess }: PromoteRcProps) => {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import React from 'react';
|
||||
import { Button, Typography, Box } from '@material-ui/core';
|
||||
|
||||
import { ComponentConfigPromoteRc } from '../../types/types';
|
||||
import { ComponentConfig, PromoteRcOnSuccessArgs } from '../../types/types';
|
||||
import { Differ } from '../../components/Differ';
|
||||
import { GetLatestReleaseResult } from '../../api/GitReleaseClient';
|
||||
import { ResponseStepDialog } from '../../components/ResponseStepDialog/ResponseStepDialog';
|
||||
@@ -26,7 +26,7 @@ import { usePromoteRc } from './hooks/usePromoteRc';
|
||||
|
||||
interface PromoteRcBodyProps {
|
||||
rcRelease: NonNullable<GetLatestReleaseResult['latestRelease']>;
|
||||
onSuccess?: ComponentConfigPromoteRc['onSuccess'];
|
||||
onSuccess?: ComponentConfig<PromoteRcOnSuccessArgs>['onSuccess'];
|
||||
}
|
||||
|
||||
export const PromoteRcBody = ({ rcRelease, onSuccess }: PromoteRcBodyProps) => {
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useAsync, useAsyncFn } from 'react-use';
|
||||
import { CardHook, ComponentConfigPromoteRc } from '../../../types/types';
|
||||
import {
|
||||
CardHook,
|
||||
ComponentConfig,
|
||||
PromoteRcOnSuccessArgs,
|
||||
} from '../../../types/types';
|
||||
|
||||
import { GetLatestReleaseResult } from '../../../api/GitReleaseClient';
|
||||
import { gitReleaseManagerApiRef } from '../../../api/serviceApiRef';
|
||||
@@ -27,17 +31,17 @@ import { useResponseSteps } from '../../../hooks/useResponseSteps';
|
||||
import { useUserContext } from '../../../contexts/UserContext';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
|
||||
interface PromoteRc {
|
||||
export interface UsePromoteRc {
|
||||
rcRelease: NonNullable<GetLatestReleaseResult['latestRelease']>;
|
||||
releaseVersion: string;
|
||||
onSuccess?: ComponentConfigPromoteRc['onSuccess'];
|
||||
onSuccess?: ComponentConfig<PromoteRcOnSuccessArgs>['onSuccess'];
|
||||
}
|
||||
|
||||
export function usePromoteRc({
|
||||
rcRelease,
|
||||
releaseVersion,
|
||||
onSuccess,
|
||||
}: PromoteRc): CardHook<void> {
|
||||
}: UsePromoteRc): CardHook<void> {
|
||||
const pluginApiClient = useApi(gitReleaseManagerApiRef);
|
||||
const { user } = useUserContext();
|
||||
const { project } = useProjectContext();
|
||||
@@ -170,12 +174,16 @@ export function usePromoteRc({
|
||||
|
||||
try {
|
||||
await onSuccess?.({
|
||||
gitReleaseUrl: promotedReleaseRes.value.htmlUrl,
|
||||
input: {
|
||||
rcRelease,
|
||||
releaseVersion,
|
||||
},
|
||||
gitReleaseName: promotedReleaseRes.value.name,
|
||||
previousTagUrl: rcRelease.htmlUrl,
|
||||
gitReleaseUrl: promotedReleaseRes.value.htmlUrl,
|
||||
previousTag: rcRelease.tagName,
|
||||
updatedTagUrl: promotedReleaseRes.value.htmlUrl,
|
||||
previousTagUrl: rcRelease.htmlUrl,
|
||||
updatedTag: promotedReleaseRes.value.tagName,
|
||||
updatedTagUrl: promotedReleaseRes.value.htmlUrl,
|
||||
});
|
||||
} catch (error) {
|
||||
asyncCatcher(error);
|
||||
|
||||
@@ -14,21 +14,26 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export type ComponentConfig<Args> = {
|
||||
import { UseCreateReleaseCandidate } from '../features/CreateReleaseCandidate/hooks/useCreateReleaseCandidate';
|
||||
import { UsePatch } from '../features/Patch/hooks/usePatch';
|
||||
import { UsePromoteRc } from '../features/PromoteRc/hooks/usePromoteRc';
|
||||
|
||||
export type ComponentConfig<OnSuccessArgs> = {
|
||||
omit?: boolean;
|
||||
onSuccess?: (args: Args) => Promise<void> | void;
|
||||
onSuccess?: (args: OnSuccessArgs) => Promise<void> | void;
|
||||
};
|
||||
|
||||
interface CreateRcOnSuccessArgs {
|
||||
gitReleaseUrl: string;
|
||||
gitReleaseName: string | null;
|
||||
export interface CreateRcOnSuccessArgs {
|
||||
input: Omit<UseCreateReleaseCandidate, 'onSuccess'>;
|
||||
comparisonUrl: string;
|
||||
previousTag?: string;
|
||||
createdTag: string;
|
||||
gitReleaseName: string | null;
|
||||
gitReleaseUrl: string;
|
||||
previousTag?: string;
|
||||
}
|
||||
export type ComponentConfigCreateRc = ComponentConfig<CreateRcOnSuccessArgs>;
|
||||
|
||||
interface PromoteRcOnSuccessArgs {
|
||||
export interface PromoteRcOnSuccessArgs {
|
||||
input: Omit<UsePromoteRc, 'onSuccess'>;
|
||||
gitReleaseUrl: string;
|
||||
gitReleaseName: string | null;
|
||||
previousTagUrl: string;
|
||||
@@ -36,9 +41,9 @@ interface PromoteRcOnSuccessArgs {
|
||||
updatedTagUrl: string;
|
||||
updatedTag: string;
|
||||
}
|
||||
export type ComponentConfigPromoteRc = ComponentConfig<PromoteRcOnSuccessArgs>;
|
||||
|
||||
interface PatchOnSuccessArgs {
|
||||
export interface PatchOnSuccessArgs {
|
||||
input: Omit<UsePatch, 'onSuccess'>;
|
||||
updatedReleaseUrl: string;
|
||||
updatedReleaseName: string | null;
|
||||
previousTag: string;
|
||||
@@ -46,7 +51,6 @@ interface PatchOnSuccessArgs {
|
||||
patchCommitUrl: string;
|
||||
patchCommitMessage: string;
|
||||
}
|
||||
export type ComponentConfigPatch = ComponentConfig<PatchOnSuccessArgs>;
|
||||
|
||||
export interface ResponseStep {
|
||||
message: string | React.ReactNode;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.8.5s",
|
||||
"@backstage/backend-common": "^0.8.6",
|
||||
"@backstage/catalog-client": "^0.3.16",
|
||||
"@backstage/catalog-model": "^0.9.0",
|
||||
"@backstage/config": "^0.1.5",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: [require.resolve('@backstage/cli/config/eslint.backend')],
|
||||
};
|
||||
@@ -0,0 +1,162 @@
|
||||
# scaffolder-backend-module-cookiecutter
|
||||
|
||||
Welcome to the `fetch:cookiecutter` action for the `scaffolder-backend`.
|
||||
|
||||
## Getting started
|
||||
|
||||
You need to configure the action in your backend:
|
||||
|
||||
## From your Backstage root directory
|
||||
|
||||
```
|
||||
cd packages/backend
|
||||
yarn add @backstage/plugin-scaffolder-backend-module-cookiecutter
|
||||
```
|
||||
|
||||
Configure the action:
|
||||
(you can check the [docs](https://backstage.io/docs/features/software-templates/writing-custom-actions#registering-custom-actions) to see all options):
|
||||
|
||||
```typescript
|
||||
// packages/backend/src/plugins/scaffolder.ts
|
||||
|
||||
const actions = [
|
||||
createFetchCookiecutterAction({
|
||||
integrations,
|
||||
reader,
|
||||
containerRunner,
|
||||
}),
|
||||
...createBuiltInActions({
|
||||
...
|
||||
})
|
||||
];
|
||||
|
||||
return await createRouter({
|
||||
containerRunner,
|
||||
logger,
|
||||
config,
|
||||
database,
|
||||
catalogClient,
|
||||
reader,
|
||||
actions,
|
||||
});
|
||||
```
|
||||
|
||||
After that you can use the action in your template:
|
||||
|
||||
```yaml
|
||||
apiVersion: backstage.io/v1beta2
|
||||
kind: Template
|
||||
metadata:
|
||||
name: cookiecutter-demo
|
||||
title: Cookiecutter Test
|
||||
description: Cookiecutter example
|
||||
spec:
|
||||
owner: backstage/techdocs-core
|
||||
type: service
|
||||
|
||||
parameters:
|
||||
- title: Fill in some steps
|
||||
required:
|
||||
- name
|
||||
- owner
|
||||
properties:
|
||||
name:
|
||||
title: Name
|
||||
type: string
|
||||
description: Unique name of the component
|
||||
ui:autofocus: true
|
||||
ui:options:
|
||||
rows: 5
|
||||
owner:
|
||||
title: Owner
|
||||
type: string
|
||||
description: Owner of the component
|
||||
ui:field: OwnerPicker
|
||||
ui:options:
|
||||
allowedKinds:
|
||||
- Group
|
||||
system:
|
||||
title: System
|
||||
type: string
|
||||
description: System of the component
|
||||
ui:field: EntityPicker
|
||||
ui:options:
|
||||
allowedKinds:
|
||||
- System
|
||||
defaultKind: System
|
||||
|
||||
- title: Choose a location
|
||||
required:
|
||||
- repoUrl
|
||||
- dryRun
|
||||
properties:
|
||||
repoUrl:
|
||||
title: Repository Location
|
||||
type: string
|
||||
ui:field: RepoUrlPicker
|
||||
ui:options:
|
||||
allowedHosts:
|
||||
- github.com
|
||||
dryRun:
|
||||
title: Only perform a dry run, don't publish anything
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
steps:
|
||||
- id: fetch-base
|
||||
name: Fetch Base
|
||||
action: fetch:cookiecutter
|
||||
input:
|
||||
url: ./template
|
||||
values:
|
||||
name: '{{ parameters.name }}'
|
||||
owner: '{{ parameters.owner }}'
|
||||
system: '{{ parameters.system }}'
|
||||
destination: '{{ parseRepoUrl parameters.repoUrl }}'
|
||||
|
||||
- id: publish
|
||||
if: '{{ not parameters.dryRun }}'
|
||||
name: Publish
|
||||
action: publish:github
|
||||
input:
|
||||
allowedHosts: ['github.com']
|
||||
description: 'This is {{ parameters.name }}'
|
||||
repoUrl: '{{ parameters.repoUrl }}'
|
||||
|
||||
- id: register
|
||||
if: '{{ not parameters.dryRun }}'
|
||||
name: Register
|
||||
action: catalog:register
|
||||
input:
|
||||
repoContentsUrl: '{{ steps.publish.output.repoContentsUrl }}'
|
||||
catalogInfoPath: '/catalog-info.yaml'
|
||||
|
||||
- name: Results
|
||||
if: '{{ parameters.dryRun }}'
|
||||
action: debug:log
|
||||
input:
|
||||
listWorkspace: true
|
||||
|
||||
output:
|
||||
links:
|
||||
- title: Repository
|
||||
url: '{{ steps.publish.output.remoteUrl }}'
|
||||
- title: Open in catalog
|
||||
icon: 'catalog'
|
||||
entityRef: '{{ steps.register.output.entityRef }}'
|
||||
```
|
||||
|
||||
You can also visit the `/create/actions` route in your Backstage application to find out more about the parameters this action accepts when it's installed to configure how you like.
|
||||
|
||||
### Environment setup
|
||||
|
||||
The environment needs to have either `cookiecutter` installed and be available in the `PATH` or access to a `docker` daemon so it can spin up a docker container with `cookiecutter` available.
|
||||
|
||||
If you are running Backstage from a Docker container and you want to avoid calling a container inside a container, you can set up `cookiecutter` in your own image, this will use the local installation instead.
|
||||
|
||||
You can do so by including the following lines in the last step of your Dockerfile:
|
||||
|
||||
```dockerfile
|
||||
RUN apt-get update && apt-get install -y python3 python3-pip
|
||||
RUN pip3 install cookiecutter
|
||||
```
|
||||
@@ -0,0 +1,23 @@
|
||||
## API Report File for "@backstage/plugin-scaffolder-backend-module-cookiecutter"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
/// <reference types="node" />
|
||||
|
||||
import { ContainerRunner } from '@backstage/backend-common';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { TemplateAction } from '@backstage/plugin-scaffolder-backend';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "createFetchCookiecutterAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export function createFetchCookiecutterAction(options: {
|
||||
reader: UrlReader;
|
||||
integrations: ScmIntegrations;
|
||||
containerRunner: ContainerRunner;
|
||||
}): TemplateAction<any>;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "@backstage/plugin-scaffolder-backend-module-cookiecutter",
|
||||
"version": "0.1.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli backend:dev",
|
||||
"build": "backstage-cli backend:build",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"prepack": "backstage-cli prepack",
|
||||
"postpack": "backstage-cli postpack",
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.8.6",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/integration": "^0.5.7",
|
||||
"@backstage/plugin-scaffolder-backend": "^0.14.0",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"command-exists": "^1.2.9",
|
||||
"fs-extra": "10.0.0",
|
||||
"winston": "^3.2.1",
|
||||
"cross-fetch": "^3.0.6",
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.3",
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/mock-fs": "^4.13.0",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/command-exists": "^1.2.0",
|
||||
"mock-fs": "^4.13.0",
|
||||
"msw": "^0.29.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
+6
-3
@@ -17,9 +17,12 @@ const runCommand = jest.fn();
|
||||
const commandExists = jest.fn();
|
||||
const fetchContents = jest.fn();
|
||||
|
||||
jest.mock('./helpers', () => ({ fetchContents }));
|
||||
jest.mock('@backstage/plugin-scaffolder-backend', () => ({
|
||||
...jest.requireActual('@backstage/plugin-scaffolder-backend'),
|
||||
fetchContents,
|
||||
runCommand,
|
||||
}));
|
||||
jest.mock('command-exists', () => commandExists);
|
||||
jest.mock('../helpers', () => ({ runCommand }));
|
||||
|
||||
import {
|
||||
getVoidLogger,
|
||||
@@ -33,7 +36,7 @@ import os from 'os';
|
||||
import { PassThrough } from 'stream';
|
||||
import { createFetchCookiecutterAction } from './cookiecutter';
|
||||
import { join } from 'path';
|
||||
import { ActionContext } from '../../types';
|
||||
import type { ActionContext } from '@backstage/plugin-scaffolder-backend';
|
||||
|
||||
describe('fetch:cookiecutter', () => {
|
||||
const integrations = ScmIntegrations.fromConfig(
|
||||
+6
-4
@@ -26,9 +26,11 @@ import commandExists from 'command-exists';
|
||||
import fs from 'fs-extra';
|
||||
import path, { resolve as resolvePath } from 'path';
|
||||
import { Writable } from 'stream';
|
||||
import { runCommand } from '../helpers';
|
||||
import { createTemplateAction } from '../../createTemplateAction';
|
||||
import { fetchContents } from './helpers';
|
||||
import {
|
||||
runCommand,
|
||||
createTemplateAction,
|
||||
fetchContents,
|
||||
} from '@backstage/plugin-scaffolder-backend';
|
||||
|
||||
export class CookiecutterRunner {
|
||||
private readonly containerRunner: ContainerRunner;
|
||||
@@ -136,7 +138,7 @@ export function createFetchCookiecutterAction(options: {
|
||||
}>({
|
||||
id: 'fetch:cookiecutter',
|
||||
description:
|
||||
"Downloads a template from the given URL into the workspace, and runs cookiecutter on it. This action is deprecated in favor of 'fetch:template'. See https://backstage.io/docs/features/software-templates/builtin-actions#migrating-from-fetch-cookiecutter-to-fetch-template for more details.",
|
||||
'Downloads a template from the given URL into the workspace, and runs cookiecutter on it.',
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { createFetchCookiecutterAction } from './cookiecutter';
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export * from './fetch';
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export * from './actions';
|
||||
@@ -8,6 +8,7 @@
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { Config } from '@backstage/config';
|
||||
import { ContainerRunner } from '@backstage/backend-common';
|
||||
import { createFetchCookiecutterAction } from '@backstage/plugin-scaffolder-backend-module-cookiecutter';
|
||||
import { createPullRequest } from 'octokit-plugin-create-pull-request';
|
||||
import express from 'express';
|
||||
import { JsonObject } from '@backstage/config';
|
||||
@@ -78,14 +79,7 @@ export function createCatalogWriteAction(): TemplateAction<any>;
|
||||
// @public
|
||||
export function createDebugLogAction(): TemplateAction<any>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "createFetchCookiecutterAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export function createFetchCookiecutterAction(options: {
|
||||
reader: UrlReader;
|
||||
integrations: ScmIntegrations;
|
||||
containerRunner: ContainerRunner;
|
||||
}): TemplateAction<any>;
|
||||
export { createFetchCookiecutterAction };
|
||||
|
||||
// Warning: (ae-missing-release-tag) "createFetchPlainAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/integration": "^0.5.8",
|
||||
"@backstage/plugin-scaffolder-backend-module-cookiecutter": "^0.1.0",
|
||||
"@gitbeaker/core": "^30.2.0",
|
||||
"@gitbeaker/node": "^30.2.0",
|
||||
"@octokit/rest": "^18.5.3",
|
||||
|
||||
@@ -5,5 +5,10 @@ metadata:
|
||||
description: A collection of all Backstage example templates
|
||||
spec:
|
||||
targets:
|
||||
- ./local-templates.yaml
|
||||
- ./remote-templates.yaml
|
||||
|
||||
# For local development of a template, you can reference your local templates here.
|
||||
# Examples:
|
||||
#
|
||||
# - ./local-template/template.yaml
|
||||
# - ../all-templates/local/template.yaml
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Location
|
||||
metadata:
|
||||
name: example-templates-local
|
||||
description: A collection of locally available Backstage example templates
|
||||
spec:
|
||||
targets:
|
||||
- ./docs-template/template.yaml
|
||||
- ./react-ssr-template/template.yaml
|
||||
- ./create-react-app/template.yaml
|
||||
- ./springboot-grpc-template/template.yaml
|
||||
- ./v1beta2-demo/template.yaml
|
||||
- ./pull-request/template.yaml
|
||||
@@ -7,3 +7,9 @@ spec:
|
||||
type: url
|
||||
targets:
|
||||
- https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml
|
||||
- https://github.com/backstage/software-templates/blob/main/scaffolder-templates/create-react-app/template.yaml
|
||||
- https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml
|
||||
- https://github.com/backstage/software-templates/blob/main/scaffolder-templates/pull-request/template.yaml
|
||||
- https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml
|
||||
- https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml
|
||||
- https://github.com/backstage/software-templates/blob/main/scaffolder-templates/v1beta2-demo/template.yaml
|
||||
|
||||
@@ -24,11 +24,8 @@ import {
|
||||
} from './catalog';
|
||||
|
||||
import { createDebugLogAction } from './debug';
|
||||
import {
|
||||
createFetchCookiecutterAction,
|
||||
createFetchPlainAction,
|
||||
createFetchTemplateAction,
|
||||
} from './fetch';
|
||||
import { createFetchPlainAction, createFetchTemplateAction } from './fetch';
|
||||
import { createFetchCookiecutterAction } from '@backstage/plugin-scaffolder-backend-module-cookiecutter';
|
||||
import {
|
||||
createFilesystemDeleteAction,
|
||||
createFilesystemRenameAction,
|
||||
|
||||
@@ -15,6 +15,5 @@
|
||||
*/
|
||||
|
||||
export { createFetchPlainAction } from './plain';
|
||||
export { createFetchCookiecutterAction } from './cookiecutter';
|
||||
export { createFetchTemplateAction } from './template';
|
||||
export { fetchContents } from './helpers';
|
||||
|
||||
@@ -20,4 +20,6 @@ export * from './debug';
|
||||
export * from './fetch';
|
||||
export * from './filesystem';
|
||||
export * from './publish';
|
||||
|
||||
export { createFetchCookiecutterAction } from '@backstage/plugin-scaffolder-backend-module-cookiecutter';
|
||||
export { runCommand } from './helpers';
|
||||
|
||||
@@ -13,7 +13,7 @@ import { DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityName } from '@backstage/catalog-model';
|
||||
import { IdentityApi } from '@backstage/core-plugin-api';
|
||||
import { Location as Location_2 } from '@backstage/catalog-model';
|
||||
import { LocationSpec } from '@backstage/catalog-model';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "DocsCardGrid" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { EntityName } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';
|
||||
import { NotFoundError } from '@backstage/errors';
|
||||
import { NotFoundError, ResponseError } from '@backstage/errors';
|
||||
import EventSource from 'eventsource';
|
||||
import { SyncResult, TechDocsApi, TechDocsStorageApi } from './api';
|
||||
import { TechDocsEntityMetadata, TechDocsMetadata } from './types';
|
||||
@@ -72,9 +72,12 @@ export class TechDocsClient implements TechDocsApi {
|
||||
const request = await fetch(`${requestUrl}`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
const res = await request.json();
|
||||
|
||||
return res;
|
||||
if (!request.ok) {
|
||||
throw await ResponseError.fromResponse(request);
|
||||
}
|
||||
|
||||
return await request.json();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,9 +100,12 @@ export class TechDocsClient implements TechDocsApi {
|
||||
const request = await fetch(`${requestUrl}`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
const res = await request.json();
|
||||
|
||||
return res;
|
||||
if (!request.ok) {
|
||||
throw await ResponseError.fromResponse(request);
|
||||
}
|
||||
|
||||
return await request.json();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,12 @@ import { Progress } from '@backstage/core-components';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { scmIntegrationsApiRef } from '@backstage/integration-react';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { Button, CircularProgress, useTheme } from '@material-ui/core';
|
||||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
makeStyles,
|
||||
useTheme,
|
||||
} from '@material-ui/core';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
@@ -45,10 +50,20 @@ type Props = {
|
||||
onReady?: () => void;
|
||||
};
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme>(() => ({
|
||||
message: {
|
||||
// `word-break: break-word` is deprecated, but gives legacy support to browsers not supporting `overflow-wrap` yet
|
||||
// https://developer.mozilla.org/en-US/docs/Web/CSS/word-break
|
||||
wordBreak: 'break-word',
|
||||
overflowWrap: 'anywhere',
|
||||
},
|
||||
}));
|
||||
|
||||
export const Reader = ({ entityId, onReady }: Props) => {
|
||||
const { kind, namespace, name } = entityId;
|
||||
const { '*': path } = useParams();
|
||||
const theme = useTheme<BackstageTheme>();
|
||||
const classes = useStyles();
|
||||
|
||||
const {
|
||||
state,
|
||||
@@ -369,6 +384,7 @@ export const Reader = ({ entityId, onReady }: Props) => {
|
||||
variant="outlined"
|
||||
severity="error"
|
||||
action={<TechDocsBuildLogs buildLog={buildLog} />}
|
||||
classes={{ message: classes.message }}
|
||||
>
|
||||
Building a newer version of this documentation failed.{' '}
|
||||
{syncErrorMessage}
|
||||
@@ -381,6 +397,7 @@ export const Reader = ({ entityId, onReady }: Props) => {
|
||||
variant="outlined"
|
||||
severity="error"
|
||||
action={<TechDocsBuildLogs buildLog={buildLog} />}
|
||||
classes={{ message: classes.message }}
|
||||
>
|
||||
Building a newer version of this documentation failed.{' '}
|
||||
{syncErrorMessage}
|
||||
|
||||
@@ -30,7 +30,7 @@ export const TechDocsPage = () => {
|
||||
|
||||
const techdocsApi = useApi(techdocsApiRef);
|
||||
|
||||
const techdocsMetadataRequest = useAsync(() => {
|
||||
const { value: techdocsMetadataValue } = useAsync(() => {
|
||||
if (documentReady) {
|
||||
return techdocsApi.getTechDocsMetadata({ kind, namespace, name });
|
||||
}
|
||||
@@ -38,7 +38,7 @@ export const TechDocsPage = () => {
|
||||
return Promise.resolve(undefined);
|
||||
}, [kind, namespace, name, techdocsApi, documentReady]);
|
||||
|
||||
const entityMetadataRequest = useAsync(() => {
|
||||
const { value: entityMetadataValue } = useAsync(() => {
|
||||
return techdocsApi.getEntityMetadata({ kind, namespace, name });
|
||||
}, [kind, namespace, name, techdocsApi]);
|
||||
|
||||
@@ -49,10 +49,8 @@ export const TechDocsPage = () => {
|
||||
return (
|
||||
<Page themeId="documentation">
|
||||
<TechDocsPageHeader
|
||||
metadataRequest={{
|
||||
techdocs: techdocsMetadataRequest,
|
||||
entity: entityMetadataRequest,
|
||||
}}
|
||||
techDocsMetadata={techdocsMetadataValue}
|
||||
entityMetadata={entityMetadataValue}
|
||||
entityId={{
|
||||
kind,
|
||||
namespace,
|
||||
|
||||
@@ -30,26 +30,23 @@ describe('<TechDocsPageHeader />', () => {
|
||||
name: 'test-name',
|
||||
namespace: 'test-namespace',
|
||||
}}
|
||||
metadataRequest={{
|
||||
entity: {
|
||||
loading: false,
|
||||
value: {
|
||||
locationMetadata: {
|
||||
type: 'github',
|
||||
target: 'https://example.com/',
|
||||
},
|
||||
spec: {
|
||||
owner: 'test',
|
||||
},
|
||||
},
|
||||
entityMetadata={{
|
||||
locationMetadata: {
|
||||
type: 'github',
|
||||
target: 'https://example.com/',
|
||||
},
|
||||
techdocs: {
|
||||
loading: false,
|
||||
value: {
|
||||
site_name: 'test-site-name',
|
||||
site_description: 'test-site-desc',
|
||||
},
|
||||
apiVersion: 'v1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'test',
|
||||
},
|
||||
spec: {
|
||||
owner: 'test',
|
||||
},
|
||||
}}
|
||||
techDocsMetadata={{
|
||||
site_name: 'test-site-name',
|
||||
site_description: 'test-site-desc',
|
||||
}}
|
||||
/>,
|
||||
{
|
||||
@@ -75,14 +72,6 @@ describe('<TechDocsPageHeader />', () => {
|
||||
name: 'test-name',
|
||||
namespace: 'test-namespace',
|
||||
}}
|
||||
metadataRequest={{
|
||||
entity: {
|
||||
loading: false,
|
||||
},
|
||||
techdocs: {
|
||||
loading: false,
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
@@ -105,17 +94,9 @@ describe('<TechDocsPageHeader />', () => {
|
||||
name: 'test-name',
|
||||
namespace: 'test-namespace',
|
||||
}}
|
||||
metadataRequest={{
|
||||
entity: {
|
||||
loading: false,
|
||||
},
|
||||
techdocs: {
|
||||
loading: false,
|
||||
value: {
|
||||
site_name: 'test-site-name',
|
||||
site_description: 'test-site-desc',
|
||||
},
|
||||
},
|
||||
techDocsMetadata={{
|
||||
site_name: 'test-site-name',
|
||||
site_description: 'test-site-desc',
|
||||
}}
|
||||
/>,
|
||||
{
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
|
||||
import { EntityName, RELATION_OWNED_BY } from '@backstage/catalog-model';
|
||||
import { Header, HeaderLabel } from '@backstage/core-components';
|
||||
import { useRouteRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
EntityRefLink,
|
||||
EntityRefLinks,
|
||||
@@ -22,45 +24,30 @@ import {
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import CodeIcon from '@material-ui/icons/Code';
|
||||
import React from 'react';
|
||||
import { AsyncState } from 'react-use/lib/useAsync';
|
||||
import { rootRouteRef } from '../../routes';
|
||||
import { TechDocsMetadata } from '../../types';
|
||||
|
||||
import { Header, HeaderLabel } from '@backstage/core-components';
|
||||
import { useRouteRef } from '@backstage/core-plugin-api';
|
||||
import { TechDocsEntityMetadata, TechDocsMetadata } from '../../types';
|
||||
|
||||
type TechDocsPageHeaderProps = {
|
||||
entityId: EntityName;
|
||||
metadataRequest: {
|
||||
entity: AsyncState<any>;
|
||||
techdocs: AsyncState<TechDocsMetadata>;
|
||||
};
|
||||
entityMetadata?: TechDocsEntityMetadata;
|
||||
techDocsMetadata?: TechDocsMetadata;
|
||||
};
|
||||
|
||||
export const TechDocsPageHeader = ({
|
||||
entityId,
|
||||
metadataRequest,
|
||||
entityMetadata,
|
||||
techDocsMetadata,
|
||||
}: TechDocsPageHeaderProps) => {
|
||||
const {
|
||||
techdocs: techdocsMetadata,
|
||||
entity: entityMetadata,
|
||||
} = metadataRequest;
|
||||
|
||||
const { value: techdocsMetadataValues } = techdocsMetadata;
|
||||
const { value: entityMetadataValues } = entityMetadata;
|
||||
|
||||
const { name } = entityId;
|
||||
|
||||
const { site_name: siteName, site_description: siteDescription } =
|
||||
techdocsMetadataValues || {};
|
||||
techDocsMetadata || {};
|
||||
|
||||
const {
|
||||
locationMetadata,
|
||||
spec: { lifecycle },
|
||||
} = entityMetadataValues || { spec: {} };
|
||||
const { locationMetadata, spec } = entityMetadata || {};
|
||||
const lifecycle = spec?.lifecycle;
|
||||
|
||||
const ownedByRelations = entityMetadataValues
|
||||
? getEntityRelations(entityMetadataValues, RELATION_OWNED_BY)
|
||||
const ownedByRelations = entityMetadata
|
||||
? getEntityRelations(entityMetadata, RELATION_OWNED_BY)
|
||||
: [];
|
||||
|
||||
const docsRootLink = useRouteRef(rootRouteRef)();
|
||||
|
||||
@@ -14,11 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entity, Location } from '@backstage/catalog-model';
|
||||
import { Entity, LocationSpec } from '@backstage/catalog-model';
|
||||
|
||||
export type TechDocsMetadata = {
|
||||
site_name: string;
|
||||
site_description: string;
|
||||
};
|
||||
|
||||
export type TechDocsEntityMetadata = Entity & { locationMetadata?: Location };
|
||||
export type TechDocsEntityMetadata = Entity & {
|
||||
locationMetadata?: LocationSpec;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user