Merge branch 'backstage:master' into master
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
## API Report File for "@backstage/plugin-api-docs"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiEntity } from '@backstage/catalog-model';
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { CatalogTableRow } from '@backstage/plugin-catalog';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { ExternalRouteRef } from '@backstage/core';
|
||||
import { default as React_2 } from 'react';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
import { TableColumn } from '@backstage/core';
|
||||
import { UserListFilterKind } from '@backstage/plugin-catalog-react';
|
||||
|
||||
// @public (undocumented)
|
||||
export const ApiDefinitionCard: (_: Props) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type ApiDefinitionWidget = {
|
||||
type: string;
|
||||
title: string;
|
||||
component: (definition: string) => React_2.ReactElement;
|
||||
rawLanguage?: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const apiDocsConfigRef: ApiRef<ApiDocsConfig>;
|
||||
|
||||
// @public (undocumented)
|
||||
const apiDocsPlugin: BackstagePlugin<{
|
||||
root: RouteRef<undefined>;
|
||||
}, {
|
||||
createComponent: ExternalRouteRef<undefined, true>;
|
||||
}>;
|
||||
|
||||
export { apiDocsPlugin }
|
||||
|
||||
export { apiDocsPlugin as plugin }
|
||||
|
||||
// @public (undocumented)
|
||||
export const ApiExplorerPage: ({ initiallySelectedFilter, columns, }: ApiExplorerPageProps) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ApiTypeTitle: ({ apiEntity }: {
|
||||
apiEntity: ApiEntity;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const AsyncApiDefinitionWidget: ({ definition }: Props_5) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ConsumedApisCard: ({ variant }: Props_2) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ConsumingComponentsCard: ({ variant }: Props_6) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export function defaultDefinitionWidgets(): ApiDefinitionWidget[];
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityApiDefinitionCard: (_: {
|
||||
apiEntity?: ApiEntity | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityConsumedApisCard: ({ variant }: {
|
||||
entity?: Entity| undefined;
|
||||
variant?: "gridItem" | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityConsumingComponentsCard: ({ variant }: {
|
||||
entity?: Entity| undefined;
|
||||
variant?: "gridItem" | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityHasApisCard: ({ variant }: {
|
||||
variant?: "gridItem" | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityProvidedApisCard: ({ variant }: {
|
||||
entity?: Entity| undefined;
|
||||
variant?: "gridItem" | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityProvidingComponentsCard: ({ variant }: {
|
||||
entity?: Entity| undefined;
|
||||
variant?: "gridItem" | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const HasApisCard: ({ variant }: Props_3) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const OpenApiDefinitionWidget: ({ definition }: Props_8) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const PlainApiDefinitionWidget: ({ definition, language }: Props_9) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ProvidedApisCard: ({ variant }: Props_4) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ProvidingComponentsCard: ({ variant }: Props_7) => JSX.Element;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,28 @@
|
||||
## API Report File for "@backstage/plugin-app-backend"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
// @public (undocumented)
|
||||
export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RouterOptions {
|
||||
appPackageName: string;
|
||||
// (undocumented)
|
||||
config: Config;
|
||||
disableConfigInjection?: boolean;
|
||||
// (undocumented)
|
||||
logger: Logger;
|
||||
staticFallbackHandler?: express.Handler;
|
||||
}
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,211 @@
|
||||
## API Report File for "@backstage/plugin-auth-backend"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { Config } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import { JSONWebKey } from 'jose';
|
||||
import { Logger } from 'winston';
|
||||
import { PluginDatabaseManager } from '@backstage/backend-common';
|
||||
import { PluginEndpointDiscovery } from '@backstage/backend-common';
|
||||
import { Profile } from 'passport';
|
||||
|
||||
// @public (undocumented)
|
||||
export type AuthProviderFactory = (options: AuthProviderFactoryOptions) => AuthProviderRouteHandlers;
|
||||
|
||||
// @public (undocumented)
|
||||
export type AuthProviderFactoryOptions = {
|
||||
providerId: string;
|
||||
globalConfig: AuthProviderConfig;
|
||||
config: Config;
|
||||
logger: Logger;
|
||||
tokenIssuer: TokenIssuer;
|
||||
discovery: PluginEndpointDiscovery;
|
||||
catalogApi: CatalogApi;
|
||||
identityResolver?: ExperimentalIdentityResolver;
|
||||
};
|
||||
|
||||
// @public
|
||||
export interface AuthProviderRouteHandlers {
|
||||
frameHandler(req: express.Request, res: express.Response): Promise<void>;
|
||||
logout?(req: express.Request, res: express.Response): Promise<void>;
|
||||
refresh?(req: express.Request, res: express.Response): Promise<void>;
|
||||
start(req: express.Request, res: express.Response): Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type AuthResponse<ProviderInfo> = {
|
||||
providerInfo: ProviderInfo;
|
||||
profile: ProfileInfo;
|
||||
backstageIdentity?: BackstageIdentity;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type BackstageIdentity = {
|
||||
id: string;
|
||||
idToken?: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export function createRouter({ logger, config, discovery, database, providerFactories, }: RouterOptions): Promise<express.Router>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const defaultAuthProviderFactories: {
|
||||
[providerId: string]: AuthProviderFactory;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const encodeState: (state: OAuthState) => string;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ensuresXRequestedWith: (req: express.Request) => boolean;
|
||||
|
||||
// @public
|
||||
export class IdentityClient {
|
||||
constructor(options: {
|
||||
discovery: PluginEndpointDiscovery;
|
||||
issuer: string;
|
||||
});
|
||||
authenticate(token: string | undefined): Promise<BackstageIdentity>;
|
||||
static getBearerToken(authorizationHeader: string | undefined): string | undefined;
|
||||
listPublicKeys(): Promise<{
|
||||
keys: JSONWebKey[];
|
||||
}>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class OAuthAdapter implements AuthProviderRouteHandlers {
|
||||
constructor(handlers: OAuthHandlers, options: Options);
|
||||
// (undocumented)
|
||||
frameHandler(req: express.Request, res: express.Response): Promise<void>;
|
||||
// (undocumented)
|
||||
static fromConfig(config: AuthProviderConfig, handlers: OAuthHandlers, options: Pick<Options, 'providerId' | 'persistScopes' | 'disableRefresh' | 'tokenIssuer'>): OAuthAdapter;
|
||||
// (undocumented)
|
||||
logout(req: express.Request, res: express.Response): Promise<void>;
|
||||
// (undocumented)
|
||||
refresh(req: express.Request, res: express.Response): Promise<void>;
|
||||
// (undocumented)
|
||||
start(req: express.Request, res: express.Response): Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class OAuthEnvironmentHandler implements AuthProviderRouteHandlers {
|
||||
constructor(handlers: Map<string, AuthProviderRouteHandlers>);
|
||||
// (undocumented)
|
||||
frameHandler(req: express.Request, res: express.Response): Promise<void>;
|
||||
// (undocumented)
|
||||
logout(req: express.Request, res: express.Response): Promise<void>;
|
||||
// (undocumented)
|
||||
static mapConfig(config: Config, factoryFunc: (envConfig: Config) => AuthProviderRouteHandlers): OAuthEnvironmentHandler;
|
||||
// (undocumented)
|
||||
refresh(req: express.Request, res: express.Response): Promise<void>;
|
||||
// (undocumented)
|
||||
start(req: express.Request, res: express.Response): Promise<void>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface OAuthHandlers {
|
||||
handler(req: express.Request): Promise<{
|
||||
response: AuthResponse<OAuthProviderInfo>;
|
||||
refreshToken?: string;
|
||||
}>;
|
||||
logout?(): Promise<void>;
|
||||
refresh?(req: OAuthRefreshRequest): Promise<AuthResponse<OAuthProviderInfo>>;
|
||||
start(req: OAuthStartRequest): Promise<RedirectInfo>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type OAuthProviderInfo = {
|
||||
accessToken: string;
|
||||
idToken?: string;
|
||||
expiresInSeconds?: number;
|
||||
scope: string;
|
||||
refreshToken?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type OAuthProviderOptions = {
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
callbackUrl: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type OAuthRefreshRequest = express.Request<{}> & {
|
||||
scope: string;
|
||||
refreshToken: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type OAuthResponse = AuthResponse<OAuthProviderInfo>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type OAuthResult = {
|
||||
fullProfile: Profile;
|
||||
params: {
|
||||
id_token?: string;
|
||||
scope: string;
|
||||
expires_in: number;
|
||||
};
|
||||
accessToken: string;
|
||||
refreshToken?: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type OAuthStartRequest = express.Request<{}> & {
|
||||
scope: string;
|
||||
state: OAuthState;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type OAuthState = {
|
||||
nonce: string;
|
||||
env: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const postMessageResponse: (res: express.Response, appOrigin: string, response: WebMessageResponse) => void;
|
||||
|
||||
// @public
|
||||
export type ProfileInfo = {
|
||||
email?: string;
|
||||
displayName?: string;
|
||||
picture?: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const readState: (stateString: string) => OAuthState;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RouterOptions {
|
||||
// (undocumented)
|
||||
config: Config;
|
||||
// (undocumented)
|
||||
database: PluginDatabaseManager;
|
||||
// (undocumented)
|
||||
discovery: PluginEndpointDiscovery;
|
||||
// (undocumented)
|
||||
logger: Logger;
|
||||
// (undocumented)
|
||||
providerFactories?: ProviderFactories;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const verifyNonce: (req: express.Request, providerId: string) => void;
|
||||
|
||||
// @public
|
||||
export type WebMessageResponse = {
|
||||
type: 'authorization_response';
|
||||
response: AuthResponse<unknown>;
|
||||
} | {
|
||||
type: 'authorization_response';
|
||||
error: Error;
|
||||
};
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -67,13 +67,14 @@ export class TokenFactory implements TokenIssuer {
|
||||
|
||||
const iss = this.issuer;
|
||||
const sub = params.claims.sub;
|
||||
const ent = params.claims.ent;
|
||||
const aud = 'backstage';
|
||||
const iat = Math.floor(Date.now() / MS_IN_S);
|
||||
const exp = iat + this.keyDurationSeconds;
|
||||
|
||||
this.logger.info(`Issuing token for ${sub}`);
|
||||
this.logger.info(`Issuing token for ${sub}, with entities ${ent ?? []}`);
|
||||
|
||||
return JWS.sign({ iss, sub, aud, iat, exp }, key, {
|
||||
return JWS.sign({ iss, sub, aud, iat, exp, ent }, key, {
|
||||
alg: key.alg,
|
||||
kid: key.kid,
|
||||
});
|
||||
|
||||
@@ -28,6 +28,8 @@ export type TokenParams = {
|
||||
claims: {
|
||||
/** The token subject, i.e. User ID */
|
||||
sub: string;
|
||||
/** A list of entity references that the user claims ownership through */
|
||||
ent?: string[];
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { UserEntity } from '@backstage/catalog-model';
|
||||
import { TokenIssuer } from '../../identity';
|
||||
import { CatalogIdentityClient } from './CatalogIdentityClient';
|
||||
|
||||
describe('CatalogIdentityClient', () => {
|
||||
@@ -29,25 +30,36 @@ describe('CatalogIdentityClient', () => {
|
||||
getLocationByEntity: jest.fn(),
|
||||
removeEntityByUid: jest.fn(),
|
||||
};
|
||||
const tokenIssuer: jest.Mocked<TokenIssuer> = {
|
||||
issueToken: jest.fn(),
|
||||
listPublicKeys: jest.fn(),
|
||||
};
|
||||
|
||||
afterEach(() => jest.resetAllMocks());
|
||||
|
||||
it('passes through the correct search params', async () => {
|
||||
catalogApi.getEntities.mockResolvedValueOnce({ items: [{} as UserEntity] });
|
||||
tokenIssuer.issueToken.mockResolvedValue('my-token');
|
||||
const client = new CatalogIdentityClient({
|
||||
catalogApi: catalogApi as CatalogApi,
|
||||
catalogApi: catalogApi,
|
||||
tokenIssuer: tokenIssuer,
|
||||
});
|
||||
|
||||
client.findUser({ annotations: { key: 'value' } });
|
||||
await client.findUser({ annotations: { key: 'value' } });
|
||||
|
||||
expect(catalogApi.getEntities).toBeCalledWith(
|
||||
expect(catalogApi.getEntities).toHaveBeenCalledWith(
|
||||
{
|
||||
filter: {
|
||||
kind: 'user',
|
||||
'metadata.annotations.key': 'value',
|
||||
},
|
||||
},
|
||||
undefined,
|
||||
{ token: 'my-token' },
|
||||
);
|
||||
expect(tokenIssuer.issueToken).toHaveBeenCalledWith({
|
||||
claims: {
|
||||
sub: 'backstage.io/auth-backend',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import { ConflictError, NotFoundError } from '@backstage/errors';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { UserEntity } from '@backstage/catalog-model';
|
||||
import { TokenIssuer } from '../../identity';
|
||||
|
||||
type UserQuery = {
|
||||
annotations: Record<string, string>;
|
||||
@@ -27,9 +28,11 @@ type UserQuery = {
|
||||
*/
|
||||
export class CatalogIdentityClient {
|
||||
private readonly catalogApi: CatalogApi;
|
||||
private readonly tokenIssuer: TokenIssuer;
|
||||
|
||||
constructor(options: { catalogApi: CatalogApi }) {
|
||||
constructor(options: { catalogApi: CatalogApi; tokenIssuer: TokenIssuer }) {
|
||||
this.catalogApi = options.catalogApi;
|
||||
this.tokenIssuer = options.tokenIssuer;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,10 +40,7 @@ export class CatalogIdentityClient {
|
||||
*
|
||||
* Throws a NotFoundError or ConflictError if 0 or multiple users are found.
|
||||
*/
|
||||
async findUser(
|
||||
query: UserQuery,
|
||||
options?: { token?: string },
|
||||
): Promise<UserEntity> {
|
||||
async findUser(query: UserQuery): Promise<UserEntity> {
|
||||
const filter: Record<string, string> = {
|
||||
kind: 'user',
|
||||
};
|
||||
@@ -48,7 +48,11 @@ export class CatalogIdentityClient {
|
||||
filter[`metadata.annotations.${key}`] = value;
|
||||
}
|
||||
|
||||
const { items } = await this.catalogApi.getEntities({ filter }, options);
|
||||
// TODO(Rugvip): cache the token
|
||||
const token = await this.tokenIssuer.issueToken({
|
||||
claims: { sub: 'backstage.io/auth-backend' },
|
||||
});
|
||||
const { items } = await this.catalogApi.getEntities({ filter }, { token });
|
||||
|
||||
if (items.length !== 1) {
|
||||
if (items.length > 1) {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* 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 {
|
||||
RELATION_MEMBER_OF,
|
||||
stringifyEntityRef,
|
||||
UserEntity,
|
||||
} from '@backstage/catalog-model';
|
||||
import { TokenParams } from '../../identity';
|
||||
|
||||
export function getEntityClaims(entity: UserEntity): TokenParams['claims'] {
|
||||
const userRef = stringifyEntityRef(entity);
|
||||
|
||||
const membershipRefs =
|
||||
entity.relations
|
||||
?.filter(
|
||||
r =>
|
||||
r.type === RELATION_MEMBER_OF &&
|
||||
r.target.kind.toLocaleLowerCase('en-US') === 'group',
|
||||
)
|
||||
.map(r => stringifyEntityRef(r.target)) ?? [];
|
||||
|
||||
return {
|
||||
sub: userRef,
|
||||
ent: [userRef, ...membershipRefs],
|
||||
};
|
||||
}
|
||||
@@ -15,3 +15,4 @@
|
||||
*/
|
||||
|
||||
export { CatalogIdentityClient } from './CatalogIdentityClient';
|
||||
export { getEntityClaims } from './helpers';
|
||||
|
||||
@@ -14,5 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { createGoogleProvider } from './provider';
|
||||
export {
|
||||
createGoogleProvider,
|
||||
googleDefaultSignInResolver,
|
||||
googleEmailSignInResolver,
|
||||
} from './provider';
|
||||
export type { GoogleProviderOptions } from './provider';
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 { GoogleAuthProvider } from './provider';
|
||||
import * as helpers from '../../lib/passport/PassportStrategyHelper';
|
||||
import { OAuthResult } from '../../lib/oauth';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { TokenIssuer } from '../../identity/types';
|
||||
import { CatalogIdentityClient } from '../../lib/catalog';
|
||||
|
||||
const mockFrameHandler = (jest.spyOn(
|
||||
helpers,
|
||||
'executeFrameHandlerStrategy',
|
||||
) as unknown) as jest.MockedFunction<
|
||||
() => Promise<{ result: OAuthResult; privateInfo: any }>
|
||||
>;
|
||||
|
||||
describe('createGoogleProvider', () => {
|
||||
it('should auth', async () => {
|
||||
const tokenIssuer = {
|
||||
issueToken: jest.fn(),
|
||||
listPublicKeys: jest.fn(),
|
||||
};
|
||||
const catalogIdentityClient = {
|
||||
findUser: jest.fn(),
|
||||
};
|
||||
|
||||
const provider = new GoogleAuthProvider({
|
||||
logger: getVoidLogger(),
|
||||
catalogIdentityClient: (catalogIdentityClient as unknown) as CatalogIdentityClient,
|
||||
tokenIssuer: (tokenIssuer as unknown) as TokenIssuer,
|
||||
authHandler: async ({ fullProfile }) => ({
|
||||
profile: {
|
||||
email: fullProfile.emails![0]!.value,
|
||||
displayName: fullProfile.displayName,
|
||||
picture: 'http://google.com/lols',
|
||||
},
|
||||
}),
|
||||
clientId: 'mock',
|
||||
clientSecret: 'mock',
|
||||
callbackUrl: 'mock',
|
||||
});
|
||||
|
||||
mockFrameHandler.mockResolvedValueOnce({
|
||||
result: {
|
||||
fullProfile: {
|
||||
emails: [{ value: 'conrad@example.com' }],
|
||||
displayName: 'Conrad',
|
||||
id: 'conrad',
|
||||
provider: 'google',
|
||||
},
|
||||
params: {
|
||||
id_token: 'idToken',
|
||||
scope: 'scope',
|
||||
expires_in: 123,
|
||||
},
|
||||
accessToken: 'accessToken',
|
||||
},
|
||||
privateInfo: {
|
||||
refreshToken: 'wacka',
|
||||
},
|
||||
});
|
||||
const { response } = await provider.handler({} as any);
|
||||
expect(response).toEqual({
|
||||
providerInfo: {
|
||||
accessToken: 'accessToken',
|
||||
expiresInSeconds: 123,
|
||||
idToken: 'idToken',
|
||||
scope: 'scope',
|
||||
},
|
||||
profile: {
|
||||
email: 'conrad@example.com',
|
||||
displayName: 'Conrad',
|
||||
picture: 'http://google.com/lols',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -17,8 +17,8 @@
|
||||
import express from 'express';
|
||||
import passport from 'passport';
|
||||
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
|
||||
import { Logger } from 'winston';
|
||||
import { CatalogIdentityClient } from '../../lib/catalog';
|
||||
import { TokenIssuer } from '../../identity/types';
|
||||
import { CatalogIdentityClient, getEntityClaims } from '../../lib/catalog';
|
||||
import {
|
||||
encodeState,
|
||||
OAuthAdapter,
|
||||
@@ -27,8 +27,8 @@ import {
|
||||
OAuthProviderOptions,
|
||||
OAuthRefreshRequest,
|
||||
OAuthResponse,
|
||||
OAuthStartRequest,
|
||||
OAuthResult,
|
||||
OAuthStartRequest,
|
||||
} from '../../lib/oauth';
|
||||
import {
|
||||
executeFetchUserProfileStrategy,
|
||||
@@ -38,30 +38,40 @@ import {
|
||||
makeProfileInfo,
|
||||
PassportDoneCallback,
|
||||
} from '../../lib/passport';
|
||||
import { AuthProviderFactory, RedirectInfo } from '../types';
|
||||
import { TokenIssuer } from '../../identity/types';
|
||||
import {
|
||||
AuthProviderFactory,
|
||||
AuthHandler,
|
||||
RedirectInfo,
|
||||
SignInResolver,
|
||||
} from '../types';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
type PrivateInfo = {
|
||||
refreshToken: string;
|
||||
};
|
||||
|
||||
type Options = OAuthProviderOptions & {
|
||||
logger: Logger;
|
||||
identityClient: CatalogIdentityClient;
|
||||
signInResolver?: SignInResolver<OAuthResult>;
|
||||
authHandler: AuthHandler<OAuthResult>;
|
||||
tokenIssuer: TokenIssuer;
|
||||
catalogIdentityClient: CatalogIdentityClient;
|
||||
logger: Logger;
|
||||
};
|
||||
|
||||
export class GoogleAuthProvider implements OAuthHandlers {
|
||||
private readonly _strategy: GoogleStrategy;
|
||||
private readonly logger: Logger;
|
||||
private readonly identityClient: CatalogIdentityClient;
|
||||
private readonly signInResolver?: SignInResolver<OAuthResult>;
|
||||
private readonly authHandler: AuthHandler<OAuthResult>;
|
||||
private readonly tokenIssuer: TokenIssuer;
|
||||
private readonly catalogIdentityClient: CatalogIdentityClient;
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(options: Options) {
|
||||
this.logger = options.logger;
|
||||
this.identityClient = options.identityClient;
|
||||
this.signInResolver = options.signInResolver;
|
||||
this.authHandler = options.authHandler;
|
||||
this.tokenIssuer = options.tokenIssuer;
|
||||
// TODO: throw error if env variables not set?
|
||||
this.catalogIdentityClient = options.catalogIdentityClient;
|
||||
this.logger = options.logger;
|
||||
this._strategy = new GoogleStrategy(
|
||||
{
|
||||
clientID: options.clientId,
|
||||
@@ -111,18 +121,8 @@ export class GoogleAuthProvider implements OAuthHandlers {
|
||||
PrivateInfo
|
||||
>(req, this._strategy);
|
||||
|
||||
const profile = makeProfileInfo(result.fullProfile, result.params.id_token);
|
||||
|
||||
return {
|
||||
response: await this.populateIdentity({
|
||||
providerInfo: {
|
||||
idToken: result.params.id_token,
|
||||
accessToken: result.accessToken,
|
||||
scope: result.params.scope,
|
||||
expiresInSeconds: result.params.expires_in,
|
||||
},
|
||||
profile,
|
||||
}),
|
||||
response: await this.handleResult(result),
|
||||
refreshToken: privateInfo.refreshToken,
|
||||
};
|
||||
}
|
||||
@@ -133,89 +133,170 @@ export class GoogleAuthProvider implements OAuthHandlers {
|
||||
req.refreshToken,
|
||||
req.scope,
|
||||
);
|
||||
|
||||
const fullProfile = await executeFetchUserProfileStrategy(
|
||||
this._strategy,
|
||||
accessToken,
|
||||
);
|
||||
const profile = makeProfileInfo(fullProfile, params.id_token);
|
||||
|
||||
return this.populateIdentity({
|
||||
providerInfo: {
|
||||
accessToken,
|
||||
idToken: params.id_token,
|
||||
expiresInSeconds: params.expires_in,
|
||||
scope: params.scope,
|
||||
},
|
||||
profile,
|
||||
return this.handleResult({
|
||||
fullProfile,
|
||||
params,
|
||||
accessToken,
|
||||
refreshToken: req.refreshToken,
|
||||
});
|
||||
}
|
||||
|
||||
private async populateIdentity(
|
||||
response: OAuthResponse,
|
||||
): Promise<OAuthResponse> {
|
||||
const { profile } = response;
|
||||
private async handleResult(result: OAuthResult) {
|
||||
const { profile } = await this.authHandler(result);
|
||||
|
||||
if (!profile.email) {
|
||||
throw new Error('Google profile contained no email');
|
||||
}
|
||||
const response: OAuthResponse = {
|
||||
providerInfo: {
|
||||
idToken: result.params.id_token,
|
||||
accessToken: result.accessToken,
|
||||
scope: result.params.scope,
|
||||
expiresInSeconds: result.params.expires_in,
|
||||
},
|
||||
profile,
|
||||
};
|
||||
|
||||
try {
|
||||
const token = await this.tokenIssuer.issueToken({
|
||||
claims: { sub: 'backstage.io/auth-backend' },
|
||||
});
|
||||
const user = await this.identityClient.findUser(
|
||||
if (this.signInResolver) {
|
||||
response.backstageIdentity = await this.signInResolver(
|
||||
{
|
||||
annotations: {
|
||||
'google.com/email': profile.email,
|
||||
},
|
||||
result,
|
||||
profile,
|
||||
},
|
||||
{ token },
|
||||
);
|
||||
|
||||
return {
|
||||
...response,
|
||||
backstageIdentity: {
|
||||
id: user.metadata.name,
|
||||
{
|
||||
tokenIssuer: this.tokenIssuer,
|
||||
catalogIdentityClient: this.catalogIdentityClient,
|
||||
logger: this.logger,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.warn(
|
||||
`Failed to look up user, ${error}, falling back to allowing login based on email pattern, this will probably break in the future`,
|
||||
);
|
||||
return {
|
||||
...response,
|
||||
backstageIdentity: { id: profile.email.split('@')[0] },
|
||||
};
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
export type GoogleProviderOptions = {};
|
||||
export const googleEmailSignInResolver: SignInResolver<OAuthResult> = async (
|
||||
info,
|
||||
ctx,
|
||||
) => {
|
||||
const { profile } = info;
|
||||
|
||||
if (!profile.email) {
|
||||
throw new Error('Google profile contained no email');
|
||||
}
|
||||
|
||||
const entity = await ctx.catalogIdentityClient.findUser({
|
||||
annotations: {
|
||||
'google.com/email': profile.email,
|
||||
},
|
||||
});
|
||||
|
||||
const claims = getEntityClaims(entity);
|
||||
const token = await ctx.tokenIssuer.issueToken({ claims });
|
||||
|
||||
return { id: entity.metadata.name, entity, token };
|
||||
};
|
||||
|
||||
export const googleDefaultSignInResolver: SignInResolver<OAuthResult> = async (
|
||||
info,
|
||||
ctx,
|
||||
) => {
|
||||
const { profile } = info;
|
||||
|
||||
if (!profile.email) {
|
||||
throw new Error('Google profile contained no email');
|
||||
}
|
||||
|
||||
let userId: string;
|
||||
try {
|
||||
const entity = await ctx.catalogIdentityClient.findUser({
|
||||
annotations: {
|
||||
'google.com/email': profile.email,
|
||||
},
|
||||
});
|
||||
userId = entity.metadata.name;
|
||||
} catch (error) {
|
||||
ctx.logger.warn(
|
||||
`Failed to look up user, ${error}, falling back to allowing login based on email pattern, this will probably break in the future`,
|
||||
);
|
||||
userId = profile.email.split('@')[0];
|
||||
}
|
||||
|
||||
const token = await ctx.tokenIssuer.issueToken({
|
||||
claims: { sub: userId, ent: [`user:default/${userId}`] },
|
||||
});
|
||||
|
||||
return { id: userId, token };
|
||||
};
|
||||
|
||||
export type GoogleProviderOptions = {
|
||||
/**
|
||||
* The profile transformation function used to verify and convert the auth response
|
||||
* into the profile that will be presented to the user.
|
||||
*/
|
||||
authHandler?: AuthHandler<OAuthResult>;
|
||||
|
||||
/**
|
||||
* Configure sign-in for this provider, without it the provider can not be used to sign users in.
|
||||
*/
|
||||
/**
|
||||
* Maps an auth result to a Backstage identity for the user.
|
||||
*
|
||||
* Set to `'email'` to use the default email-based sign in resolver, which will search
|
||||
* the catalog for a single user entity that has a matching `google.com/email` annotation.
|
||||
*/
|
||||
signIn?: {
|
||||
resolver?: SignInResolver<OAuthResult>;
|
||||
};
|
||||
};
|
||||
|
||||
export const createGoogleProvider = (
|
||||
_options?: GoogleProviderOptions,
|
||||
options?: GoogleProviderOptions,
|
||||
): AuthProviderFactory => {
|
||||
return ({
|
||||
providerId,
|
||||
globalConfig,
|
||||
config,
|
||||
logger,
|
||||
tokenIssuer,
|
||||
catalogApi,
|
||||
logger,
|
||||
}) =>
|
||||
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
|
||||
const clientId = envConfig.getString('clientId');
|
||||
const clientSecret = envConfig.getString('clientSecret');
|
||||
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
||||
|
||||
const catalogIdentityClient = new CatalogIdentityClient({
|
||||
catalogApi,
|
||||
tokenIssuer,
|
||||
});
|
||||
|
||||
const authHandler: AuthHandler<OAuthResult> = options?.authHandler
|
||||
? options.authHandler
|
||||
: async ({ fullProfile, params }) => ({
|
||||
profile: makeProfileInfo(fullProfile, params.id_token),
|
||||
});
|
||||
|
||||
const signInResolverFn =
|
||||
options?.signIn?.resolver ?? googleDefaultSignInResolver;
|
||||
|
||||
const signInResolver: SignInResolver<OAuthResult> = info =>
|
||||
signInResolverFn(info, {
|
||||
catalogIdentityClient,
|
||||
tokenIssuer,
|
||||
logger,
|
||||
});
|
||||
|
||||
const provider = new GoogleAuthProvider({
|
||||
clientId,
|
||||
clientSecret,
|
||||
callbackUrl,
|
||||
logger,
|
||||
signInResolver,
|
||||
authHandler,
|
||||
tokenIssuer,
|
||||
identityClient: new CatalogIdentityClient({ catalogApi }),
|
||||
catalogIdentityClient,
|
||||
logger,
|
||||
});
|
||||
|
||||
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './google';
|
||||
export { factories as defaultAuthProviderFactories } from './factories';
|
||||
|
||||
// Export the minimal interface required for implementing a
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
|
||||
import { PluginEndpointDiscovery } from '@backstage/backend-common';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import { Logger } from 'winston';
|
||||
import { TokenIssuer } from '../identity/types';
|
||||
import { CatalogIdentityClient } from '../lib/catalog';
|
||||
|
||||
export type AuthProviderConfig = {
|
||||
/**
|
||||
@@ -148,14 +150,30 @@ export type AuthResponse<ProviderInfo> = {
|
||||
|
||||
export type BackstageIdentity = {
|
||||
/**
|
||||
* The backstage user ID.
|
||||
* An opaque ID that uniquely identifies the user within Backstage.
|
||||
*
|
||||
* This is typically the same as the user entity `metadata.name`.
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* An ID token that can be used to authenticate the user within Backstage.
|
||||
* This is deprecated, use `token` instead.
|
||||
* @deprecated
|
||||
*/
|
||||
idToken?: string;
|
||||
|
||||
/**
|
||||
* The token used to authenticate the user within Backstage.
|
||||
*/
|
||||
token?: string;
|
||||
|
||||
/**
|
||||
* The entity that the user is represented by within Backstage.
|
||||
*
|
||||
* This entity may or may not exist within the Catalog, and it can be used
|
||||
* to read and store additional metadata about the user.
|
||||
*/
|
||||
entity?: Entity;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -179,3 +197,38 @@ export type ProfileInfo = {
|
||||
*/
|
||||
picture?: string;
|
||||
};
|
||||
|
||||
export type SignInInfo<AuthResult> = {
|
||||
/**
|
||||
* The simple profile passed down for use in the frontend.
|
||||
*/
|
||||
profile: ProfileInfo;
|
||||
|
||||
/**
|
||||
* The authentication result that was received from the authentication provider.
|
||||
*/
|
||||
result: AuthResult;
|
||||
};
|
||||
|
||||
export type SignInResolver<AuthResult> = (
|
||||
info: SignInInfo<AuthResult>,
|
||||
context: {
|
||||
tokenIssuer: TokenIssuer;
|
||||
catalogIdentityClient: CatalogIdentityClient;
|
||||
logger: Logger;
|
||||
},
|
||||
) => Promise<BackstageIdentity>;
|
||||
|
||||
export type AuthHandlerResult = { profile: ProfileInfo };
|
||||
|
||||
/**
|
||||
* The AuthHandler function is called every time the user authenticates using the provider.
|
||||
*
|
||||
* The handler should return a profile that represents the session for the user in the frontend.
|
||||
*
|
||||
* Throwing an error in the function will cause the authentication to fail, making it
|
||||
* possible to use this function as a way to limit access to a certain group of users.
|
||||
*/
|
||||
export type AuthHandler<AuthResult> = (
|
||||
input: AuthResult,
|
||||
) => Promise<AuthHandlerResult>;
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
## API Report File for "@backstage/plugin-badges-backend"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { Config } from '@backstage/config';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import express from 'express';
|
||||
import { PluginEndpointDiscovery } from '@backstage/backend-common';
|
||||
|
||||
// @public (undocumented)
|
||||
export interface Badge {
|
||||
color?: string;
|
||||
description?: string;
|
||||
kind?: 'entity';
|
||||
label: string;
|
||||
labelColor?: string;
|
||||
link?: string;
|
||||
message: string;
|
||||
style?: BadgeStyle;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const BADGE_STYLES: readonly ["plastic", "flat", "flat-square", "for-the-badge", "social"];
|
||||
|
||||
// @public (undocumented)
|
||||
export type BadgeBuilder = {
|
||||
getBadges(): Promise<BadgeInfo[]>;
|
||||
createBadgeJson(options: BadgeOptions): Promise<BadgeSpec>;
|
||||
createBadgeSvg(options: BadgeOptions): Promise<string>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface BadgeContext {
|
||||
// (undocumented)
|
||||
badgeUrl: string;
|
||||
// (undocumented)
|
||||
config: Config;
|
||||
// (undocumented)
|
||||
entity?: Entity;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface BadgeFactories {
|
||||
// (undocumented)
|
||||
[id: string]: BadgeFactory;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface BadgeFactory {
|
||||
// (undocumented)
|
||||
createBadge(context: BadgeContext): Badge;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type BadgeInfo = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type BadgeOptions = {
|
||||
badgeInfo: BadgeInfo;
|
||||
context: BadgeContext;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type BadgeSpec = {
|
||||
id: string;
|
||||
badge: Badge;
|
||||
url: string;
|
||||
markdown: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type BadgeStyle = typeof BADGE_STYLES[number];
|
||||
|
||||
// @public (undocumented)
|
||||
export const createDefaultBadgeFactories: () => BadgeFactories;
|
||||
|
||||
// @public (undocumented)
|
||||
export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class DefaultBadgeBuilder implements BadgeBuilder {
|
||||
constructor(factories: BadgeFactories);
|
||||
// (undocumented)
|
||||
createBadgeJson(options: BadgeOptions): Promise<BadgeSpec>;
|
||||
// (undocumented)
|
||||
createBadgeSvg(options: BadgeOptions): Promise<string>;
|
||||
// (undocumented)
|
||||
getBadges(): Promise<BadgeInfo[]>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RouterOptions {
|
||||
// (undocumented)
|
||||
badgeBuilder?: BadgeBuilder;
|
||||
// (undocumented)
|
||||
badgeFactories?: BadgeFactories;
|
||||
// (undocumented)
|
||||
catalog?: CatalogApi;
|
||||
// (undocumented)
|
||||
config: Config;
|
||||
// (undocumented)
|
||||
discovery: PluginEndpointDiscovery;
|
||||
}
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
## API Report File for "@backstage/plugin-badges"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export const badgesPlugin: BackstagePlugin<{}, {}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityBadgesDialog: ({ open, onClose }: {
|
||||
open: boolean;
|
||||
onClose?: (() => any) | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,22 @@
|
||||
## API Report File for "@backstage/plugin-bitrise"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
// @public (undocumented)
|
||||
export const bitrisePlugin: BackstagePlugin<{}, {}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityBitriseContent: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const isBitriseAvailable: (entity: Entity) => boolean;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -1,21 +1,80 @@
|
||||
# Catalog Backend
|
||||
|
||||
This is the backend part of the default catalog plugin.
|
||||
This is the backend for the default Backstage [software
|
||||
catalog](http://backstage.io/docs/features/software-catalog/software-catalog-overview).
|
||||
This provides an API for consumers such as the frontend [catalog
|
||||
plugin](https://github.com/backstage/backstage/tree/master/plugins/catalog).
|
||||
|
||||
It comes with a builtin database backed implementation of the catalog, that can store
|
||||
and serve your catalog for you.
|
||||
It comes with a builtin database-backed implementation of the catalog that can
|
||||
store and serve your catalog for you.
|
||||
|
||||
It can also act as a bridge to your existing catalog solutions, either ingesting their
|
||||
data to store in the database, or by effectively proxying calls to an external catalog
|
||||
service.
|
||||
It can also act as a bridge to your existing catalog solutions, either ingesting
|
||||
data to store in the database, or by effectively proxying calls to an
|
||||
external catalog service.
|
||||
|
||||
## Getting Started
|
||||
## Installation
|
||||
|
||||
This backend plugin can be started in a standalone mode from directly in this package
|
||||
with `yarn start`. However, it will have limited functionality and that process is
|
||||
most convenient when developing the catalog backend plugin itself.
|
||||
This `@backstage/plugin-catalog-backend` package comes installed by default in
|
||||
any Backstage application created with `npx @backstage/create-app`, so
|
||||
installation is not usually required.
|
||||
|
||||
To evaluate the catalog and have a greater amount of functionality available, instead do
|
||||
To check if you already have the package, look under
|
||||
`packages/backend/package.json`, in the `dependencies` block, for
|
||||
`@backstage/plugin-catalog-backend`. The instructions below walk through
|
||||
restoring the plugin, if you previously removed it.
|
||||
|
||||
### Install the package
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
cd packages/backend
|
||||
yarn add @backstage/plugin-catalog-backend
|
||||
```
|
||||
|
||||
### Adding the plugin to your `packages/backend`
|
||||
|
||||
You'll need to add the plugin to the router in your `backend` package. You can
|
||||
do this by creating a file called `packages/backend/src/plugins/catalog.ts` with
|
||||
contents matching [catalog.ts in the create-app
|
||||
template](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/packages/backend/src/plugins/catalog.ts).
|
||||
|
||||
With the `catalog.ts` router setup in place, add the router to
|
||||
`packages/backend/src/index.ts`:
|
||||
|
||||
```diff
|
||||
+import catalog from './plugins/catalog';
|
||||
|
||||
async function main() {
|
||||
...
|
||||
const createEnv = makeCreateEnv(config);
|
||||
|
||||
+ const catalogEnv = useHotMemoize(module, () => createEnv('catalog'));
|
||||
const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder'));
|
||||
|
||||
const apiRouter = Router();
|
||||
+ apiRouter.use('/catalog', await catalog(catalogEnv));
|
||||
...
|
||||
apiRouter.use(notFoundHandler());
|
||||
|
||||
```
|
||||
|
||||
### Adding catalog entities
|
||||
|
||||
At this point the `catalog-backend` is installed in your backend package, but
|
||||
you will not have any catalog entities loaded. See [Catalog
|
||||
Configuration](https://backstage.io/docs/features/software-catalog/configuration)
|
||||
for how to add locations, or copy the catalog locations from the [create-app
|
||||
template](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/app-config.yaml.hbs)
|
||||
to get up and running quickly.
|
||||
|
||||
## Development
|
||||
|
||||
This backend plugin can be started in a standalone mode from directly in this
|
||||
package with `yarn start`. However, it will have limited functionality and that
|
||||
process is most convenient when developing the catalog backend plugin itself.
|
||||
|
||||
To evaluate the catalog and have a greater amount of functionality available,
|
||||
run the entire Backstage example application from the root folder:
|
||||
|
||||
```bash
|
||||
# in one terminal window, run this from from the very root of the Backstage project
|
||||
@@ -23,9 +82,10 @@ cd packages/backend
|
||||
yarn start
|
||||
```
|
||||
|
||||
This will launch the full example backend, populated some example entities.
|
||||
This will launch both frontend and backend in the same window, populated with
|
||||
some example entities.
|
||||
|
||||
## Links
|
||||
|
||||
- [Frontend part of the plugin](https://github.com/backstage/backstage/tree/master/plugins/catalog)
|
||||
- [The Backstage homepage](https://backstage.io)
|
||||
- [catalog](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend)
|
||||
is the frontend interface for this plugin.
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
## API Report File for "@backstage/plugin-catalog-graphql"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import { GraphQLModule } from '@graphql-modules/core';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
// @public (undocumented)
|
||||
export function createModule(options: ModuleOptions): Promise<GraphQLModule>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ModuleOptions {
|
||||
// (undocumented)
|
||||
config: Config;
|
||||
// (undocumented)
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,130 @@
|
||||
## API Report File for "@backstage/plugin-catalog-import"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { ConfigApi } from '@backstage/core';
|
||||
import { Control } from 'react-hook-form';
|
||||
import { DiscoveryApi } from '@backstage/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityName } from '@backstage/catalog-model';
|
||||
import { FieldErrors } from 'react-hook-form';
|
||||
import { IdentityApi } from '@backstage/core';
|
||||
import { InfoCardVariants } from '@backstage/core';
|
||||
import { OAuthApi } from '@backstage/core';
|
||||
import { default as React_2 } from 'react';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { SubmitHandler } from 'react-hook-form';
|
||||
import { TextFieldProps } from '@material-ui/core/TextField/TextField';
|
||||
import { UnpackNestedValue } from 'react-hook-form';
|
||||
import { UseControllerOptions } from 'react-hook-form';
|
||||
import { UseFormMethods } from 'react-hook-form';
|
||||
import { UseFormOptions } from 'react-hook-form';
|
||||
|
||||
// @public (undocumented)
|
||||
export type AnalyzeResult = {
|
||||
type: 'locations';
|
||||
locations: Array<{
|
||||
target: string;
|
||||
entities: EntityName[];
|
||||
}>;
|
||||
} | {
|
||||
type: 'repository';
|
||||
url: string;
|
||||
integrationType: string;
|
||||
generatedEntities: PartialEntity[];
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const AutocompleteTextField: <TFieldValue extends string>({ name, options, required, control, errors, rules, loading, loadingText, helperText, errorHelperText, textFieldProps, }: Props_4<TFieldValue>) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CatalogImportApi {
|
||||
// (undocumented)
|
||||
analyzeUrl(url: string): Promise<AnalyzeResult>;
|
||||
// (undocumented)
|
||||
submitPullRequest(options: {
|
||||
repositoryUrl: string;
|
||||
fileContent: string;
|
||||
title: string;
|
||||
body: string;
|
||||
}): Promise<{
|
||||
link: string;
|
||||
location: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const catalogImportApiRef: ApiRef<CatalogImportApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class CatalogImportClient implements CatalogImportApi {
|
||||
constructor(options: {
|
||||
discoveryApi: DiscoveryApi;
|
||||
githubAuthApi: OAuthApi;
|
||||
identityApi: IdentityApi;
|
||||
scmIntegrationsApi: ScmIntegrationRegistry;
|
||||
catalogApi: CatalogApi;
|
||||
});
|
||||
// (undocumented)
|
||||
analyzeUrl(url: string): Promise<AnalyzeResult>;
|
||||
// (undocumented)
|
||||
submitPullRequest({ repositoryUrl, fileContent, title, body, }: {
|
||||
repositoryUrl: string;
|
||||
fileContent: string;
|
||||
title: string;
|
||||
body: string;
|
||||
}): Promise<{
|
||||
link: string;
|
||||
location: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const CatalogImportPage: (opts: StepperProviderOpts) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
const catalogImportPlugin: BackstagePlugin<{
|
||||
importPage: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
export { catalogImportPlugin }
|
||||
|
||||
export { catalogImportPlugin as plugin }
|
||||
|
||||
// @public
|
||||
export function defaultGenerateStepper(flow: ImportFlows, defaults: StepperProvider): StepperProvider;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityListComponent: ({ locations, collapsed, locationListItemIcon, onItemClick, firstListItem, withLinks, }: Props_2) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ImportStepper: ({ initialUrl, generateStepper, variant, opts, }: Props) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export const PreparePullRequestForm: <TFieldValues extends Record<string, any>>({ defaultValues, onSubmit, render, }: Props_5<TFieldValues>) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const PreviewCatalogInfoComponent: ({ repositoryUrl, entities, classes, }: Props_6) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const PreviewPullRequestComponent: ({ title, description, classes, }: Props_7) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const Router: (opts: StepperProviderOpts) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export const StepInitAnalyzeUrl: ({ onAnalysis, analysisUrl, disablePullRequest, }: Props_3) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const StepPrepareCreatePullRequest: ({ analyzeResult, onPrepare, onGoBack, renderFormFields, defaultTitle, defaultBody, }: Props_8) => JSX.Element;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
+93
-12
@@ -1,26 +1,107 @@
|
||||
# Backstage Catalog Frontend
|
||||
|
||||
This is the frontend part of the default catalog plugin.
|
||||
This is the React frontend for the default Backstage [software
|
||||
catalog](http://backstage.io/docs/features/software-catalog/software-catalog-overview).
|
||||
This package supplies interfaces related to listing catalog entities or showing
|
||||
more information about them on entity pages.
|
||||
|
||||
It will implement the core API for handling your catalog of software, and
|
||||
supply the base views to show and manage them.
|
||||
## Installation
|
||||
|
||||
## Getting Started
|
||||
This `@backstage/plugin-catalog` package comes installed by default in any
|
||||
Backstage application created with `npx @backstage/create-app`, so installation
|
||||
is not usually required.
|
||||
|
||||
This frontend plugin can be started in a standalone mode from directly in this package
|
||||
with `yarn start`. However, it will have limited functionality and that process is
|
||||
most convenient when developing the catalog frontend plugin itself.
|
||||
To check if you already have the package, look under
|
||||
`packages/app/package.json`, in the `dependencies` block, for
|
||||
`@backstage/plugin-catalog`. The instructions below walk through restoring the
|
||||
plugin, if you previously removed it.
|
||||
|
||||
To evaluate the catalog and have a greater amount of functionality available, from the main
|
||||
Backstage root folder, instead do:
|
||||
### Install the package
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
cd packages/app
|
||||
yarn add @backstage/plugin-catalog
|
||||
```
|
||||
|
||||
### Add the plugin to your `packages/app`
|
||||
|
||||
Add the two pages that the catalog plugin provides to your app. You can choose
|
||||
any name for these routes, but we recommend the following:
|
||||
|
||||
```diff
|
||||
// packages/app/src/App.tsx
|
||||
import {
|
||||
CatalogIndexPage,
|
||||
CatalogEntityPage,
|
||||
} from '@backstage/plugin-catalog';
|
||||
import { entityPage } from './components/catalog/EntityPage';
|
||||
|
||||
<FlatRoutes>
|
||||
+ <Route path="/catalog" element={<CatalogIndexPage />} />
|
||||
+ <Route path="/catalog/:namespace/:kind/:name" element={<CatalogEntityPage />}>
|
||||
+ {/*
|
||||
+ This is the root of the custom entity pages for your app, refer to the example app
|
||||
+ in the main repo or the output of @backstage/create-app for an example
|
||||
+ */}
|
||||
+ {entityPage}
|
||||
+ </Route>
|
||||
...
|
||||
</FlatRoutes>
|
||||
```
|
||||
|
||||
The catalog plugin also has one external route that needs to be bound for it to
|
||||
function: the `createComponent` route which should link to the page where the
|
||||
user can create components. In a typical setup the create component route will
|
||||
be linked to the scaffolder plugin's template index page:
|
||||
|
||||
```diff
|
||||
// packages/app/src/App.tsx
|
||||
+import { catalogPlugin } from '@backstage/plugin-catalog';
|
||||
+import { scaffolderPlugin } from '@backstage/plugin-scaffolder';
|
||||
|
||||
const app = createApp({
|
||||
// ...
|
||||
bindRoutes({ bind }) {
|
||||
+ bind(catalogPlugin.externalRoutes, {
|
||||
+ createComponent: scaffolderPlugin.routes.root,
|
||||
+ });
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
You may also want to add a link to the catalog index page to your application
|
||||
sidebar:
|
||||
|
||||
```diff
|
||||
// packages/app/src/components/Root/Root.tsx
|
||||
+import HomeIcon from '@material-ui/icons/Home';
|
||||
|
||||
export const Root = ({ children }: PropsWithChildren<{}>) => (
|
||||
<SidebarPage>
|
||||
<Sidebar>
|
||||
+ <SidebarItem icon={HomeIcon} to="/catalog" text="Home" />
|
||||
...
|
||||
</Sidebar>
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
This frontend plugin can be started in a standalone mode from directly in this
|
||||
package with `yarn start`. However, it will have limited functionality and that
|
||||
process is most convenient when developing the catalog frontend plugin itself.
|
||||
|
||||
To evaluate the catalog and have a greater amount of functionality available,
|
||||
run the entire Backstage example application from the root folder:
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
```
|
||||
|
||||
This will launch both frontend and backend in the same window, populated with some example entities.
|
||||
This will launch both frontend and backend in the same window, populated with
|
||||
some example entities.
|
||||
|
||||
## Links
|
||||
|
||||
- [Backend part of the plugin](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend)
|
||||
- [The Backstage homepage](https://backstage.io)
|
||||
- [catalog-backend](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend)
|
||||
provides the backend API for this frontend.
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"@backstage/catalog-client": "^0.3.13",
|
||||
"@backstage/catalog-model": "^0.8.3",
|
||||
"@backstage/core": "^0.7.13",
|
||||
"@backstage/core-plugin-api": "^0.1.2",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/integration": "^0.5.6",
|
||||
"@backstage/integration-react": "^0.1.3",
|
||||
@@ -54,6 +55,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.1",
|
||||
"@backstage/core-app-api": "^0.1.2",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
Progress,
|
||||
RoutedTabs,
|
||||
} from '@backstage/core';
|
||||
import { useElementFilter } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
EntityContext,
|
||||
EntityRefLinks,
|
||||
@@ -37,14 +38,7 @@ import {
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { Box, TabProps } from '@material-ui/core';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import {
|
||||
Children,
|
||||
default as React,
|
||||
Fragment,
|
||||
isValidElement,
|
||||
useContext,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu';
|
||||
import { FavouriteEntity } from '../FavouriteEntity/FavouriteEntity';
|
||||
@@ -58,46 +52,14 @@ type SubRoute = {
|
||||
tabProps?: TabProps<React.ElementType, { component?: React.ElementType }>;
|
||||
};
|
||||
|
||||
const dataKey = 'plugin.catalog.entityLayoutRoute';
|
||||
|
||||
const Route: (props: SubRoute) => null = () => null;
|
||||
attachComponentData(Route, dataKey, true);
|
||||
|
||||
// This causes all mount points that are discovered within this route to use the path of the route itself
|
||||
attachComponentData(Route, 'core.gatherMountPoints', true);
|
||||
|
||||
function createSubRoutesFromChildren(
|
||||
childrenProps: React.ReactNode,
|
||||
entity: Entity | undefined,
|
||||
): SubRoute[] {
|
||||
// Directly comparing child.type with Route will not work with in
|
||||
// combination with react-hot-loader in storybook
|
||||
// https://github.com/gaearon/react-hot-loader/issues/304
|
||||
const routeType = (
|
||||
<Route path="" title="">
|
||||
<div />
|
||||
</Route>
|
||||
).type;
|
||||
|
||||
return Children.toArray(childrenProps).flatMap(child => {
|
||||
if (!isValidElement(child)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (child.type === Fragment) {
|
||||
return createSubRoutesFromChildren(child.props.children, entity);
|
||||
}
|
||||
|
||||
if (child.type !== routeType) {
|
||||
throw new Error('Child of EntityLayout must be an EntityLayout.Route');
|
||||
}
|
||||
|
||||
const { path, title, children, if: condition, tabProps } = child.props;
|
||||
if (condition && entity && !condition(entity)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [{ path, title, children, tabProps }];
|
||||
});
|
||||
}
|
||||
|
||||
const EntityLayoutTitle = ({
|
||||
entity,
|
||||
title,
|
||||
@@ -195,7 +157,29 @@ export const EntityLayout = ({
|
||||
const { kind, namespace, name } = useEntityCompoundName();
|
||||
const { entity, loading, error } = useContext(EntityContext);
|
||||
|
||||
const routes = createSubRoutesFromChildren(children, entity);
|
||||
const routes = useElementFilter(children, elements =>
|
||||
elements
|
||||
.selectByComponentData({
|
||||
key: dataKey,
|
||||
withStrictError: 'Child of EntityLayout must be an EntityLayout.Route',
|
||||
})
|
||||
.getElements<SubRoute>() // all nodes, element data, maintain structure or not?
|
||||
.flatMap(({ props }) => {
|
||||
if (props.if && entity && !props.if(entity)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
path: props.path,
|
||||
title: props.title,
|
||||
children: props.children,
|
||||
tabProps: props.tabProps,
|
||||
},
|
||||
];
|
||||
}),
|
||||
);
|
||||
|
||||
const { headerTitle, headerType } = headerProps(
|
||||
kind,
|
||||
namespace,
|
||||
|
||||
@@ -20,6 +20,19 @@ import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { isKind } from './conditions';
|
||||
import { EntitySwitch } from './EntitySwitch';
|
||||
import { featureFlagsApiRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
LocalStorageFeatureFlags,
|
||||
ApiProvider,
|
||||
ApiRegistry,
|
||||
} from '@backstage/core-app-api';
|
||||
|
||||
const mockFeatureFlagsApi = new LocalStorageFeatureFlags();
|
||||
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
|
||||
<ApiProvider apis={ApiRegistry.with(featureFlagsApiRef, mockFeatureFlagsApi)}>
|
||||
{children}
|
||||
</ApiProvider>
|
||||
);
|
||||
|
||||
describe('EntitySwitch', () => {
|
||||
it('should switch child when entity switches', () => {
|
||||
@@ -32,15 +45,17 @@ describe('EntitySwitch', () => {
|
||||
);
|
||||
|
||||
const rendered = render(
|
||||
<EntityContext.Provider
|
||||
value={{
|
||||
entity: { kind: 'component' } as Entity,
|
||||
loading: false,
|
||||
error: undefined,
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</EntityContext.Provider>,
|
||||
<Wrapper>
|
||||
<EntityContext.Provider
|
||||
value={{
|
||||
entity: { kind: 'component' } as Entity,
|
||||
loading: false,
|
||||
error: undefined,
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</EntityContext.Provider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
expect(rendered.queryByText('A')).toBeInTheDocument();
|
||||
@@ -48,15 +63,17 @@ describe('EntitySwitch', () => {
|
||||
expect(rendered.queryByText('C')).not.toBeInTheDocument();
|
||||
|
||||
rendered.rerender(
|
||||
<EntityContext.Provider
|
||||
value={{
|
||||
entity: { kind: 'template' } as Entity,
|
||||
loading: false,
|
||||
error: undefined,
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</EntityContext.Provider>,
|
||||
<Wrapper>
|
||||
<EntityContext.Provider
|
||||
value={{
|
||||
entity: { kind: 'template' } as Entity,
|
||||
loading: false,
|
||||
error: undefined,
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</EntityContext.Provider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
expect(rendered.queryByText('A')).not.toBeInTheDocument();
|
||||
@@ -64,15 +81,17 @@ describe('EntitySwitch', () => {
|
||||
expect(rendered.queryByText('C')).not.toBeInTheDocument();
|
||||
|
||||
rendered.rerender(
|
||||
<EntityContext.Provider
|
||||
value={{
|
||||
entity: { kind: 'derp' } as Entity,
|
||||
loading: false,
|
||||
error: undefined,
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</EntityContext.Provider>,
|
||||
<Wrapper>
|
||||
<EntityContext.Provider
|
||||
value={{
|
||||
entity: { kind: 'derp' } as Entity,
|
||||
loading: false,
|
||||
error: undefined,
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</EntityContext.Provider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
expect(rendered.queryByText('A')).not.toBeInTheDocument();
|
||||
@@ -88,24 +107,28 @@ describe('EntitySwitch', () => {
|
||||
};
|
||||
|
||||
const rendered = render(
|
||||
<EntityContext.Provider value={entityContextValue}>
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={isKind('component')} children="A" />
|
||||
<EntitySwitch.Case children="B" />
|
||||
</EntitySwitch>
|
||||
</EntityContext.Provider>,
|
||||
<Wrapper>
|
||||
<EntityContext.Provider value={entityContextValue}>
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={isKind('component')} children="A" />
|
||||
<EntitySwitch.Case children="B" />
|
||||
</EntitySwitch>
|
||||
</EntityContext.Provider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
expect(rendered.queryByText('A')).toBeInTheDocument();
|
||||
expect(rendered.queryByText('B')).not.toBeInTheDocument();
|
||||
|
||||
rendered.rerender(
|
||||
<EntityContext.Provider value={entityContextValue}>
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={isKind('template')} children="A" />
|
||||
<EntitySwitch.Case children="B" />
|
||||
</EntitySwitch>
|
||||
</EntityContext.Provider>,
|
||||
<Wrapper>
|
||||
<EntityContext.Provider value={entityContextValue}>
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={isKind('template')} children="A" />
|
||||
<EntitySwitch.Case children="B" />
|
||||
</EntitySwitch>
|
||||
</EntityContext.Provider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
expect(rendered.queryByText('A')).not.toBeInTheDocument();
|
||||
|
||||
@@ -16,49 +16,40 @@
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import { PropsWithChildren, ReactNode } from 'react';
|
||||
import {
|
||||
Children,
|
||||
Fragment,
|
||||
isValidElement,
|
||||
PropsWithChildren,
|
||||
ReactNode,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
attachComponentData,
|
||||
useElementFilter,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
const ENTITY_SWITCH_KEY = 'core.backstage.entitySwitch';
|
||||
|
||||
const EntitySwitchCase = (_: {
|
||||
if?: (entity: Entity) => boolean;
|
||||
children: ReactNode;
|
||||
}) => null;
|
||||
|
||||
attachComponentData(EntitySwitchCase, ENTITY_SWITCH_KEY, true);
|
||||
|
||||
type SwitchCase = {
|
||||
if?: (entity: Entity) => boolean;
|
||||
children: JSX.Element;
|
||||
};
|
||||
|
||||
function createSwitchCasesFromChildren(childrenNode: ReactNode): SwitchCase[] {
|
||||
return Children.toArray(childrenNode).flatMap(child => {
|
||||
if (!isValidElement(child)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (child.type === Fragment) {
|
||||
return createSwitchCasesFromChildren(child.props.children);
|
||||
}
|
||||
|
||||
if (child.type !== EntitySwitchCase) {
|
||||
throw new Error(`Child of EntitySwitch is not an EntitySwitch.Case`);
|
||||
}
|
||||
|
||||
const { if: condition, children } = child.props;
|
||||
return [{ if: condition, children }];
|
||||
});
|
||||
}
|
||||
|
||||
export const EntitySwitch = ({ children }: PropsWithChildren<{}>) => {
|
||||
const { entity } = useEntity();
|
||||
const switchCases = useMemo(() => createSwitchCasesFromChildren(children), [
|
||||
children,
|
||||
]);
|
||||
const switchCases = useElementFilter(children, collection =>
|
||||
collection
|
||||
.selectByComponentData({
|
||||
key: ENTITY_SWITCH_KEY,
|
||||
withStrictError: 'Child of EntitySwitch is not an EntitySwitch.Case',
|
||||
})
|
||||
.getElements()
|
||||
.flatMap<SwitchCase>((element: React.ReactElement) => {
|
||||
const { if: condition, children: elementsChildren } = element.props;
|
||||
return [{ if: condition, children: elementsChildren }];
|
||||
}),
|
||||
);
|
||||
|
||||
const matchingCase = switchCases.find(switchCase =>
|
||||
switchCase.if ? switchCase.if(entity) : true,
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
## API Report File for "@backstage/plugin-circleci"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { BuildStepAction } from 'circleci-api';
|
||||
import { BuildSummary } from 'circleci-api';
|
||||
import { BuildSummaryResponse } from 'circleci-api';
|
||||
import { BuildWithSteps } from 'circleci-api';
|
||||
import { CircleCIOptions } from 'circleci-api';
|
||||
import { DiscoveryApi } from '@backstage/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { GitType } from 'circleci-api';
|
||||
import { Me } from 'circleci-api';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
|
||||
export { BuildStepAction }
|
||||
|
||||
export { BuildSummary }
|
||||
|
||||
export { BuildWithSteps }
|
||||
|
||||
// @public (undocumented)
|
||||
export const CIRCLECI_ANNOTATION = "circleci.com/project-slug";
|
||||
|
||||
// @public (undocumented)
|
||||
export class CircleCIApi {
|
||||
constructor(options: Options);
|
||||
// (undocumented)
|
||||
getBuild(buildNumber: number, options: Partial<CircleCIOptions>): Promise<BuildWithSteps>;
|
||||
// (undocumented)
|
||||
getBuilds({ limit, offset }: {
|
||||
limit: number;
|
||||
offset: number;
|
||||
}, options: Partial<CircleCIOptions>): Promise<BuildSummaryResponse>;
|
||||
// (undocumented)
|
||||
getUser(options: Partial<CircleCIOptions>): Promise<Me>;
|
||||
// (undocumented)
|
||||
retry(buildNumber: number, options: Partial<CircleCIOptions>): Promise<BuildSummary>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const circleCIApiRef: ApiRef<CircleCIApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const circleCIBuildRouteRef: RouteRef<undefined>;
|
||||
|
||||
// @public (undocumented)
|
||||
const circleCIPlugin: BackstagePlugin<{}, {}>;
|
||||
|
||||
export { circleCIPlugin }
|
||||
|
||||
export { circleCIPlugin as plugin }
|
||||
|
||||
// @public (undocumented)
|
||||
export const circleCIRouteRef: RouteRef<undefined>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityCircleCIContent: (_props: {
|
||||
entity?: Entity| undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
export { GitType }
|
||||
|
||||
// @public (undocumented)
|
||||
const isCircleCIAvailable: (entity: Entity) => boolean;
|
||||
|
||||
export { isCircleCIAvailable }
|
||||
|
||||
export { isCircleCIAvailable as isPluginApplicableToEntity }
|
||||
|
||||
// @public (undocumented)
|
||||
export const Router: (_props: Props) => JSX.Element;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,283 @@
|
||||
## API Report File for "@backstage/plugin-cloudbuild"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { OAuthApi } from '@backstage/core';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export type ActionsGetWorkflowResponseData = {
|
||||
id: string;
|
||||
status: string;
|
||||
source: Source;
|
||||
createTime: string;
|
||||
startTime: string;
|
||||
steps: Step[];
|
||||
timeout: string;
|
||||
projectId: string;
|
||||
logsBucket: string;
|
||||
sourceProvenance: SourceProvenance;
|
||||
buildTriggerId: string;
|
||||
options: Options;
|
||||
logUrl: string;
|
||||
substitutions: Substitutions;
|
||||
tags: string[];
|
||||
queueTtl: string;
|
||||
name: string;
|
||||
finishTime: any;
|
||||
results: Results;
|
||||
timing: Timing2;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ActionsListWorkflowRunsForRepoResponseData {
|
||||
// (undocumented)
|
||||
builds: ActionsGetWorkflowResponseData[];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface BUILD {
|
||||
// (undocumented)
|
||||
endTime: string;
|
||||
// (undocumented)
|
||||
startTime: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const CLOUDBUILD_ANNOTATION = "google.com/cloudbuild-project-slug";
|
||||
|
||||
// @public (undocumented)
|
||||
export type CloudbuildApi = {
|
||||
listWorkflowRuns: (request: {
|
||||
projectId: string;
|
||||
}) => Promise<ActionsListWorkflowRunsForRepoResponseData>;
|
||||
getWorkflow: ({ projectId, id, }: {
|
||||
projectId: string;
|
||||
id: string;
|
||||
}) => Promise<ActionsGetWorkflowResponseData>;
|
||||
getWorkflowRun: ({ projectId, id, }: {
|
||||
projectId: string;
|
||||
id: string;
|
||||
}) => Promise<ActionsGetWorkflowResponseData>;
|
||||
reRunWorkflow: ({ projectId, runId, }: {
|
||||
projectId: string;
|
||||
runId: string;
|
||||
}) => Promise<any>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const cloudbuildApiRef: ApiRef<CloudbuildApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class CloudbuildClient implements CloudbuildApi {
|
||||
constructor(googleAuthApi: OAuthApi);
|
||||
// (undocumented)
|
||||
getToken(): Promise<string>;
|
||||
// (undocumented)
|
||||
getWorkflow({ projectId, id, }: {
|
||||
projectId: string;
|
||||
id: string;
|
||||
}): Promise<ActionsGetWorkflowResponseData>;
|
||||
// (undocumented)
|
||||
getWorkflowRun({ projectId, id, }: {
|
||||
projectId: string;
|
||||
id: string;
|
||||
}): Promise<ActionsGetWorkflowResponseData>;
|
||||
// (undocumented)
|
||||
listWorkflowRuns({ projectId, }: {
|
||||
projectId: string;
|
||||
}): Promise<ActionsListWorkflowRunsForRepoResponseData>;
|
||||
// (undocumented)
|
||||
reRunWorkflow({ projectId, runId, }: {
|
||||
projectId: string;
|
||||
runId: string;
|
||||
}): Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
const cloudbuildPlugin: BackstagePlugin<{
|
||||
entityContent: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
export { cloudbuildPlugin }
|
||||
|
||||
export { cloudbuildPlugin as plugin }
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityCloudbuildContent: (_props: {
|
||||
entity?: Entity| undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityLatestCloudbuildRunCard: ({ branch, }: {
|
||||
entity?: Entity| undefined;
|
||||
branch: string;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityLatestCloudbuildsForBranchCard: ({ branch, }: {
|
||||
entity?: Entity| undefined;
|
||||
branch: string;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface FETCHSOURCE {
|
||||
// (undocumented)
|
||||
endTime: string;
|
||||
// (undocumented)
|
||||
startTime: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
const isCloudbuildAvailable: (entity: Entity) => boolean;
|
||||
|
||||
export { isCloudbuildAvailable }
|
||||
|
||||
export { isCloudbuildAvailable as isPluginApplicableToEntity }
|
||||
|
||||
// @public (undocumented)
|
||||
export const LatestWorkflowRunCard: ({ branch, }: {
|
||||
entity?: Entity | undefined;
|
||||
branch: string;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const LatestWorkflowsForBranchCard: ({ branch, }: {
|
||||
entity?: Entity | undefined;
|
||||
branch: string;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface Options {
|
||||
// (undocumented)
|
||||
dynamicSubstitutions: boolean;
|
||||
// (undocumented)
|
||||
logging: string;
|
||||
// (undocumented)
|
||||
machineType: string;
|
||||
// (undocumented)
|
||||
substitutionOption: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface PullTiming {
|
||||
// (undocumented)
|
||||
endTime: string;
|
||||
// (undocumented)
|
||||
startTime: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ResolvedStorageSource {
|
||||
// (undocumented)
|
||||
bucket: string;
|
||||
// (undocumented)
|
||||
generation: string;
|
||||
// (undocumented)
|
||||
object: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface Results {
|
||||
// (undocumented)
|
||||
buildStepImages: string[];
|
||||
// (undocumented)
|
||||
buildStepOutputs: string[];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const Router: (_props: Props) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface Source {
|
||||
// (undocumented)
|
||||
storageSource: StorageSource;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface SourceProvenance {
|
||||
// (undocumented)
|
||||
fileHashes: {};
|
||||
// (undocumented)
|
||||
resolvedStorageSource: {};
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface Step {
|
||||
// (undocumented)
|
||||
args: string[];
|
||||
// (undocumented)
|
||||
dir: string;
|
||||
// (undocumented)
|
||||
entrypoint: string;
|
||||
// (undocumented)
|
||||
id: string;
|
||||
// (undocumented)
|
||||
name: string;
|
||||
// (undocumented)
|
||||
pullTiming: PullTiming;
|
||||
// (undocumented)
|
||||
status: string;
|
||||
// (undocumented)
|
||||
timing: Timing;
|
||||
// (undocumented)
|
||||
volumes: Volume[];
|
||||
// (undocumented)
|
||||
waitFor: string[];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface StorageSource {
|
||||
// (undocumented)
|
||||
bucket: string;
|
||||
// (undocumented)
|
||||
object: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface Substitutions {
|
||||
// (undocumented)
|
||||
BRANCH_NAME: string;
|
||||
// (undocumented)
|
||||
COMMIT_SHA: string;
|
||||
// (undocumented)
|
||||
REPO_NAME: string;
|
||||
// (undocumented)
|
||||
REVISION_ID: string;
|
||||
// (undocumented)
|
||||
SHORT_SHA: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface Timing {
|
||||
// (undocumented)
|
||||
endTime: string;
|
||||
// (undocumented)
|
||||
startTime: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface Timing2 {
|
||||
// (undocumented)
|
||||
BUILD: BUILD;
|
||||
// (undocumented)
|
||||
FETCHSOURCE: FETCHSOURCE;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface Volume {
|
||||
// (undocumented)
|
||||
name: string;
|
||||
// (undocumented)
|
||||
path: string;
|
||||
}
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,43 @@
|
||||
## API Report File for "@backstage/plugin-code-coverage-backend"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import { Logger } from 'winston';
|
||||
import { PluginDatabaseManager } from '@backstage/backend-common';
|
||||
import { PluginEndpointDiscovery } from '@backstage/backend-common';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CodeCoverageApi {
|
||||
// (undocumented)
|
||||
name: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const makeRouter: (options: RouterOptions) => Promise<express.Router>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RouterOptions {
|
||||
// (undocumented)
|
||||
config: Config;
|
||||
// (undocumented)
|
||||
database: PluginDatabaseManager;
|
||||
// (undocumented)
|
||||
discovery: PluginEndpointDiscovery;
|
||||
// (undocumented)
|
||||
logger: Logger;
|
||||
// (undocumented)
|
||||
urlReader: UrlReader;
|
||||
}
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,32 @@
|
||||
## API Report File for "@backstage/plugin-code-coverage"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export const codeCoveragePlugin: BackstagePlugin<{
|
||||
root: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityCodeCoverageContent: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
const isCodeCoverageAvailable: (entity: Entity) => boolean;
|
||||
|
||||
export { isCodeCoverageAvailable }
|
||||
|
||||
export { isCodeCoverageAvailable as isPluginApplicableToEntity }
|
||||
|
||||
// @public (undocumented)
|
||||
export const Router: () => JSX.Element;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,42 @@
|
||||
## API Report File for "@backstage/plugin-config-schema"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { Observable } from '@backstage/core';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
import { Schema } from 'jsonschema';
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ConfigSchemaApi {
|
||||
// (undocumented)
|
||||
schema$(): Observable<ConfigSchemaResult>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const configSchemaApiRef: ApiRef<ConfigSchemaApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ConfigSchemaPage: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const configSchemaPlugin: BackstagePlugin<{
|
||||
root: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
// @public
|
||||
export class StaticSchemaLoader implements ConfigSchemaApi {
|
||||
constructor({ url }?: {
|
||||
url?: string;
|
||||
});
|
||||
// (undocumented)
|
||||
schema$(): Observable<ConfigSchemaResult>;
|
||||
}
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,621 @@
|
||||
## API Report File for "@backstage/plugin-cost-insights"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { BackstagePalette } from '@backstage/theme';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { ContentRenderer } from 'recharts';
|
||||
import { Dispatch } from 'react';
|
||||
import { ForwardRefExoticComponent } from 'react';
|
||||
import { PaletteOptions } from '@material-ui/core/styles/createPalette';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { RechartsFunction } from 'recharts';
|
||||
import { RefAttributes } from 'react';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
import { SetStateAction } from 'react';
|
||||
import { TooltipProps } from 'recharts';
|
||||
import { TypographyProps } from '@material-ui/core';
|
||||
|
||||
// @public
|
||||
export type Alert = {
|
||||
title: string | JSX.Element;
|
||||
subtitle: string | JSX.Element;
|
||||
element?: JSX.Element;
|
||||
status?: AlertStatus;
|
||||
url?: string;
|
||||
buttonText?: string;
|
||||
SnoozeForm?: Maybe<AlertForm>;
|
||||
AcceptForm?: Maybe<AlertForm>;
|
||||
DismissForm?: Maybe<AlertForm>;
|
||||
onSnoozed?(options: AlertOptions): Promise<Alert[]>;
|
||||
onAccepted?(options: AlertOptions): Promise<Alert[]>;
|
||||
onDismissed?(options: AlertOptions): Promise<Alert[]>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface AlertCost {
|
||||
// (undocumented)
|
||||
aggregation: [number, number];
|
||||
// (undocumented)
|
||||
id: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface AlertDismissFormData {
|
||||
// (undocumented)
|
||||
feedback: Maybe<string>;
|
||||
// (undocumented)
|
||||
other: Maybe<string>;
|
||||
// (undocumented)
|
||||
reason: AlertDismissReason;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface AlertDismissOption {
|
||||
// (undocumented)
|
||||
label: string;
|
||||
// (undocumented)
|
||||
reason: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const AlertDismissOptions: AlertDismissOption[];
|
||||
|
||||
// @public (undocumented)
|
||||
export enum AlertDismissReason {
|
||||
// (undocumented)
|
||||
Expected = "expected",
|
||||
// (undocumented)
|
||||
Migration = "migration",
|
||||
// (undocumented)
|
||||
NotApplicable = "not-applicable",
|
||||
// (undocumented)
|
||||
Other = "other",
|
||||
// (undocumented)
|
||||
Resolved = "resolved",
|
||||
// (undocumented)
|
||||
Seasonal = "seasonal"
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type AlertForm<A extends Alert = any, Data = any> = ForwardRefExoticComponent<AlertFormProps<A, Data> & RefAttributes<HTMLFormElement>>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type AlertFormProps<A extends Alert, FormData = {}> = {
|
||||
alert: A;
|
||||
onSubmit: (data: FormData) => void;
|
||||
disableSubmit: (isDisabled: boolean) => void;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface AlertOptions<T = any> {
|
||||
// (undocumented)
|
||||
data: T;
|
||||
// (undocumented)
|
||||
group: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface AlertSnoozeFormData {
|
||||
// (undocumented)
|
||||
intervals: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type AlertSnoozeOption = {
|
||||
label: string;
|
||||
duration: Duration;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const AlertSnoozeOptions: AlertSnoozeOption[];
|
||||
|
||||
// @public (undocumented)
|
||||
export enum AlertStatus {
|
||||
// (undocumented)
|
||||
Accepted = "accepted",
|
||||
// (undocumented)
|
||||
Dismissed = "dismissed",
|
||||
// (undocumented)
|
||||
Snoozed = "snoozed"
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const BarChart: ({ resources, responsive, displayAmount, options, tooltip, onClick, onMouseMove, }: BarChartProps) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export interface BarChartData extends BarChartOptions {
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const BarChartLegend: ({ costStart, costEnd, options, children, }: PropsWithChildren<BarChartLegendProps>) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type BarChartLegendOptions = {
|
||||
previousName: string;
|
||||
previousFill: string;
|
||||
currentName: string;
|
||||
currentFill: string;
|
||||
hideMarker?: boolean;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type BarChartLegendProps = {
|
||||
costStart: number;
|
||||
costEnd: number;
|
||||
options?: Partial<BarChartLegendOptions>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface BarChartOptions {
|
||||
// (undocumented)
|
||||
currentFill: string;
|
||||
// (undocumented)
|
||||
currentName: string;
|
||||
// (undocumented)
|
||||
previousFill: string;
|
||||
// (undocumented)
|
||||
previousName: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type BarChartProps = {
|
||||
resources: ResourceData[];
|
||||
responsive?: boolean;
|
||||
displayAmount?: number;
|
||||
options?: Partial<BarChartData>;
|
||||
tooltip?: ContentRenderer<TooltipProps>;
|
||||
onClick?: RechartsFunction;
|
||||
onMouseMove?: RechartsFunction;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const BarChartTooltip: ({ title, content, subtitle, topRight, actions, children, }: PropsWithChildren<BarChartTooltipProps>) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const BarChartTooltipItem: ({ item }: BarChartTooltipItemProps) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type BarChartTooltipItemProps = {
|
||||
item: TooltipItem;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type BarChartTooltipProps = {
|
||||
title: string;
|
||||
content?: ReactNode | string;
|
||||
subtitle?: ReactNode;
|
||||
topRight?: ReactNode;
|
||||
actions?: ReactNode;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ChangeStatistic {
|
||||
// (undocumented)
|
||||
amount: number;
|
||||
// (undocumented)
|
||||
ratio?: number;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export enum ChangeThreshold {
|
||||
// (undocumented)
|
||||
lower = -0.05,
|
||||
// (undocumented)
|
||||
upper = 0.05
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type ChartData = {
|
||||
date: number;
|
||||
trend: number;
|
||||
dailyCost: number;
|
||||
[key: string]: number;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface Cost {
|
||||
// (undocumented)
|
||||
aggregation: DateAggregation[];
|
||||
// (undocumented)
|
||||
change?: ChangeStatistic;
|
||||
// (undocumented)
|
||||
groupedCosts?: Record<string, Cost[]>;
|
||||
// (undocumented)
|
||||
id: string;
|
||||
// (undocumented)
|
||||
trendline?: Trendline;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const CostGrowth: ({ change, duration }: CostGrowthProps) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const CostGrowthIndicator: ({ change, formatter, className, ...props }: CostGrowthIndicatorProps) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type CostGrowthIndicatorProps = TypographyProps & {
|
||||
change: ChangeStatistic;
|
||||
formatter?: (change: ChangeStatistic) => Maybe<string>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type CostGrowthProps = {
|
||||
change: ChangeStatistic;
|
||||
duration: Duration;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type CostInsightsApi = {
|
||||
getLastCompleteBillingDate(): Promise<string>;
|
||||
getUserGroups(userId: string): Promise<Group[]>;
|
||||
getGroupProjects(group: string): Promise<Project[]>;
|
||||
getGroupDailyCost(group: string, intervals: string): Promise<Cost>;
|
||||
getProjectDailyCost(project: string, intervals: string): Promise<Cost>;
|
||||
getDailyMetricData(metric: string, intervals: string): Promise<MetricData>;
|
||||
getProductInsights(options: ProductInsightsOptions): Promise<Entity>;
|
||||
getAlerts(group: string): Promise<Alert[]>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const costInsightsApiRef: ApiRef<CostInsightsApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const CostInsightsLabelDataflowInstructionsPage: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const CostInsightsPage: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type CostInsightsPalette = BackstagePalette & CostInsightsPaletteAdditions;
|
||||
|
||||
// @public (undocumented)
|
||||
export type CostInsightsPaletteOptions = PaletteOptions & CostInsightsPaletteAdditions;
|
||||
|
||||
// @public (undocumented)
|
||||
const costInsightsPlugin: BackstagePlugin<{
|
||||
root: RouteRef<undefined>;
|
||||
growthAlerts: RouteRef<undefined>;
|
||||
unlabeledDataflowAlerts: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
export { costInsightsPlugin }
|
||||
|
||||
export { costInsightsPlugin as plugin }
|
||||
|
||||
// @public (undocumented)
|
||||
export const CostInsightsProjectGrowthInstructionsPage: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CostInsightsTheme extends BackstageTheme {
|
||||
// (undocumented)
|
||||
palette: CostInsightsPalette;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CostInsightsThemeOptions extends PaletteOptions {
|
||||
// (undocumented)
|
||||
palette: CostInsightsPaletteOptions;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface Currency {
|
||||
// (undocumented)
|
||||
kind: string | null;
|
||||
// (undocumented)
|
||||
label: string;
|
||||
// (undocumented)
|
||||
prefix?: string;
|
||||
// (undocumented)
|
||||
rate?: number;
|
||||
// (undocumented)
|
||||
unit: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export enum CurrencyType {
|
||||
// (undocumented)
|
||||
Beers = "BEERS",
|
||||
// (undocumented)
|
||||
CarbonOffsetTons = "CARBON_OFFSET_TONS",
|
||||
// (undocumented)
|
||||
IceCream = "PINTS_OF_ICE_CREAM",
|
||||
// (undocumented)
|
||||
USD = "USD"
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export enum DataKey {
|
||||
// (undocumented)
|
||||
Current = "current",
|
||||
// (undocumented)
|
||||
Name = "name",
|
||||
// (undocumented)
|
||||
Previous = "previous"
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type DateAggregation = {
|
||||
date: string;
|
||||
amount: number;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const DEFAULT_DATE_FORMAT = "YYYY-MM-DD";
|
||||
|
||||
// @public
|
||||
export enum Duration {
|
||||
// (undocumented)
|
||||
P30D = "P30D",
|
||||
// (undocumented)
|
||||
P3M = "P3M",
|
||||
// (undocumented)
|
||||
P7D = "P7D",
|
||||
// (undocumented)
|
||||
P90D = "P90D"
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const EngineerThreshold = 0.5;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface Entity {
|
||||
// (undocumented)
|
||||
aggregation: [number, number];
|
||||
// (undocumented)
|
||||
change: ChangeStatistic;
|
||||
// (undocumented)
|
||||
entities: Record<string, Entity[]>;
|
||||
// (undocumented)
|
||||
id: Maybe<string>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class ExampleCostInsightsClient implements CostInsightsApi {
|
||||
// (undocumented)
|
||||
getAlerts(group: string): Promise<Alert[]>;
|
||||
// (undocumented)
|
||||
getDailyMetricData(metric: string, intervals: string): Promise<MetricData>;
|
||||
// (undocumented)
|
||||
getGroupDailyCost(group: string, intervals: string): Promise<Cost>;
|
||||
// (undocumented)
|
||||
getGroupProjects(group: string): Promise<Project[]>;
|
||||
// (undocumented)
|
||||
getLastCompleteBillingDate(): Promise<string>;
|
||||
// (undocumented)
|
||||
getProductInsights(options: ProductInsightsOptions): Promise<Entity>;
|
||||
// (undocumented)
|
||||
getProjectDailyCost(project: string, intervals: string): Promise<Cost>;
|
||||
// (undocumented)
|
||||
getUserGroups(userId: string): Promise<Group[]>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type Group = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export enum GrowthType {
|
||||
// (undocumented)
|
||||
Excess = 2,
|
||||
// (undocumented)
|
||||
Negligible = 0,
|
||||
// (undocumented)
|
||||
Savings = 1
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type Icon = {
|
||||
kind: string;
|
||||
component: JSX.Element;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export enum IconType {
|
||||
// (undocumented)
|
||||
Compute = "compute",
|
||||
// (undocumented)
|
||||
Data = "data",
|
||||
// (undocumented)
|
||||
Database = "database",
|
||||
// (undocumented)
|
||||
ML = "ml",
|
||||
// (undocumented)
|
||||
Search = "search",
|
||||
// (undocumented)
|
||||
Storage = "storage"
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const LegendItem: ({ title, tooltipText, markerColor, children, }: PropsWithChildren<LegendItemProps>) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type LegendItemProps = {
|
||||
title: string;
|
||||
tooltipText?: string;
|
||||
markerColor?: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type Loading = Record<string, boolean>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type Maybe<T> = T | null;
|
||||
|
||||
// @public (undocumented)
|
||||
export type Metric = {
|
||||
kind: string;
|
||||
name: string;
|
||||
default: boolean;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface MetricData {
|
||||
// (undocumented)
|
||||
aggregation: DateAggregation[];
|
||||
// (undocumented)
|
||||
change: ChangeStatistic;
|
||||
// (undocumented)
|
||||
format: 'number' | 'currency';
|
||||
// (undocumented)
|
||||
id: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const MockConfigProvider: ({ children, ...context }: MockConfigProviderProps) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const MockCurrencyProvider: ({ children, ...context }: MockCurrencyProviderProps) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface PageFilters {
|
||||
// (undocumented)
|
||||
duration: Duration;
|
||||
// (undocumented)
|
||||
group: Maybe<string>;
|
||||
// (undocumented)
|
||||
metric: string | null;
|
||||
// (undocumented)
|
||||
project: Maybe<string>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface Product {
|
||||
// (undocumented)
|
||||
kind: string;
|
||||
// (undocumented)
|
||||
name: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type ProductFilters = Array<ProductPeriod>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type ProductInsightsOptions = {
|
||||
product: string;
|
||||
group: string;
|
||||
intervals: string;
|
||||
project: Maybe<string>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ProductPeriod {
|
||||
// (undocumented)
|
||||
duration: Duration;
|
||||
// (undocumented)
|
||||
productType: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface Project {
|
||||
// (undocumented)
|
||||
id: string;
|
||||
// (undocumented)
|
||||
name?: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export class ProjectGrowthAlert implements Alert {
|
||||
constructor(data: ProjectGrowthData);
|
||||
// (undocumented)
|
||||
data: ProjectGrowthData;
|
||||
// (undocumented)
|
||||
get element(): JSX.Element;
|
||||
// (undocumented)
|
||||
get subtitle(): string;
|
||||
// (undocumented)
|
||||
get title(): string;
|
||||
// (undocumented)
|
||||
get url(): string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ProjectGrowthData {
|
||||
// (undocumented)
|
||||
aggregation: [number, number];
|
||||
// (undocumented)
|
||||
change: ChangeStatistic;
|
||||
// (undocumented)
|
||||
periodEnd: string;
|
||||
// (undocumented)
|
||||
periodStart: string;
|
||||
// (undocumented)
|
||||
products: Array<AlertCost>;
|
||||
// (undocumented)
|
||||
project: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ResourceData {
|
||||
// (undocumented)
|
||||
current: number;
|
||||
// (undocumented)
|
||||
name: Maybe<string>;
|
||||
// (undocumented)
|
||||
previous: number;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type TooltipItem = {
|
||||
fill: string;
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type Trendline = {
|
||||
slope: number;
|
||||
intercept: number;
|
||||
};
|
||||
|
||||
// @public
|
||||
export class UnlabeledDataflowAlert implements Alert {
|
||||
constructor(data: UnlabeledDataflowData);
|
||||
// (undocumented)
|
||||
data: UnlabeledDataflowData;
|
||||
// (undocumented)
|
||||
get element(): JSX.Element;
|
||||
// (undocumented)
|
||||
status?: AlertStatus;
|
||||
// (undocumented)
|
||||
get subtitle(): string;
|
||||
// (undocumented)
|
||||
get title(): string;
|
||||
// (undocumented)
|
||||
get url(): string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface UnlabeledDataflowAlertProject {
|
||||
// (undocumented)
|
||||
id: string;
|
||||
// (undocumented)
|
||||
labeledCost: number;
|
||||
// (undocumented)
|
||||
unlabeledCost: number;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface UnlabeledDataflowData {
|
||||
// (undocumented)
|
||||
labeledCost: number;
|
||||
// (undocumented)
|
||||
periodEnd: string;
|
||||
// (undocumented)
|
||||
periodStart: string;
|
||||
// (undocumented)
|
||||
projects: Array<UnlabeledDataflowAlertProject>;
|
||||
// (undocumented)
|
||||
unlabeledCost: number;
|
||||
}
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,31 @@
|
||||
## API Report File for "@backstage/plugin-explore-react"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export type ExploreTool = {
|
||||
title: string;
|
||||
description?: string;
|
||||
url: string;
|
||||
image: string;
|
||||
tags?: string[];
|
||||
lifecycle?: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ExploreToolsConfig {
|
||||
// (undocumented)
|
||||
getTools: () => Promise<ExploreTool[]>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const exploreToolsConfigRef: ApiRef<ExploreToolsConfig>;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -33,3 +33,49 @@ import LayersIcon from '@material-ui/icons/Layers';
|
||||
|
||||
<SidebarItem icon={LayersIcon} to="explore" text="Explore" />
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
Create a custom explore page in `packages/app/src/components/explore/ExplorePage.tsx`.
|
||||
|
||||
```tsx
|
||||
import {
|
||||
DomainExplorerContent,
|
||||
ExploreLayout,
|
||||
} from '@backstage/plugin-explore';
|
||||
import React from 'react';
|
||||
import { InnserSourceExplorerContent } from './InnserSourceExplorerContent';
|
||||
|
||||
export const ExplorePage = () => {
|
||||
return (
|
||||
<ExploreLayout
|
||||
title="Explore the ACME corp ecosystem"
|
||||
subtitle="Browse our ecosystem"
|
||||
>
|
||||
<ExploreLayout.Route path="domains" title="Domains">
|
||||
<DomainExplorerContent />
|
||||
</ExploreLayout.Route>
|
||||
<ExploreLayout.Route path="inner-source" title="InnerSource">
|
||||
<AcmeInnserSourceExplorerContent />
|
||||
</ExploreLayout.Route>
|
||||
</ExploreLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export const explorePage = <ExplorePage />;
|
||||
```
|
||||
|
||||
Now register the new explore page in `packages/app/src/App.tsx`.
|
||||
|
||||
```diff
|
||||
+ import { explorePage } from './components/explore/ExplorePage';
|
||||
|
||||
const routes = (
|
||||
<FlatRoutes>
|
||||
- <Route path="/explore" element={<ExplorePage />} />
|
||||
+ <Route path="/explore" element={<ExplorePage />}>
|
||||
+ {explorePage}
|
||||
+ </Route>
|
||||
</FlatRoutes>
|
||||
);
|
||||
```
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
## API Report File for "@backstage/plugin-explore"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { ExternalRouteRef } from '@backstage/core';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export const catalogEntityRouteRef: ExternalRouteRef<{
|
||||
name: string;
|
||||
kind: string;
|
||||
namespace: string;
|
||||
}, false>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ExplorePage: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const explorePlugin: BackstagePlugin<{
|
||||
explore: RouteRef<undefined>;
|
||||
}, {
|
||||
catalogEntity: ExternalRouteRef<{
|
||||
name: string;
|
||||
kind: string;
|
||||
namespace: string;
|
||||
}, false>;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const exploreRouteRef: RouteRef<undefined>;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -38,9 +38,11 @@
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"@types/react": "^16.9",
|
||||
"classnames": "^2.2.6",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router": "6.0.0-beta.0",
|
||||
"react-router-dom": "6.0.0-beta.0",
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 { ApiProvider, ApiRegistry } from '@backstage/core';
|
||||
import { catalogApiRef } from '@backstage/plugin-catalog-react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { waitFor, getByText } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { DefaultExplorePage } from './DefaultExplorePage';
|
||||
|
||||
describe('<DefaultExplorePage />', () => {
|
||||
const catalogApi: jest.Mocked<typeof catalogApiRef.T> = {
|
||||
addLocation: jest.fn(_a => new Promise(() => {})),
|
||||
getEntities: jest.fn(),
|
||||
getOriginLocationByEntity: jest.fn(),
|
||||
getLocationByEntity: jest.fn(),
|
||||
getLocationById: jest.fn(),
|
||||
removeLocationById: jest.fn(),
|
||||
removeEntityByUid: jest.fn(),
|
||||
getEntityByName: jest.fn(),
|
||||
};
|
||||
|
||||
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
|
||||
<ApiProvider apis={ApiRegistry.with(catalogApiRef, catalogApi)}>
|
||||
{children}
|
||||
</ApiProvider>
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('renders the default explore page', async () => {
|
||||
catalogApi.getEntities.mockResolvedValue({ items: [] });
|
||||
|
||||
const { getAllByRole } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<DefaultExplorePage />
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const elements = getAllByRole('tab');
|
||||
expect(elements.length).toBe(3);
|
||||
expect(getByText(elements[0], 'Domains')).toBeInTheDocument();
|
||||
expect(getByText(elements[1], 'Groups')).toBeInTheDocument();
|
||||
expect(getByText(elements[2], 'Tools')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* 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';
|
||||
import { DomainExplorerContent } from '../DomainExplorerContent';
|
||||
import { ExploreLayout } from '../ExploreLayout';
|
||||
import { GroupsExplorerContent } from '../GroupsExplorerContent';
|
||||
import { ToolExplorerContent } from '../ToolExplorerContent';
|
||||
|
||||
export const DefaultExplorePage = () => {
|
||||
const configApi = useApi(configApiRef);
|
||||
const organizationName =
|
||||
configApi.getOptionalString('organization.name') ?? 'Backstage';
|
||||
|
||||
return (
|
||||
<ExploreLayout
|
||||
title={`Explore the ${organizationName} ecosystem`}
|
||||
subtitle="Discover solutions available in your ecosystem"
|
||||
>
|
||||
<ExploreLayout.Route path="domains" title="Domains">
|
||||
<DomainExplorerContent />
|
||||
</ExploreLayout.Route>
|
||||
<ExploreLayout.Route path="groups" title="Groups">
|
||||
<GroupsExplorerContent />
|
||||
</ExploreLayout.Route>
|
||||
<ExploreLayout.Route path="tools" title="Tools">
|
||||
<ToolExplorerContent />
|
||||
</ExploreLayout.Route>
|
||||
</ExploreLayout>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* 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 { DefaultExplorePage } from './DefaultExplorePage';
|
||||
+22
-15
@@ -41,6 +41,12 @@ describe('<DomainExplorerContent />', () => {
|
||||
</ApiProvider>
|
||||
);
|
||||
|
||||
const mountedRoutes = {
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': catalogEntityRouteRef,
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
@@ -74,11 +80,7 @@ describe('<DomainExplorerContent />', () => {
|
||||
<Wrapper>
|
||||
<DomainExplorerContent />
|
||||
</Wrapper>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': catalogEntityRouteRef,
|
||||
},
|
||||
},
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -87,6 +89,19 @@ describe('<DomainExplorerContent />', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('renders a custom title', async () => {
|
||||
catalogApi.getEntities.mockResolvedValue({ items: [] });
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<DomainExplorerContent title="Our Areas" />
|
||||
</Wrapper>,
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
await waitFor(() => expect(getByText('Our Areas')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('renders empty state', async () => {
|
||||
catalogApi.getEntities.mockResolvedValue({ items: [] });
|
||||
|
||||
@@ -94,11 +109,7 @@ describe('<DomainExplorerContent />', () => {
|
||||
<Wrapper>
|
||||
<DomainExplorerContent />
|
||||
</Wrapper>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': catalogEntityRouteRef,
|
||||
},
|
||||
},
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
@@ -114,11 +125,7 @@ describe('<DomainExplorerContent />', () => {
|
||||
<Wrapper>
|
||||
<DomainExplorerContent />
|
||||
</Wrapper>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': catalogEntityRouteRef,
|
||||
},
|
||||
},
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
|
||||
@@ -79,10 +79,16 @@ const Body = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const DomainExplorerContent = () => {
|
||||
type DomainExplorerContentProps = {
|
||||
title?: string;
|
||||
};
|
||||
|
||||
export const DomainExplorerContent = ({
|
||||
title,
|
||||
}: DomainExplorerContentProps) => {
|
||||
return (
|
||||
<Content noPadding>
|
||||
<ContentHeader title="Domains">
|
||||
<ContentHeader title={title ?? 'Domains'}>
|
||||
<SupportButton>Discover the domains in your ecosystem.</SupportButton>
|
||||
</ContentHeader>
|
||||
<Body />
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 { renderInTestApp } from '@backstage/test-utils';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { ExploreLayout } from './ExploreLayout';
|
||||
|
||||
describe('<ExploreLayout />', () => {
|
||||
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
|
||||
<>{children}</>
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('renders an explore tabbed layout page with defaults', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<ExploreLayout>
|
||||
<ExploreLayout.Route path="/tools" title="Tools">
|
||||
<div>Tools Content</div>
|
||||
</ExploreLayout.Route>
|
||||
</ExploreLayout>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('Explore our ecosystem')).toBeInTheDocument();
|
||||
expect(
|
||||
getByText('Discover solutions available in our ecosystem'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders a custom page title', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<ExploreLayout title="Explore our universe">
|
||||
<ExploreLayout.Route path="/tools" title="Tools">
|
||||
<div>Tools Content</div>
|
||||
</ExploreLayout.Route>
|
||||
</ExploreLayout>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(getByText('Explore our universe')).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
|
||||
it('renders a custom page subtitle', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<ExploreLayout subtitle="Browse the ACME Corp ecosystem">
|
||||
<ExploreLayout.Route path="/tools" title="Tools">
|
||||
<div>Tools Content</div>
|
||||
</ExploreLayout.Route>
|
||||
</ExploreLayout>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(getByText('Browse the ACME Corp ecosystem')).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 { attachComponentData, Header, Page, RoutedTabs } from '@backstage/core';
|
||||
import { TabProps } from '@material-ui/core';
|
||||
import { Children, default as React, Fragment, isValidElement } from 'react';
|
||||
|
||||
// TODO: This layout could be a shared based component if it was possible to create custom TabbedLayouts
|
||||
// A generalized version of createSubRoutesFromChildren, etc. would be required
|
||||
|
||||
type SubRoute = {
|
||||
path: string;
|
||||
title: string;
|
||||
children: JSX.Element;
|
||||
tabProps?: TabProps<React.ElementType, { component?: React.ElementType }>;
|
||||
};
|
||||
|
||||
const Route: (props: SubRoute) => null = () => null;
|
||||
|
||||
// This causes all mount points that are discovered within this route to use the path of the route itself
|
||||
attachComponentData(Route, 'core.gatherMountPoints', true);
|
||||
|
||||
function createSubRoutesFromChildren(
|
||||
childrenProps: React.ReactNode,
|
||||
): SubRoute[] {
|
||||
// Directly comparing child.type with Route will not work with in
|
||||
// combination with react-hot-loader in storybook
|
||||
// https://github.com/gaearon/react-hot-loader/issues/304
|
||||
const routeType = (
|
||||
<Route path="" title="">
|
||||
<div />
|
||||
</Route>
|
||||
).type;
|
||||
|
||||
return Children.toArray(childrenProps).flatMap(child => {
|
||||
if (!isValidElement(child)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (child.type === Fragment) {
|
||||
return createSubRoutesFromChildren(child.props.children);
|
||||
}
|
||||
|
||||
if (child.type !== routeType) {
|
||||
throw new Error('Child of ExploreLayout must be an ExploreLayout.Route');
|
||||
}
|
||||
|
||||
const { path, title, children, tabProps } = child.props;
|
||||
return [{ path, title, children, tabProps }];
|
||||
});
|
||||
}
|
||||
|
||||
type ExploreLayoutProps = {
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Explore is a compound component, which allows you to define a custom layout
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* <ExploreLayout title="Explore ACME's ecosystem">
|
||||
* <ExploreLayout.Route path="/example" title="Example tab">
|
||||
* <div>This is rendered under /example/anything-here route</div>
|
||||
* </ExploreLayout.Route>
|
||||
* </ExploreLayout>
|
||||
* ```
|
||||
*/
|
||||
export const ExploreLayout = ({
|
||||
title,
|
||||
subtitle,
|
||||
children,
|
||||
}: ExploreLayoutProps) => {
|
||||
const routes = createSubRoutesFromChildren(children);
|
||||
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
title={title ?? 'Explore our ecosystem'}
|
||||
subtitle={subtitle ?? 'Discover solutions available in our ecosystem'}
|
||||
/>
|
||||
<RoutedTabs routes={routes} />
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
ExploreLayout.Route = Route;
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* 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 { ExploreLayout } from './ExploreLayout';
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 { renderInTestApp } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
import { useOutlet } from 'react-router';
|
||||
import { ExplorePage } from './ExplorePage';
|
||||
|
||||
jest.mock('react-router', () => ({
|
||||
...jest.requireActual('react-router'),
|
||||
useLocation: jest.fn().mockReturnValue({
|
||||
search: '',
|
||||
}),
|
||||
useOutlet: jest.fn().mockReturnValue('Route Children'),
|
||||
}));
|
||||
|
||||
jest.mock('../DefaultExplorePage', () => ({
|
||||
...jest.requireActual('../DefaultExplorePage'),
|
||||
DefaultExplorePage: jest.fn().mockReturnValue('DefaultExplorePageMock'),
|
||||
}));
|
||||
|
||||
describe('ExplorePage', () => {
|
||||
it('renders provided router element', async () => {
|
||||
const { getByText } = await renderInTestApp(<ExplorePage />);
|
||||
|
||||
expect(getByText('Route Children')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders default explorer page when no router children are provided', async () => {
|
||||
(useOutlet as jest.Mock).mockReturnValueOnce(null);
|
||||
const { getByText } = await renderInTestApp(<ExplorePage />);
|
||||
|
||||
expect(getByText('DefaultExplorePageMock')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -13,22 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { configApiRef, Header, Page, useApi } from '@backstage/core';
|
||||
|
||||
import React from 'react';
|
||||
import { ExploreTabs } from './ExploreTabs';
|
||||
import { useOutlet } from 'react-router';
|
||||
import { DefaultExplorePage } from '../DefaultExplorePage';
|
||||
|
||||
export const ExplorePage = () => {
|
||||
const configApi = useApi(configApiRef);
|
||||
const organizationName =
|
||||
configApi.getOptionalString('organization.name') ?? 'Backstage';
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
title={`Explore the ${organizationName} ecosystem`}
|
||||
subtitle="Discover solutions available in your ecosystem"
|
||||
/>
|
||||
const outlet = useOutlet();
|
||||
|
||||
<ExploreTabs />
|
||||
</Page>
|
||||
);
|
||||
return <>{outlet || <DefaultExplorePage />}</>;
|
||||
};
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* 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 { TabbedLayout } from '@backstage/core';
|
||||
import React from 'react';
|
||||
import { DomainExplorerContent } from '../DomainExplorerContent';
|
||||
import { GroupsExplorerContent } from '../GroupsExplorerContent';
|
||||
import { ToolExplorerContent } from '../ToolExplorerContent';
|
||||
|
||||
export const ExploreTabs = () => (
|
||||
<TabbedLayout>
|
||||
<TabbedLayout.Route path="domains" title="Domains">
|
||||
<DomainExplorerContent />
|
||||
</TabbedLayout.Route>
|
||||
<TabbedLayout.Route path="groups" title="Groups">
|
||||
<GroupsExplorerContent />
|
||||
</TabbedLayout.Route>
|
||||
<TabbedLayout.Route path="tools" title="Tools">
|
||||
<ToolExplorerContent />
|
||||
</TabbedLayout.Route>
|
||||
</TabbedLayout>
|
||||
);
|
||||
+21
-10
@@ -40,6 +40,12 @@ describe('<GroupsExplorerContent />', () => {
|
||||
</ApiProvider>
|
||||
);
|
||||
|
||||
const mountedRoutes = {
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
|
||||
@@ -69,11 +75,7 @@ describe('<GroupsExplorerContent />', () => {
|
||||
<Wrapper>
|
||||
<GroupsExplorerContent />
|
||||
</Wrapper>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
},
|
||||
},
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -81,6 +83,19 @@ describe('<GroupsExplorerContent />', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('renders a custom title', async () => {
|
||||
catalogApi.getEntities.mockResolvedValue({ items: [] });
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<GroupsExplorerContent title="Our Teams" />
|
||||
</Wrapper>,
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
await waitFor(() => expect(getByText('Our Teams')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('renders a friendly error if it cannot collect domains', async () => {
|
||||
const catalogError = new Error('Network timeout');
|
||||
catalogApi.getEntities.mockRejectedValueOnce(catalogError);
|
||||
@@ -89,11 +104,7 @@ describe('<GroupsExplorerContent />', () => {
|
||||
<Wrapper>
|
||||
<GroupsExplorerContent />
|
||||
</Wrapper>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
},
|
||||
},
|
||||
mountedRoutes,
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
|
||||
@@ -13,14 +13,21 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Content, ContentHeader, SupportButton } from '@backstage/core';
|
||||
import React from 'react';
|
||||
import { GroupsDiagram } from './GroupsDiagram';
|
||||
|
||||
export const GroupsExplorerContent = () => {
|
||||
type GroupsExplorerContentProps = {
|
||||
title?: string;
|
||||
};
|
||||
|
||||
export const GroupsExplorerContent = ({
|
||||
title,
|
||||
}: GroupsExplorerContentProps) => {
|
||||
return (
|
||||
<Content noPadding>
|
||||
<ContentHeader title="Groups">
|
||||
<ContentHeader title={title ?? 'Groups'}>
|
||||
<SupportButton>Explore your groups.</SupportButton>
|
||||
</ContentHeader>
|
||||
|
||||
|
||||
@@ -80,6 +80,18 @@ describe('<ToolExplorerContent />', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('renders a custom title', async () => {
|
||||
exploreToolsConfigApi.getTools.mockResolvedValue([]);
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<ToolExplorerContent title="Our Tools" />
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => expect(getByText('Our Tools')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('renders empty state', async () => {
|
||||
exploreToolsConfigApi.getTools.mockResolvedValue([]);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
@@ -61,9 +62,13 @@ const Body = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const ToolExplorerContent = () => (
|
||||
type ToolExplorerContentProps = {
|
||||
title?: string;
|
||||
};
|
||||
|
||||
export const ToolExplorerContent = ({ title }: ToolExplorerContentProps) => (
|
||||
<Content noPadding>
|
||||
<ContentHeader title="Tools">
|
||||
<ContentHeader title={title ?? 'Tools'}>
|
||||
<SupportButton>Discover the tools in your ecosystem.</SupportButton>
|
||||
</ContentHeader>
|
||||
<Body />
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* 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 { ExploreLayout } from './ExploreLayout';
|
||||
@@ -14,7 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createRoutableExtension } from '@backstage/core';
|
||||
import {
|
||||
createComponentExtension,
|
||||
createRoutableExtension,
|
||||
} from '@backstage/core';
|
||||
import { explorePlugin } from './plugin';
|
||||
import { exploreRouteRef } from './routes';
|
||||
|
||||
@@ -25,3 +28,36 @@ export const ExplorePage = explorePlugin.provide(
|
||||
mountPoint: exploreRouteRef,
|
||||
}),
|
||||
);
|
||||
|
||||
export const DomainExplorerContent = explorePlugin.provide(
|
||||
createComponentExtension({
|
||||
component: {
|
||||
lazy: () =>
|
||||
import('./components/DomainExplorerContent').then(
|
||||
m => m.DomainExplorerContent,
|
||||
),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export const GroupsExplorerContent = explorePlugin.provide(
|
||||
createComponentExtension({
|
||||
component: {
|
||||
lazy: () =>
|
||||
import('./components/GroupsExplorerContent').then(
|
||||
m => m.GroupsExplorerContent,
|
||||
),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export const ToolExplorerContent = explorePlugin.provide(
|
||||
createComponentExtension({
|
||||
component: {
|
||||
lazy: () =>
|
||||
import('./components/ToolExplorerContent').then(
|
||||
m => m.ToolExplorerContent,
|
||||
),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { ExploreLayout } from './components';
|
||||
export * from './extensions';
|
||||
export { explorePlugin } from './plugin';
|
||||
export { explorePlugin, explorePlugin as plugin } from './plugin';
|
||||
export * from './routes';
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
## API Report File for "@backstage/plugin-fossa"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { InfoCardVariants } from '@backstage/core';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityFossaCard: ({ variant }: {
|
||||
variant?: InfoCardVariants| undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const FossaPage: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const fossaPlugin: BackstagePlugin<{
|
||||
fossaOverview: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,86 @@
|
||||
## API Report File for "@backstage/plugin-gcp-projects"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { OAuthApi } from '@backstage/core';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export type GcpApi = {
|
||||
listProjects(): Promise<Project[]>;
|
||||
getProject(projectId: string): Promise<Project>;
|
||||
createProject(options: {
|
||||
projectId: string;
|
||||
projectName: string;
|
||||
}): Promise<Operation>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const gcpApiRef: ApiRef<GcpApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class GcpClient implements GcpApi {
|
||||
constructor(googleAuthApi: OAuthApi);
|
||||
// (undocumented)
|
||||
createProject(options: {
|
||||
projectId: string;
|
||||
projectName: string;
|
||||
}): Promise<Operation>;
|
||||
// (undocumented)
|
||||
getProject(projectId: string): Promise<Project>;
|
||||
// (undocumented)
|
||||
getToken(): Promise<string>;
|
||||
// (undocumented)
|
||||
listProjects(): Promise<Project[]>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const GcpProjectsPage: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
const gcpProjectsPlugin: BackstagePlugin<{
|
||||
root: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
export { gcpProjectsPlugin }
|
||||
|
||||
export { gcpProjectsPlugin as plugin }
|
||||
|
||||
// @public (undocumented)
|
||||
export type Operation = {
|
||||
name: string;
|
||||
metadata: string;
|
||||
done: boolean;
|
||||
error: Status;
|
||||
response: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type Project = {
|
||||
name: string;
|
||||
projectNumber?: string;
|
||||
projectId: string;
|
||||
lifecycleState?: string;
|
||||
createTime?: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type ProjectDetails = {
|
||||
details: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type Status = {
|
||||
code: number;
|
||||
message: string;
|
||||
details: string[];
|
||||
};
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
## API Report File for "@backstage/plugin-git-release-manager"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export const gitReleaseManagerApiRef: ApiRef<GitReleaseApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const GitReleaseManagerPage: GitReleaseManager;
|
||||
|
||||
// @public (undocumented)
|
||||
export const gitReleaseManagerPlugin: BackstagePlugin<{
|
||||
root: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,213 @@
|
||||
## API Report File for "@backstage/plugin-github-actions"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { ConfigApi } from '@backstage/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { InfoCardVariants } from '@backstage/core';
|
||||
import { OAuthApi } from '@backstage/core';
|
||||
import { RestEndpointMethodTypes } from '@octokit/rest';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export enum BuildStatus {
|
||||
// (undocumented)
|
||||
'failure' = 1,
|
||||
// (undocumented)
|
||||
'pending' = 2,
|
||||
// (undocumented)
|
||||
'running' = 3,
|
||||
// (undocumented)
|
||||
'success' = 0
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityGithubActionsContent: (_props: {
|
||||
entity?: Entity| undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityLatestGithubActionRunCard: ({ branch, variant, }: {
|
||||
entity?: Entity| undefined;
|
||||
branch: string;
|
||||
variant?: InfoCardVariants| undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityLatestGithubActionsForBranchCard: ({ branch, variant, }: {
|
||||
entity?: Entity| undefined;
|
||||
branch: string;
|
||||
variant?: InfoCardVariants| undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityRecentGithubActionsRunsCard: ({ branch, dense, limit, variant, }: Props) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const GITHUB_ACTIONS_ANNOTATION = "github.com/project-slug";
|
||||
|
||||
// @public (undocumented)
|
||||
export type GithubActionsApi = {
|
||||
listWorkflowRuns: ({ hostname, owner, repo, pageSize, page, branch, }: {
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
pageSize?: number;
|
||||
page?: number;
|
||||
branch?: string;
|
||||
}) => Promise<RestEndpointMethodTypes['actions']['listWorkflowRuns']['response']['data']>;
|
||||
getWorkflow: ({ hostname, owner, repo, id, }: {
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
id: number;
|
||||
}) => Promise<RestEndpointMethodTypes['actions']['getWorkflow']['response']['data']>;
|
||||
getWorkflowRun: ({ hostname, owner, repo, id, }: {
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
id: number;
|
||||
}) => Promise<RestEndpointMethodTypes['actions']['getWorkflowRun']['response']['data']>;
|
||||
reRunWorkflow: ({ hostname, owner, repo, runId, }: {
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
runId: number;
|
||||
}) => Promise<any>;
|
||||
listJobsForWorkflowRun: ({ hostname, owner, repo, id, pageSize, page, }: {
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
id: number;
|
||||
pageSize?: number;
|
||||
page?: number;
|
||||
}) => Promise<RestEndpointMethodTypes['actions']['listJobsForWorkflowRun']['response']['data']>;
|
||||
downloadJobLogsForWorkflowRun: ({ hostname, owner, repo, runId, }: {
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
runId: number;
|
||||
}) => Promise<RestEndpointMethodTypes['actions']['downloadJobLogsForWorkflowRun']['response']['data']>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const githubActionsApiRef: ApiRef<GithubActionsApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class GithubActionsClient implements GithubActionsApi {
|
||||
constructor(options: {
|
||||
configApi: ConfigApi;
|
||||
githubAuthApi: OAuthApi;
|
||||
});
|
||||
// (undocumented)
|
||||
downloadJobLogsForWorkflowRun({ hostname, owner, repo, runId, }: {
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
runId: number;
|
||||
}): Promise<RestEndpointMethodTypes['actions']['downloadJobLogsForWorkflowRun']['response']['data']>;
|
||||
// (undocumented)
|
||||
getWorkflow({ hostname, owner, repo, id, }: {
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
id: number;
|
||||
}): Promise<RestEndpointMethodTypes['actions']['getWorkflow']['response']['data']>;
|
||||
// (undocumented)
|
||||
getWorkflowRun({ hostname, owner, repo, id, }: {
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
id: number;
|
||||
}): Promise<RestEndpointMethodTypes['actions']['getWorkflowRun']['response']['data']>;
|
||||
// (undocumented)
|
||||
listJobsForWorkflowRun({ hostname, owner, repo, id, pageSize, page, }: {
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
id: number;
|
||||
pageSize?: number;
|
||||
page?: number;
|
||||
}): Promise<RestEndpointMethodTypes['actions']['listJobsForWorkflowRun']['response']['data']>;
|
||||
// (undocumented)
|
||||
listWorkflowRuns({ hostname, owner, repo, pageSize, page, branch, }: {
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
pageSize?: number;
|
||||
page?: number;
|
||||
branch?: string;
|
||||
}): Promise<RestEndpointMethodTypes['actions']['listWorkflowRuns']['response']['data']>;
|
||||
// (undocumented)
|
||||
reRunWorkflow({ hostname, owner, repo, runId, }: {
|
||||
hostname?: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
runId: number;
|
||||
}): Promise<any>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
const githubActionsPlugin: BackstagePlugin<{
|
||||
entityContent: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
export { githubActionsPlugin }
|
||||
|
||||
export { githubActionsPlugin as plugin }
|
||||
|
||||
// @public (undocumented)
|
||||
const isGithubActionsAvailable: (entity: Entity) => boolean;
|
||||
|
||||
export { isGithubActionsAvailable }
|
||||
|
||||
export { isGithubActionsAvailable as isPluginApplicableToEntity }
|
||||
|
||||
// @public (undocumented)
|
||||
export type Job = {
|
||||
html_url: string;
|
||||
status: string;
|
||||
conclusion: string;
|
||||
started_at: string;
|
||||
completed_at: string;
|
||||
id: number;
|
||||
name: string;
|
||||
steps: Step[];
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type Jobs = {
|
||||
total_count: number;
|
||||
jobs: Job[];
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const LatestWorkflowRunCard: ({ branch, variant, }: Props_3) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const LatestWorkflowsForBranchCard: ({ branch, variant, }: Props_3) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const RecentWorkflowRunsCard: ({ branch, dense, limit, variant, }: Props) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const Router: (_props: Props_2) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type Step = {
|
||||
name: string;
|
||||
status: string;
|
||||
conclusion?: string;
|
||||
number: number;
|
||||
started_at: string;
|
||||
completed_at: string;
|
||||
};
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,197 @@
|
||||
## API Report File for "@backstage/plugin-gitops-profiles"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ApplyProfileRequest {
|
||||
// (undocumented)
|
||||
gitHubToken: string;
|
||||
// (undocumented)
|
||||
gitHubUser: string;
|
||||
// (undocumented)
|
||||
profiles: string[];
|
||||
// (undocumented)
|
||||
targetOrg: string;
|
||||
// (undocumented)
|
||||
targetRepo: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ChangeClusterStateRequest {
|
||||
// (undocumented)
|
||||
clusterState: 'present' | 'absent';
|
||||
// (undocumented)
|
||||
gitHubToken: string;
|
||||
// (undocumented)
|
||||
gitHubUser: string;
|
||||
// (undocumented)
|
||||
targetOrg: string;
|
||||
// (undocumented)
|
||||
targetRepo: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CloneFromTemplateRequest {
|
||||
// (undocumented)
|
||||
gitHubToken: string;
|
||||
// (undocumented)
|
||||
gitHubUser: string;
|
||||
// (undocumented)
|
||||
secrets: {
|
||||
awsAccessKeyId: string;
|
||||
awsSecretAccessKey: string;
|
||||
};
|
||||
// (undocumented)
|
||||
targetOrg: string;
|
||||
// (undocumented)
|
||||
targetRepo: string;
|
||||
// (undocumented)
|
||||
templateRepository: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ClusterStatus {
|
||||
// (undocumented)
|
||||
conclusion: string;
|
||||
// (undocumented)
|
||||
link: string;
|
||||
// (undocumented)
|
||||
name: string;
|
||||
// (undocumented)
|
||||
runStatus: Status[];
|
||||
// (undocumented)
|
||||
status: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class FetchError extends Error {
|
||||
// (undocumented)
|
||||
static forResponse(resp: Response): Promise<FetchError>;
|
||||
// (undocumented)
|
||||
get name(): string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface GithubUserInfoRequest {
|
||||
// (undocumented)
|
||||
accessToken: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface GithubUserInfoResponse {
|
||||
// (undocumented)
|
||||
login: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type GitOpsApi = {
|
||||
url: string;
|
||||
fetchLog(req: PollLogRequest): Promise<StatusResponse>;
|
||||
changeClusterState(req: ChangeClusterStateRequest): Promise<any>;
|
||||
cloneClusterFromTemplate(req: CloneFromTemplateRequest): Promise<any>;
|
||||
applyProfiles(req: ApplyProfileRequest): Promise<any>;
|
||||
listClusters(req: ListClusterRequest): Promise<ListClusterStatusesResponse>;
|
||||
fetchUserInfo(req: GithubUserInfoRequest): Promise<GithubUserInfoResponse>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const gitOpsApiRef: ApiRef<GitOpsApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const GitopsProfilesClusterListPage: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const GitopsProfilesClusterPage: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const GitopsProfilesCreatePage: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
const gitopsProfilesPlugin: BackstagePlugin<{
|
||||
listPage: RouteRef<undefined>;
|
||||
detailsPage: RouteRef<{
|
||||
owner: string;
|
||||
repo: string;
|
||||
}>;
|
||||
createPage: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
export { gitopsProfilesPlugin }
|
||||
|
||||
export { gitopsProfilesPlugin as plugin }
|
||||
|
||||
// @public (undocumented)
|
||||
export class GitOpsRestApi implements GitOpsApi {
|
||||
constructor(url?: string);
|
||||
// (undocumented)
|
||||
applyProfiles(req: ApplyProfileRequest): Promise<any>;
|
||||
// (undocumented)
|
||||
changeClusterState(req: ChangeClusterStateRequest): Promise<any>;
|
||||
// (undocumented)
|
||||
cloneClusterFromTemplate(req: CloneFromTemplateRequest): Promise<any>;
|
||||
// (undocumented)
|
||||
fetchLog(req: PollLogRequest): Promise<StatusResponse>;
|
||||
// (undocumented)
|
||||
fetchUserInfo(req: GithubUserInfoRequest): Promise<GithubUserInfoResponse>;
|
||||
// (undocumented)
|
||||
listClusters(req: ListClusterRequest): Promise<ListClusterStatusesResponse>;
|
||||
// (undocumented)
|
||||
url: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ListClusterRequest {
|
||||
// (undocumented)
|
||||
gitHubToken: string;
|
||||
// (undocumented)
|
||||
gitHubUser: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ListClusterStatusesResponse {
|
||||
// (undocumented)
|
||||
result: ClusterStatus[];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface PollLogRequest {
|
||||
// (undocumented)
|
||||
gitHubToken: string;
|
||||
// (undocumented)
|
||||
gitHubUser: string;
|
||||
// (undocumented)
|
||||
targetOrg: string;
|
||||
// (undocumented)
|
||||
targetRepo: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface Status {
|
||||
// (undocumented)
|
||||
conclusion: string;
|
||||
// (undocumented)
|
||||
message: string;
|
||||
// (undocumented)
|
||||
status: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface StatusResponse {
|
||||
// (undocumented)
|
||||
link: string;
|
||||
// (undocumented)
|
||||
result: Status[];
|
||||
// (undocumented)
|
||||
status: string;
|
||||
}
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,82 @@
|
||||
## API Report File for "@backstage/plugin-graphiql"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core-plugin-api';
|
||||
import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { ErrorApi } from '@backstage/core-plugin-api';
|
||||
import { IconComponent } from '@backstage/core';
|
||||
import { OAuthApi } from '@backstage/core-plugin-api';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
// @public (undocumented)
|
||||
export type EndpointConfig = {
|
||||
id: string;
|
||||
title: string;
|
||||
url: string;
|
||||
method?: 'POST';
|
||||
headers?: {
|
||||
[name in string]: string;
|
||||
};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type GithubEndpointConfig = {
|
||||
id: string;
|
||||
title: string;
|
||||
url?: string;
|
||||
errorApi?: ErrorApi;
|
||||
githubAuthApi: OAuthApi;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const GraphiQLIcon: IconComponent;
|
||||
|
||||
// @public (undocumented)
|
||||
export const GraphiQLPage: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
const graphiqlPlugin: BackstagePlugin<{}, {}>;
|
||||
|
||||
export { graphiqlPlugin }
|
||||
|
||||
export { graphiqlPlugin as plugin }
|
||||
|
||||
// @public (undocumented)
|
||||
export const graphiQLRouteRef: RouteRef<undefined>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type GraphQLBrowseApi = {
|
||||
getEndpoints(): Promise<GraphQLEndpoint[]>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const graphQlBrowseApiRef: ApiRef<GraphQLBrowseApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type GraphQLEndpoint = {
|
||||
id: string;
|
||||
title: string;
|
||||
fetcher: (body: any) => Promise<any>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export class GraphQLEndpoints implements GraphQLBrowseApi {
|
||||
// (undocumented)
|
||||
static create(config: EndpointConfig): GraphQLEndpoint;
|
||||
// (undocumented)
|
||||
static from(endpoints: GraphQLEndpoint[]): GraphQLEndpoints;
|
||||
// (undocumented)
|
||||
getEndpoints(): Promise<GraphQLEndpoint[]>;
|
||||
static github(config: GithubEndpointConfig): GraphQLEndpoint;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const Router: () => JSX.Element;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
## API Report File for "@backstage/plugin-graphql-backend"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
// @public (undocumented)
|
||||
export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RouterOptions {
|
||||
// (undocumented)
|
||||
config: Config;
|
||||
// (undocumented)
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,205 @@
|
||||
## API Report File for "@backstage/plugin-ilert"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { ConfigApi } from '@backstage/core';
|
||||
import { DiscoveryApi } from '@backstage/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { IconComponent } from '@backstage/core';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityILertCard: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type GetIncidentsCountOpts = {
|
||||
states?: IncidentStatus[];
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type GetIncidentsOpts = {
|
||||
maxResults?: number;
|
||||
startIndex?: number;
|
||||
states?: IncidentStatus[];
|
||||
alertSources?: number[];
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ILertApi {
|
||||
// (undocumented)
|
||||
acceptIncident(incident: Incident, userName: string): Promise<Incident>;
|
||||
// (undocumented)
|
||||
addImmediateMaintenance(alertSourceId: number, minutes: number): Promise<void>;
|
||||
// (undocumented)
|
||||
assignIncident(incident: Incident, responder: IncidentResponder): Promise<Incident>;
|
||||
// (undocumented)
|
||||
createIncident(eventRequest: EventRequest): Promise<boolean>;
|
||||
// (undocumented)
|
||||
disableAlertSource(alertSource: AlertSource): Promise<AlertSource>;
|
||||
// (undocumented)
|
||||
enableAlertSource(alertSource: AlertSource): Promise<AlertSource>;
|
||||
// (undocumented)
|
||||
fetchAlertSource(idOrIntegrationKey: number | string): Promise<AlertSource>;
|
||||
// (undocumented)
|
||||
fetchAlertSourceOnCalls(alertSource: AlertSource): Promise<OnCall[]>;
|
||||
// (undocumented)
|
||||
fetchAlertSources(): Promise<AlertSource[]>;
|
||||
// (undocumented)
|
||||
fetchIncident(id: number): Promise<Incident>;
|
||||
// (undocumented)
|
||||
fetchIncidentActions(incident: Incident): Promise<IncidentAction[]>;
|
||||
// (undocumented)
|
||||
fetchIncidentResponders(incident: Incident): Promise<IncidentResponder[]>;
|
||||
// (undocumented)
|
||||
fetchIncidents(opts?: GetIncidentsOpts): Promise<Incident[]>;
|
||||
// (undocumented)
|
||||
fetchIncidentsCount(opts?: GetIncidentsCountOpts): Promise<number>;
|
||||
// (undocumented)
|
||||
fetchOnCallSchedules(): Promise<Schedule[]>;
|
||||
// (undocumented)
|
||||
fetchUptimeMonitor(id: number): Promise<UptimeMonitor>;
|
||||
// (undocumented)
|
||||
fetchUptimeMonitors(): Promise<UptimeMonitor[]>;
|
||||
// (undocumented)
|
||||
fetchUsers(): Promise<User[]>;
|
||||
// (undocumented)
|
||||
getAlertSourceDetailsURL(alertSource: AlertSource | null): string;
|
||||
// (undocumented)
|
||||
getEscalationPolicyDetailsURL(escalationPolicy: EscalationPolicy): string;
|
||||
// (undocumented)
|
||||
getIncidentDetailsURL(incident: Incident): string;
|
||||
// (undocumented)
|
||||
getScheduleDetailsURL(schedule: Schedule): string;
|
||||
// (undocumented)
|
||||
getUptimeMonitorDetailsURL(uptimeMonitor: UptimeMonitor): string;
|
||||
// (undocumented)
|
||||
getUserInitials(user: User | null): string;
|
||||
// (undocumented)
|
||||
getUserPhoneNumber(user: User | null): string;
|
||||
// (undocumented)
|
||||
overrideShift(scheduleId: number, userId: number, start: string, end: string): Promise<Schedule>;
|
||||
// (undocumented)
|
||||
pauseUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise<UptimeMonitor>;
|
||||
// (undocumented)
|
||||
resolveIncident(incident: Incident, userName: string): Promise<Incident>;
|
||||
// (undocumented)
|
||||
resumeUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise<UptimeMonitor>;
|
||||
// (undocumented)
|
||||
triggerIncidentAction(incident: Incident, action: IncidentAction): Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const ilertApiRef: ApiRef<ILertApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ILertCard: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export class ILertClient implements ILertApi {
|
||||
constructor(opts: Options);
|
||||
// (undocumented)
|
||||
acceptIncident(incident: Incident, userName: string): Promise<Incident>;
|
||||
// (undocumented)
|
||||
addImmediateMaintenance(alertSourceId: number, minutes: number): Promise<void>;
|
||||
// (undocumented)
|
||||
assignIncident(incident: Incident, responder: IncidentResponder): Promise<Incident>;
|
||||
// (undocumented)
|
||||
createIncident(eventRequest: EventRequest): Promise<boolean>;
|
||||
// (undocumented)
|
||||
disableAlertSource(alertSource: AlertSource): Promise<AlertSource>;
|
||||
// (undocumented)
|
||||
enableAlertSource(alertSource: AlertSource): Promise<AlertSource>;
|
||||
// (undocumented)
|
||||
fetchAlertSource(idOrIntegrationKey: number | string): Promise<AlertSource>;
|
||||
// (undocumented)
|
||||
fetchAlertSourceOnCalls(alertSource: AlertSource): Promise<OnCall[]>;
|
||||
// (undocumented)
|
||||
fetchAlertSources(): Promise<AlertSource[]>;
|
||||
// (undocumented)
|
||||
fetchIncident(id: number): Promise<Incident>;
|
||||
// (undocumented)
|
||||
fetchIncidentActions(incident: Incident): Promise<IncidentAction[]>;
|
||||
// (undocumented)
|
||||
fetchIncidentResponders(incident: Incident): Promise<IncidentResponder[]>;
|
||||
// (undocumented)
|
||||
fetchIncidents(opts?: GetIncidentsOpts): Promise<Incident[]>;
|
||||
// (undocumented)
|
||||
fetchIncidentsCount(opts?: GetIncidentsCountOpts): Promise<number>;
|
||||
// (undocumented)
|
||||
fetchOnCallSchedules(): Promise<Schedule[]>;
|
||||
// (undocumented)
|
||||
fetchUptimeMonitor(id: number): Promise<UptimeMonitor>;
|
||||
// (undocumented)
|
||||
fetchUptimeMonitors(): Promise<UptimeMonitor[]>;
|
||||
// (undocumented)
|
||||
fetchUsers(): Promise<User[]>;
|
||||
// (undocumented)
|
||||
static fromConfig(configApi: ConfigApi, discoveryApi: DiscoveryApi): ILertClient;
|
||||
// (undocumented)
|
||||
getAlertSourceDetailsURL(alertSource: AlertSource | null): string;
|
||||
// (undocumented)
|
||||
getEscalationPolicyDetailsURL(escalationPolicy: EscalationPolicy): string;
|
||||
// (undocumented)
|
||||
getIncidentDetailsURL(incident: Incident): string;
|
||||
// (undocumented)
|
||||
getScheduleDetailsURL(schedule: Schedule): string;
|
||||
// (undocumented)
|
||||
getUptimeMonitorDetailsURL(uptimeMonitor: UptimeMonitor): string;
|
||||
// (undocumented)
|
||||
getUserInitials(user: User | null): string;
|
||||
// (undocumented)
|
||||
getUserPhoneNumber(user: User | null): string;
|
||||
// (undocumented)
|
||||
overrideShift(scheduleId: number, userId: number, start: string, end: string): Promise<Schedule>;
|
||||
// (undocumented)
|
||||
pauseUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise<UptimeMonitor>;
|
||||
// (undocumented)
|
||||
resolveIncident(incident: Incident, userName: string): Promise<Incident>;
|
||||
// (undocumented)
|
||||
resumeUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise<UptimeMonitor>;
|
||||
// (undocumented)
|
||||
triggerIncidentAction(incident: Incident, action: IncidentAction): Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const ILertIcon: IconComponent;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ILertPage: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
const ilertPlugin: BackstagePlugin<{
|
||||
root: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
export { ilertPlugin }
|
||||
|
||||
export { ilertPlugin as plugin }
|
||||
|
||||
// @public (undocumented)
|
||||
export const iLertRouteRef: RouteRef<undefined>;
|
||||
|
||||
// @public (undocumented)
|
||||
const isPluginApplicableToEntity: (entity: Entity) => boolean;
|
||||
|
||||
export { isPluginApplicableToEntity as isILertAvailable }
|
||||
|
||||
export { isPluginApplicableToEntity }
|
||||
|
||||
// @public (undocumented)
|
||||
export const Router: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TableState = {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
};
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,83 @@
|
||||
## API Report File for "@backstage/plugin-jenkins"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { DiscoveryApi } from '@backstage/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { InfoCardVariants } from '@backstage/core';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityJenkinsContent: (_props: {
|
||||
entity?: Entity| undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityLatestJenkinsRunCard: ({ branch, variant, }: {
|
||||
branch: string;
|
||||
variant?: InfoCardVariants| undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
const isJenkinsAvailable: (entity: Entity) => boolean;
|
||||
|
||||
export { isJenkinsAvailable }
|
||||
|
||||
export { isJenkinsAvailable as isPluginApplicableToEntity }
|
||||
|
||||
// @public (undocumented)
|
||||
export const JENKINS_ANNOTATION = "jenkins.io/github-folder";
|
||||
|
||||
// @public (undocumented)
|
||||
export class JenkinsApi {
|
||||
constructor(options: Options);
|
||||
// (undocumented)
|
||||
extractJobDetailsFromBuildName(buildName: string): {
|
||||
jobName: string;
|
||||
buildNumber: number;
|
||||
};
|
||||
// (undocumented)
|
||||
extractScmDetailsFromJob(jobDetails: any): any | undefined;
|
||||
// (undocumented)
|
||||
getBuild(buildName: string): Promise<any>;
|
||||
// (undocumented)
|
||||
getFolder(folderName: string): Promise<CITableBuildInfo[]>;
|
||||
// (undocumented)
|
||||
getJob(jobName: string): Promise<any>;
|
||||
// (undocumented)
|
||||
getLastBuild(jobName: string): Promise<any>;
|
||||
// (undocumented)
|
||||
mapJenkinsBuildToCITable(jenkinsResult: any, jobScmInfo?: any): CITableBuildInfo;
|
||||
// (undocumented)
|
||||
retry(buildName: string): Promise<any>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const jenkinsApiRef: ApiRef<JenkinsApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
const jenkinsPlugin: BackstagePlugin<{
|
||||
entityContent: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
export { jenkinsPlugin }
|
||||
|
||||
export { jenkinsPlugin as plugin }
|
||||
|
||||
// @public (undocumented)
|
||||
export const LatestRunCard: ({ branch, variant, }: {
|
||||
branch: string;
|
||||
variant?: InfoCardVariants | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const Router: (_props: Props) => JSX.Element;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,17 @@
|
||||
## API Report File for "@backstage/plugin-kafka-backend"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
// @public (undocumented)
|
||||
export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,41 @@
|
||||
## API Report File for "@backstage/plugin-kafka"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityKafkaContent: (_props: {
|
||||
entity?: Entity| undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
const isPluginApplicableToEntity: (entity: Entity) => boolean;
|
||||
|
||||
export { isPluginApplicableToEntity as isKafkaAvailable }
|
||||
|
||||
export { isPluginApplicableToEntity }
|
||||
|
||||
// @public (undocumented)
|
||||
export const KAFKA_CONSUMER_GROUP_ANNOTATION = "kafka.apache.org/consumer-groups";
|
||||
|
||||
// @public (undocumented)
|
||||
const kafkaPlugin: BackstagePlugin<{
|
||||
entityContent: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
export { kafkaPlugin }
|
||||
|
||||
export { kafkaPlugin as plugin }
|
||||
|
||||
// @public (undocumented)
|
||||
export const Router: (_props: Props) => JSX.Element;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,103 @@
|
||||
## API Report File for "@backstage/plugin-kubernetes-backend"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import { FetchResponse } from '@backstage/plugin-kubernetes-common';
|
||||
import { KubernetesFetchError } from '@backstage/plugin-kubernetes-common';
|
||||
import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ClusterDetails {
|
||||
// (undocumented)
|
||||
authProvider: string;
|
||||
// (undocumented)
|
||||
name: string;
|
||||
// (undocumented)
|
||||
serviceAccountToken?: string | undefined;
|
||||
// (undocumented)
|
||||
skipTLSVerify?: boolean;
|
||||
// (undocumented)
|
||||
url: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CustomResource {
|
||||
// (undocumented)
|
||||
apiVersion: string;
|
||||
// (undocumented)
|
||||
group: string;
|
||||
// (undocumented)
|
||||
plural: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface FetchResponseWrapper {
|
||||
// (undocumented)
|
||||
errors: KubernetesFetchError[];
|
||||
// (undocumented)
|
||||
responses: FetchResponse[];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface KubernetesClustersSupplier {
|
||||
// (undocumented)
|
||||
getClusters(): Promise<ClusterDetails[]>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface KubernetesFetcher {
|
||||
// (undocumented)
|
||||
fetchObjectsForService(params: ObjectFetchParams): Promise<FetchResponseWrapper>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type KubernetesObjectTypes = 'pods' | 'services' | 'configmaps' | 'deployments' | 'replicasets' | 'horizontalpodautoscalers' | 'ingresses' | 'customresources';
|
||||
|
||||
// @public (undocumented)
|
||||
export interface KubernetesServiceLocator {
|
||||
// (undocumented)
|
||||
getClustersByServiceId(serviceId: string): Promise<ClusterDetails[]>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const makeRouter: (logger: Logger, kubernetesFanOutHandler: KubernetesFanOutHandler, clusterDetails: ClusterDetails[]) => express.Router;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ObjectFetchParams {
|
||||
// (undocumented)
|
||||
clusterDetails: ClusterDetails;
|
||||
// (undocumented)
|
||||
customResources: CustomResource[];
|
||||
// (undocumented)
|
||||
labelSelector: string;
|
||||
// (undocumented)
|
||||
objectTypesToFetch: Set<KubernetesObjectTypes>;
|
||||
// (undocumented)
|
||||
serviceId: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RouterOptions {
|
||||
// (undocumented)
|
||||
clusterSupplier?: KubernetesClustersSupplier;
|
||||
// (undocumented)
|
||||
config: Config;
|
||||
// (undocumented)
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type ServiceLocatorMethod = 'multiTenant' | 'http';
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,130 @@
|
||||
## API Report File for "@backstage/plugin-kubernetes-common"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { ExtensionsV1beta1Ingress } from '@kubernetes/client-node';
|
||||
import { V1ConfigMap } from '@kubernetes/client-node';
|
||||
import { V1Deployment } from '@kubernetes/client-node';
|
||||
import { V1HorizontalPodAutoscaler } from '@kubernetes/client-node';
|
||||
import { V1Pod } from '@kubernetes/client-node';
|
||||
import { V1ReplicaSet } from '@kubernetes/client-node';
|
||||
import { V1Service } from '@kubernetes/client-node';
|
||||
|
||||
// @public (undocumented)
|
||||
export type AuthProviderType = 'google' | 'serviceAccount' | 'aws';
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ClusterObjects {
|
||||
// (undocumented)
|
||||
cluster: {
|
||||
name: string;
|
||||
};
|
||||
// (undocumented)
|
||||
errors: KubernetesFetchError[];
|
||||
// (undocumented)
|
||||
resources: FetchResponse[];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ConfigMapFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1ConfigMap>;
|
||||
// (undocumented)
|
||||
type: 'configmaps';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CustomResourceFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<any>;
|
||||
// (undocumented)
|
||||
type: 'customresources';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface DeploymentFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1Deployment>;
|
||||
// (undocumented)
|
||||
type: 'deployments';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type FetchResponse = PodFetchResponse | ServiceFetchResponse | ConfigMapFetchResponse | DeploymentFetchResponse | ReplicaSetsFetchResponse | HorizontalPodAutoscalersFetchResponse | IngressesFetchResponse | CustomResourceFetchResponse;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface HorizontalPodAutoscalersFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1HorizontalPodAutoscaler>;
|
||||
// (undocumented)
|
||||
type: 'horizontalpodautoscalers';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface IngressesFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<ExtensionsV1beta1Ingress>;
|
||||
// (undocumented)
|
||||
type: 'ingresses';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type KubernetesErrorTypes = 'BAD_REQUEST' | 'UNAUTHORIZED_ERROR' | 'SYSTEM_ERROR' | 'UNKNOWN_ERROR';
|
||||
|
||||
// @public (undocumented)
|
||||
export interface KubernetesFetchError {
|
||||
// (undocumented)
|
||||
errorType: KubernetesErrorTypes;
|
||||
// (undocumented)
|
||||
resourcePath?: string;
|
||||
// (undocumented)
|
||||
statusCode?: number;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface KubernetesRequestBody {
|
||||
// (undocumented)
|
||||
auth?: {
|
||||
google?: string;
|
||||
};
|
||||
// (undocumented)
|
||||
entity: Entity;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ObjectsByEntityResponse {
|
||||
// (undocumented)
|
||||
items: ClusterObjects[];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface PodFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1Pod>;
|
||||
// (undocumented)
|
||||
type: 'pods';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ReplicaSetsFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1ReplicaSet>;
|
||||
// (undocumented)
|
||||
type: 'replicasets';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ServiceFetchResponse {
|
||||
// (undocumented)
|
||||
resources: Array<V1Service>;
|
||||
// (undocumented)
|
||||
type: 'services';
|
||||
}
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,46 @@
|
||||
## API Report File for "@backstage/plugin-kubernetes"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';
|
||||
import { OAuthApi } from '@backstage/core';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityKubernetesContent: (_props: {
|
||||
entity?: Entity| undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export class KubernetesAuthProviders implements KubernetesAuthProvidersApi {
|
||||
constructor(options: {
|
||||
googleAuthApi: OAuthApi;
|
||||
});
|
||||
// (undocumented)
|
||||
decorateRequestBodyForAuth(authProvider: string, requestBody: KubernetesRequestBody): Promise<KubernetesRequestBody>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const kubernetesAuthProvidersApiRef: ApiRef<KubernetesAuthProvidersApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
const kubernetesPlugin: BackstagePlugin<{
|
||||
entityContent: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
export { kubernetesPlugin }
|
||||
|
||||
export { kubernetesPlugin as plugin }
|
||||
|
||||
// @public (undocumented)
|
||||
export const Router: (_props: Props) => JSX.Element;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,186 @@
|
||||
## API Report File for "@backstage/plugin-lighthouse"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { Config } from '@backstage/config';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { InfoCardVariants } from '@backstage/core';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export type Audit = AuditRunning | AuditFailed | AuditCompleted;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface AuditCompleted extends AuditBase {
|
||||
// (undocumented)
|
||||
categories: Record<LighthouseCategoryId, LighthouseCategoryAbbr>;
|
||||
// (undocumented)
|
||||
report: Object;
|
||||
// (undocumented)
|
||||
status: 'COMPLETED';
|
||||
// (undocumented)
|
||||
timeCompleted: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface AuditFailed extends AuditBase {
|
||||
// (undocumented)
|
||||
status: 'FAILED';
|
||||
// (undocumented)
|
||||
timeCompleted: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface AuditRunning extends AuditBase {
|
||||
// (undocumented)
|
||||
status: 'RUNNING';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const EmbeddedRouter: (_props: Props) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityLastLighthouseAuditCard: ({ dense, variant, }: {
|
||||
dense?: boolean | undefined;
|
||||
variant?: InfoCardVariants| undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityLighthouseContent: (_props: {
|
||||
entity?: Entity| undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export class FetchError extends Error {
|
||||
// (undocumented)
|
||||
static forResponse(resp: Response): Promise<FetchError>;
|
||||
// (undocumented)
|
||||
get name(): string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
const isLighthouseAvailable: (entity: Entity) => boolean;
|
||||
|
||||
export { isLighthouseAvailable }
|
||||
|
||||
export { isLighthouseAvailable as isPluginApplicableToEntity }
|
||||
|
||||
// @public (undocumented)
|
||||
export interface LASListRequest {
|
||||
// (undocumented)
|
||||
limit?: number;
|
||||
// (undocumented)
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface LASListResponse<Item> {
|
||||
// (undocumented)
|
||||
items: Item[];
|
||||
// (undocumented)
|
||||
limit: number;
|
||||
// (undocumented)
|
||||
offset: number;
|
||||
// (undocumented)
|
||||
total: number;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const LastLighthouseAuditCard: ({ dense, variant, }: {
|
||||
dense?: boolean | undefined;
|
||||
variant?: InfoCardVariants | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type LighthouseApi = {
|
||||
url: string;
|
||||
getWebsiteList: (listOptions: LASListRequest) => Promise<WebsiteListResponse>;
|
||||
getWebsiteForAuditId: (auditId: string) => Promise<Website>;
|
||||
triggerAudit: (payload: TriggerAuditPayload) => Promise<Audit>;
|
||||
getWebsiteByUrl: (websiteUrl: string) => Promise<Website>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const lighthouseApiRef: ApiRef<LighthouseApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface LighthouseCategoryAbbr {
|
||||
// (undocumented)
|
||||
id: LighthouseCategoryId;
|
||||
// (undocumented)
|
||||
score: number;
|
||||
// (undocumented)
|
||||
title: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type LighthouseCategoryId = 'pwa' | 'seo' | 'performance' | 'accessibility' | 'best-practices';
|
||||
|
||||
// @public (undocumented)
|
||||
export const LighthousePage: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
const lighthousePlugin: BackstagePlugin<{
|
||||
root: RouteRef<undefined>;
|
||||
entityContent: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
export { lighthousePlugin }
|
||||
|
||||
export { lighthousePlugin as plugin }
|
||||
|
||||
// @public (undocumented)
|
||||
export class LighthouseRestApi implements LighthouseApi {
|
||||
constructor(url: string);
|
||||
// (undocumented)
|
||||
static fromConfig(config: Config): LighthouseRestApi;
|
||||
// (undocumented)
|
||||
getWebsiteByUrl(websiteUrl: string): Promise<Website>;
|
||||
// (undocumented)
|
||||
getWebsiteForAuditId(auditId: string): Promise<Website>;
|
||||
// (undocumented)
|
||||
getWebsiteList({ limit, offset, }?: LASListRequest): Promise<WebsiteListResponse>;
|
||||
// (undocumented)
|
||||
triggerAudit(payload: TriggerAuditPayload): Promise<Audit>;
|
||||
// (undocumented)
|
||||
url: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const Router: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TriggerAuditPayload {
|
||||
// (undocumented)
|
||||
options: {
|
||||
lighthouseConfig: {
|
||||
settings: {
|
||||
emulatedFormFactor: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
// (undocumented)
|
||||
url: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface Website {
|
||||
// (undocumented)
|
||||
audits: Audit[];
|
||||
// (undocumented)
|
||||
lastAudit: Audit;
|
||||
// (undocumented)
|
||||
url: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type WebsiteListResponse = LASListResponse<Website>;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
## API Report File for "@backstage/plugin-newrelic"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export const NewRelicPage: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
const newRelicPlugin: BackstagePlugin<{
|
||||
root: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
export { newRelicPlugin }
|
||||
|
||||
export { newRelicPlugin as plugin }
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,69 @@
|
||||
## API Report File for "@backstage/plugin-org"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { GroupEntity } from '@backstage/catalog-model';
|
||||
import { InfoCardVariants } from '@backstage/core';
|
||||
import { UserEntity } from '@backstage/catalog-model';
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityGroupProfileCard: ({ variant, }: {
|
||||
entity?: GroupEntity| undefined;
|
||||
variant?: InfoCardVariants| undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityMembersListCard: (_props: {
|
||||
entity?: GroupEntity| undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityOwnershipCard: ({ variant, }: {
|
||||
entity?: Entity| undefined;
|
||||
variant?: InfoCardVariants| undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityUserProfileCard: ({ variant, }: {
|
||||
entity?: UserEntity| undefined;
|
||||
variant?: InfoCardVariants| undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const GroupProfileCard: ({ variant, }: {
|
||||
entity?: GroupEntity | undefined;
|
||||
variant?: InfoCardVariants | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const MembersListCard: (_props: {
|
||||
entity?: GroupEntity;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
const orgPlugin: BackstagePlugin<{}, {}>;
|
||||
|
||||
export { orgPlugin }
|
||||
|
||||
export { orgPlugin as plugin }
|
||||
|
||||
// @public (undocumented)
|
||||
export const OwnershipCard: ({ variant, }: {
|
||||
entity?: Entity | undefined;
|
||||
variant?: InfoCardVariants | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const UserProfileCard: ({ variant, }: {
|
||||
entity?: UserEntity | undefined;
|
||||
variant?: InfoCardVariants | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,62 @@
|
||||
## API Report File for "@backstage/plugin-pagerduty"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { ConfigApi } from '@backstage/core';
|
||||
import { DiscoveryApi } from '@backstage/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityPagerDutyCard: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
const isPluginApplicableToEntity: (entity: Entity) => boolean;
|
||||
|
||||
export { isPluginApplicableToEntity as isPagerDutyAvailable }
|
||||
|
||||
export { isPluginApplicableToEntity }
|
||||
|
||||
// @public (undocumented)
|
||||
export const pagerDutyApiRef: ApiRef<PagerDutyApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const PagerDutyCard: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export class PagerDutyClient implements PagerDutyApi {
|
||||
constructor(config: ClientApiConfig);
|
||||
// (undocumented)
|
||||
static fromConfig(configApi: ConfigApi, discoveryApi: DiscoveryApi): PagerDutyClient;
|
||||
// (undocumented)
|
||||
getIncidentsByServiceId(serviceId: string): Promise<Incident[]>;
|
||||
// (undocumented)
|
||||
getOnCallByPolicyId(policyId: string): Promise<OnCall[]>;
|
||||
// (undocumented)
|
||||
getServiceByIntegrationKey(integrationKey: string): Promise<Service[]>;
|
||||
// (undocumented)
|
||||
triggerAlarm({ integrationKey, source, description, userName, }: TriggerAlarmRequest): Promise<Response>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
const pagerDutyPlugin: BackstagePlugin<{}, {}>;
|
||||
|
||||
export { pagerDutyPlugin }
|
||||
|
||||
export { pagerDutyPlugin as plugin }
|
||||
|
||||
// @public (undocumented)
|
||||
export function TriggerButton({ children, }: PropsWithChildren<TriggerButtonProps>): JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export class UnauthorizedError extends Error {
|
||||
}
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,18 @@
|
||||
## API Report File for "@backstage/plugin-proxy-backend"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import { Logger } from 'winston';
|
||||
import { PluginEndpointDiscovery } from '@backstage/backend-common';
|
||||
|
||||
// @public (undocumented)
|
||||
export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,32 @@
|
||||
## API Report File for "@backstage/plugin-register-component"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export const RegisterComponentPage: ({ catalogRouteRef, }: {
|
||||
catalogRouteRef: RouteRef<any>;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
const registerComponentPlugin: BackstagePlugin<{
|
||||
root: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
export { registerComponentPlugin as plugin }
|
||||
|
||||
export { registerComponentPlugin }
|
||||
|
||||
// @public @deprecated
|
||||
export const Router: ({ catalogRouteRef }: {
|
||||
catalogRouteRef: RouteRef;
|
||||
}) => JSX.Element;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,60 @@
|
||||
## API Report File for "@backstage/plugin-rollbar-backend"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
// @public (undocumented)
|
||||
export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function getRequestHeaders(token: string): {
|
||||
headers: {
|
||||
'X-Rollbar-Access-Token': string;
|
||||
};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export class RollbarApi {
|
||||
constructor(accessToken: string, logger: Logger);
|
||||
// (undocumented)
|
||||
getActivatedCounts(projectName: string, options?: {
|
||||
environment: string;
|
||||
item_id?: number;
|
||||
}): Promise<RollbarItemCount[]>;
|
||||
// (undocumented)
|
||||
getAllProjects(): Promise<RollbarProject[]>;
|
||||
// (undocumented)
|
||||
getOccuranceCounts(projectName: string, options?: {
|
||||
environment: string;
|
||||
item_id?: number;
|
||||
}): Promise<RollbarItemCount[]>;
|
||||
// (undocumented)
|
||||
getProject(projectName: string): Promise<RollbarProject>;
|
||||
// (undocumented)
|
||||
getProjectItems(projectName: string): Promise<RollbarItemsResponse>;
|
||||
// (undocumented)
|
||||
getTopActiveItems(projectName: string, options?: {
|
||||
hours: number;
|
||||
environment: string;
|
||||
}): Promise<RollbarTopActiveItem[]>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RouterOptions {
|
||||
// (undocumented)
|
||||
config: Config;
|
||||
// (undocumented)
|
||||
logger: Logger;
|
||||
// (undocumented)
|
||||
rollbarApi?: RollbarApi;
|
||||
}
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,78 @@
|
||||
## API Report File for "@backstage/plugin-rollbar"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { DiscoveryApi } from '@backstage/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { IdentityApi } from '@backstage/core';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityPageRollbar: (_props: Props) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityRollbarContent: (_props: {
|
||||
entity?: Entity| undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
const isPluginApplicableToEntity: (entity: Entity) => boolean;
|
||||
|
||||
export { isPluginApplicableToEntity }
|
||||
|
||||
export { isPluginApplicableToEntity as isRollbarAvailable }
|
||||
|
||||
// @public (undocumented)
|
||||
export const ROLLBAR_ANNOTATION = "rollbar.com/project-slug";
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RollbarApi {
|
||||
// (undocumented)
|
||||
getAllProjects(): Promise<RollbarProject[]>;
|
||||
// (undocumented)
|
||||
getProject(projectName: string): Promise<RollbarProject>;
|
||||
// (undocumented)
|
||||
getProjectItems(project: string): Promise<RollbarItemsResponse>;
|
||||
// (undocumented)
|
||||
getTopActiveItems(project: string, hours?: number): Promise<RollbarTopActiveItem[]>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const rollbarApiRef: ApiRef<RollbarApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class RollbarClient implements RollbarApi {
|
||||
constructor(options: {
|
||||
discoveryApi: DiscoveryApi;
|
||||
identityApi: IdentityApi;
|
||||
});
|
||||
// (undocumented)
|
||||
getAllProjects(): Promise<RollbarProject[]>;
|
||||
// (undocumented)
|
||||
getProject(projectName: string): Promise<RollbarProject>;
|
||||
// (undocumented)
|
||||
getProjectItems(project: string): Promise<RollbarItemsResponse>;
|
||||
// (undocumented)
|
||||
getTopActiveItems(project: string, hours?: number, environment?: string): Promise<RollbarTopActiveItem[]>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
const rollbarPlugin: BackstagePlugin<{
|
||||
entityContent: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
export { rollbarPlugin as plugin }
|
||||
|
||||
export { rollbarPlugin }
|
||||
|
||||
// @public (undocumented)
|
||||
export const Router: (_props: Props_2) => JSX.Element;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/plugin-scaffolder-backend
|
||||
|
||||
## 0.12.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- b49222176: Keep the empty string as empty string in `input` rather than replacing with `undefined` to make empty values ok for `cookiecutter`
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,25 +1,64 @@
|
||||
# Scaffolder Backend
|
||||
|
||||
Welcome to the scaffolder plugin!
|
||||
This is the backend for the default Backstage [software
|
||||
templates](https://backstage.io/docs/features/software-templates/software-templates-index).
|
||||
This provides the API for the frontend [scaffolder
|
||||
plugin](https://github.com/backstage/backstage/tree/master/plugins/scaffolder),
|
||||
as well as the built-in template actions, tasks and stages.
|
||||
|
||||
## Jobs
|
||||
## Installation
|
||||
|
||||
Documentation for `Jobs` here
|
||||
This `@backstage/plugin-scaffolder-backend` package comes installed by default
|
||||
in any Backstage application created with `npx @backstage/create-app`, so
|
||||
installation is not usually required.
|
||||
|
||||
## Stages
|
||||
To check if you already have the package, look under
|
||||
`packages/backend/package.json`, in the `dependencies` block, for
|
||||
`@backstage/plugin-scaffolder-backend`. The instructions below walk through
|
||||
restoring the plugin, if you previously removed it.
|
||||
|
||||
Documentation for `Stages` here
|
||||
### Install the package
|
||||
|
||||
## Tasks
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
cd packages/backend
|
||||
yarn add @backstage/plugin-scaffolder-backend
|
||||
```
|
||||
|
||||
Documentation for `Tasks` here
|
||||
### Adding the plugin to your `packages/backend`
|
||||
|
||||
## Actions
|
||||
You'll need to add the plugin to the router in your `backend` package. You can
|
||||
do this by creating a file called `packages/backend/src/plugins/scaffolder.ts`
|
||||
with contents matching [scaffolder.ts in the create-app
|
||||
template](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts).
|
||||
|
||||
### Built-in:
|
||||
With the `scaffolder.ts` router setup in place, add the router to
|
||||
`packages/backend/src/index.ts`:
|
||||
|
||||
- #### GitHub Pull Request
|
||||
- Minimum permissions required for GitHub App for creating a Pull Request with the built-in action:
|
||||
- Read and Write permissions for `Contents`.
|
||||
- Read and write permissions for `Pull Requests` and `Issues`.
|
||||
- Read permissions on `Metadata`.
|
||||
```diff
|
||||
+import scaffolder from './plugins/scaffolder';
|
||||
|
||||
async function main() {
|
||||
...
|
||||
const createEnv = makeCreateEnv(config);
|
||||
|
||||
const catalogEnv = useHotMemoize(module, () => createEnv('catalog'));
|
||||
+ const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder'));
|
||||
|
||||
const apiRouter = Router();
|
||||
+ apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv));
|
||||
...
|
||||
apiRouter.use(notFoundHandler());
|
||||
|
||||
```
|
||||
|
||||
### Adding templates
|
||||
|
||||
At this point the scaffolder backend is installed in your backend package, but
|
||||
you will not have any templates available to use. These need to be [added to the
|
||||
software
|
||||
catalog](https://backstage.io/docs/features/software-templates/adding-templates).
|
||||
|
||||
To get up and running and try out some templates quickly, you can or copy the
|
||||
catalog locations from the [create-app
|
||||
template](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/app-config.yaml.hbs).
|
||||
|
||||
@@ -0,0 +1,518 @@
|
||||
## API Report File for "@backstage/plugin-scaffolder-backend"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { AzureIntegrationConfig } from '@backstage/integration';
|
||||
import { BitbucketIntegrationConfig } from '@backstage/integration';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { Config } from '@backstage/config';
|
||||
import { ContainerRunner } from '@backstage/backend-common';
|
||||
import { createPullRequest } from 'octokit-plugin-create-pull-request';
|
||||
import express from 'express';
|
||||
import { GithubCredentialsProvider } from '@backstage/integration';
|
||||
import { GitHubIntegrationConfig } from '@backstage/integration';
|
||||
import { Gitlab } from '@gitbeaker/core';
|
||||
import { GitLabIntegrationConfig } from '@backstage/integration';
|
||||
import gitUrlParse from 'git-url-parse';
|
||||
import { JsonObject } from '@backstage/config';
|
||||
import { JsonValue } from '@backstage/config';
|
||||
import { Logger } from 'winston';
|
||||
import { PluginDatabaseManager } from '@backstage/backend-common';
|
||||
import { Schema } from 'jsonschema';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
import { TemplateEntityV1beta2 } from '@backstage/catalog-model';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
import { Writable } from 'stream';
|
||||
|
||||
// @public (undocumented)
|
||||
export type ActionContext<Input extends InputBase> = {
|
||||
baseUrl?: string;
|
||||
logger: Logger;
|
||||
logStream: Writable;
|
||||
token?: string | undefined;
|
||||
workspacePath: string;
|
||||
input: Input;
|
||||
output(name: string, value: JsonValue): void;
|
||||
createTemporaryDirectory(): Promise<string>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export class AzurePreparer implements PreparerBase {
|
||||
constructor(config: {
|
||||
token?: string;
|
||||
});
|
||||
// (undocumented)
|
||||
static fromConfig(config: AzureIntegrationConfig): AzurePreparer;
|
||||
// (undocumented)
|
||||
prepare({ url, workspacePath, logger }: PreparerOptions): Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class AzurePublisher implements PublisherBase {
|
||||
constructor(config: {
|
||||
token: string;
|
||||
});
|
||||
// (undocumented)
|
||||
static fromConfig(config: AzureIntegrationConfig): Promise<AzurePublisher | undefined>;
|
||||
// (undocumented)
|
||||
publish({ values, workspacePath, logger, }: PublisherOptions): Promise<PublisherResult>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class BitbucketPreparer implements PreparerBase {
|
||||
constructor(config: {
|
||||
username?: string;
|
||||
token?: string;
|
||||
appPassword?: string;
|
||||
});
|
||||
// (undocumented)
|
||||
static fromConfig(config: BitbucketIntegrationConfig): BitbucketPreparer;
|
||||
// (undocumented)
|
||||
prepare({ url, workspacePath, logger }: PreparerOptions): Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class BitbucketPublisher implements PublisherBase {
|
||||
constructor(config: {
|
||||
host: string;
|
||||
token?: string;
|
||||
appPassword?: string;
|
||||
username?: string;
|
||||
apiBaseUrl?: string;
|
||||
repoVisibility: RepoVisibilityOptions_2;
|
||||
});
|
||||
// (undocumented)
|
||||
static fromConfig(config: BitbucketIntegrationConfig, { repoVisibility }: {
|
||||
repoVisibility: RepoVisibilityOptions_2;
|
||||
}): Promise<BitbucketPublisher>;
|
||||
// (undocumented)
|
||||
publish({ values, workspacePath, logger, }: PublisherOptions): Promise<PublisherResult>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export class CatalogEntityClient {
|
||||
constructor(catalogClient: CatalogApi);
|
||||
findTemplate(templateName: string, options?: {
|
||||
token?: string;
|
||||
}): Promise<TemplateEntityV1alpha1 | TemplateEntityV1beta2>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class CookieCutter implements TemplaterBase {
|
||||
constructor({ containerRunner }: {
|
||||
containerRunner: ContainerRunner;
|
||||
});
|
||||
// (undocumented)
|
||||
run({ workspacePath, values, logStream, }: TemplaterRunOptions): Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const createBuiltinActions: (options: {
|
||||
reader: UrlReader;
|
||||
integrations: ScmIntegrations;
|
||||
catalogClient: CatalogApi;
|
||||
templaters: TemplaterBuilder;
|
||||
}) => TemplateAction<any>[];
|
||||
|
||||
// @public (undocumented)
|
||||
export function createCatalogRegisterAction(options: {
|
||||
catalogClient: CatalogApi;
|
||||
integrations: ScmIntegrations;
|
||||
}): TemplateAction<any>;
|
||||
|
||||
// @public
|
||||
export function createDebugLogAction(): TemplateAction<any>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function createFetchCookiecutterAction(options: {
|
||||
reader: UrlReader;
|
||||
integrations: ScmIntegrations;
|
||||
templaters: TemplaterBuilder;
|
||||
}): TemplateAction<any>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function createFetchPlainAction(options: {
|
||||
reader: UrlReader;
|
||||
integrations: ScmIntegrations;
|
||||
}): TemplateAction<any>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function createLegacyActions(options: Options): TemplateAction<any>[];
|
||||
|
||||
// @public (undocumented)
|
||||
export function createPublishAzureAction(options: {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
}): TemplateAction<any>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function createPublishBitbucketAction(options: {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
}): TemplateAction<any>;
|
||||
|
||||
// @public
|
||||
export function createPublishFileAction(): TemplateAction<any>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function createPublishGithubAction(options: {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
}): TemplateAction<any>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const createPublishGithubPullRequestAction: ({ integrations, clientFactory, }: CreateGithubPullRequestActionOptions) => TemplateAction<any>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function createPublishGitlabAction(options: {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
}): TemplateAction<any>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class CreateReactAppTemplater implements TemplaterBase {
|
||||
constructor({ containerRunner }: {
|
||||
containerRunner: ContainerRunner;
|
||||
});
|
||||
// (undocumented)
|
||||
run({ workspacePath, values, logStream, }: TemplaterRunOptions): Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const createTemplateAction: <Input extends Partial<{
|
||||
[name: string]: JsonValue| Partial<JsonObject> | undefined;
|
||||
}>>(templateAction: TemplateAction<Input>) => TemplateAction<any>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class FilePreparer implements PreparerBase {
|
||||
// (undocumented)
|
||||
prepare({ url, workspacePath }: PreparerOptions): Promise<void>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const getTemplaterKey: (entity: TemplateEntityV1alpha1) => string;
|
||||
|
||||
// @public (undocumented)
|
||||
export class GithubPreparer implements PreparerBase {
|
||||
constructor(config: {
|
||||
credentialsProvider: GithubCredentialsProvider;
|
||||
});
|
||||
// (undocumented)
|
||||
static fromConfig(config: GitHubIntegrationConfig): GithubPreparer;
|
||||
// (undocumented)
|
||||
prepare({ url, workspacePath, logger }: PreparerOptions): Promise<void>;
|
||||
}
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export class GithubPublisher implements PublisherBase {
|
||||
constructor(config: {
|
||||
credentialsProvider: GithubCredentialsProvider;
|
||||
repoVisibility: RepoVisibilityOptions;
|
||||
apiBaseUrl: string | undefined;
|
||||
});
|
||||
// (undocumented)
|
||||
static fromConfig(config: GitHubIntegrationConfig, { repoVisibility }: {
|
||||
repoVisibility: RepoVisibilityOptions;
|
||||
}): Promise<GithubPublisher | undefined>;
|
||||
// (undocumented)
|
||||
publish({ values, workspacePath, logger, }: PublisherOptions): Promise<PublisherResult>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class GitlabPreparer implements PreparerBase {
|
||||
constructor(config: {
|
||||
token?: string;
|
||||
});
|
||||
// (undocumented)
|
||||
static fromConfig(config: GitLabIntegrationConfig): GitlabPreparer;
|
||||
// (undocumented)
|
||||
prepare({ url, workspacePath, logger }: PreparerOptions): Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class GitlabPublisher implements PublisherBase {
|
||||
constructor(config: {
|
||||
token: string;
|
||||
client: Gitlab;
|
||||
repoVisibility: RepoVisibilityOptions_3;
|
||||
});
|
||||
// (undocumented)
|
||||
static fromConfig(config: GitLabIntegrationConfig, { repoVisibility }: {
|
||||
repoVisibility: RepoVisibilityOptions_3;
|
||||
}): Promise<GitlabPublisher | undefined>;
|
||||
// (undocumented)
|
||||
publish({ values, workspacePath, logger, }: PublisherOptions): Promise<PublisherResult>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type Job = {
|
||||
id: string;
|
||||
context: StageContext;
|
||||
status: ProcessorStatus;
|
||||
stages: StageResult[];
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type JobAndDirectoryTuple = {
|
||||
job: Job;
|
||||
directory: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export class JobProcessor implements Processor {
|
||||
constructor(workingDirectory: string);
|
||||
// (undocumented)
|
||||
create({ entity, values, stages, }: {
|
||||
entity: TemplateEntityV1alpha1;
|
||||
values: TemplaterValues;
|
||||
stages: StageInput[];
|
||||
}): Job;
|
||||
// (undocumented)
|
||||
static fromConfig({ config, logger, }: {
|
||||
config: Config;
|
||||
logger: Logger;
|
||||
}): Promise<JobProcessor>;
|
||||
// (undocumented)
|
||||
get(id: string): Job | undefined;
|
||||
// (undocumented)
|
||||
run(job: Job): Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export function joinGitUrlPath(repoUrl: string, path?: string): string;
|
||||
|
||||
// @public (undocumented)
|
||||
export type ParsedLocationAnnotation = {
|
||||
protocol: 'file' | 'url';
|
||||
location: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const parseLocationAnnotation: (entity: TemplateEntityV1alpha1) => ParsedLocationAnnotation;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface PreparerBase {
|
||||
prepare(opts: PreparerOptions): Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type PreparerBuilder = {
|
||||
register(host: string, preparer: PreparerBase): void;
|
||||
get(url: string): PreparerBase;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type PreparerOptions = {
|
||||
url: string;
|
||||
workspacePath: string;
|
||||
logger: Logger;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export class Preparers implements PreparerBuilder {
|
||||
// (undocumented)
|
||||
static fromConfig(config: Config, _: {
|
||||
logger: Logger;
|
||||
}): Promise<PreparerBuilder>;
|
||||
// (undocumented)
|
||||
get(url: string): PreparerBase;
|
||||
// (undocumented)
|
||||
register(host: string, preparer: PreparerBase): void;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type Processor = {
|
||||
create({ entity, values, stages, }: {
|
||||
entity: TemplateEntityV1alpha1;
|
||||
values: TemplaterValues;
|
||||
stages: StageInput[];
|
||||
}): Job;
|
||||
get(id: string): Job | undefined;
|
||||
run(job: Job): Promise<void>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type ProcessorStatus = 'PENDING' | 'STARTED' | 'COMPLETED' | 'FAILED';
|
||||
|
||||
// @public
|
||||
export type PublisherBase = {
|
||||
publish(opts: PublisherOptions): Promise<PublisherResult>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type PublisherBuilder = {
|
||||
register(host: string, publisher: PublisherBase): void;
|
||||
get(storePath: string): PublisherBase;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type PublisherOptions = {
|
||||
values: TemplaterValues;
|
||||
workspacePath: string;
|
||||
logger: Logger;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type PublisherResult = {
|
||||
remoteUrl: string;
|
||||
catalogInfoUrl?: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export class Publishers implements PublisherBuilder {
|
||||
// (undocumented)
|
||||
static fromConfig(config: Config, _options: {
|
||||
logger: Logger;
|
||||
}): Promise<PublisherBuilder>;
|
||||
// (undocumented)
|
||||
get(url: string): PublisherBase;
|
||||
// (undocumented)
|
||||
register(host: string, preparer: PublisherBase | undefined): void;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type RepoVisibilityOptions = 'private' | 'internal' | 'public';
|
||||
|
||||
// @public
|
||||
export type RequiredTemplateValues = {
|
||||
owner: string;
|
||||
storePath: string;
|
||||
destination?: {
|
||||
git?: gitUrlParse.GitUrl;
|
||||
};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RouterOptions {
|
||||
// (undocumented)
|
||||
actions?: TemplateAction<any>[];
|
||||
// (undocumented)
|
||||
catalogClient: CatalogApi;
|
||||
// (undocumented)
|
||||
config: Config;
|
||||
// (undocumented)
|
||||
database: PluginDatabaseManager;
|
||||
// (undocumented)
|
||||
logger: Logger;
|
||||
// (undocumented)
|
||||
preparers: PreparerBuilder;
|
||||
// (undocumented)
|
||||
publishers: PublisherBuilder;
|
||||
// (undocumented)
|
||||
reader: UrlReader;
|
||||
// (undocumented)
|
||||
taskWorkers?: number;
|
||||
// (undocumented)
|
||||
templaters: TemplaterBuilder;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const runCommand: ({ command, args, logStream, }: RunCommandOptions) => Promise<void>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type RunCommandOptions = {
|
||||
command: string;
|
||||
args: string[];
|
||||
logStream?: Writable;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type StageContext<T = {}> = {
|
||||
values: TemplaterValues;
|
||||
entity: TemplateEntityV1alpha1;
|
||||
logger: Logger;
|
||||
logStream: Writable;
|
||||
workspacePath: string;
|
||||
} & T;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface StageInput<T = {}> {
|
||||
// (undocumented)
|
||||
handler(ctx: StageContext<T>): Promise<void | object>;
|
||||
// (undocumented)
|
||||
name: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface StageResult extends StageInput {
|
||||
// (undocumented)
|
||||
endedAt?: number;
|
||||
// (undocumented)
|
||||
log: string[];
|
||||
// (undocumented)
|
||||
startedAt?: number;
|
||||
// (undocumented)
|
||||
status: ProcessorStatus;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type SupportedTemplatingKey = 'cookiecutter' | string;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TemplateAction<Input extends InputBase> = {
|
||||
id: string;
|
||||
description?: string;
|
||||
schema?: {
|
||||
input?: Schema;
|
||||
output?: Schema;
|
||||
};
|
||||
handler: (ctx: ActionContext<Input>) => Promise<void>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export class TemplateActionRegistry {
|
||||
// (undocumented)
|
||||
get(actionId: string): TemplateAction<any>;
|
||||
// (undocumented)
|
||||
list(): TemplateAction<any>[];
|
||||
// (undocumented)
|
||||
register<Parameters extends InputBase>(action: TemplateAction<Parameters>): void;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type TemplaterBase = {
|
||||
run(opts: TemplaterRunOptions): Promise<void>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type TemplaterBuilder = {
|
||||
register(protocol: SupportedTemplatingKey, templater: TemplaterBase): void;
|
||||
get(templater: string): TemplaterBase;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type TemplaterConfig = {
|
||||
templater?: TemplaterBase;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type TemplaterRunOptions = {
|
||||
workspacePath: string;
|
||||
values: TemplaterValues;
|
||||
logStream?: Writable;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type TemplaterRunResult = {
|
||||
resultDir: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export class Templaters implements TemplaterBuilder {
|
||||
// (undocumented)
|
||||
get(templaterId: string): TemplaterBase;
|
||||
// (undocumented)
|
||||
register(templaterKey: SupportedTemplatingKey, templater: TemplaterBase): void;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type TemplaterValues = RequiredTemplateValues & Record<string, any>;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-scaffolder-backend",
|
||||
"version": "0.12.1",
|
||||
"version": "0.12.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -170,11 +170,6 @@ export class TaskWorker {
|
||||
preventIndent: true,
|
||||
})(templateCtx);
|
||||
|
||||
// If it's just an empty string, treat it as undefined
|
||||
if (templated === '') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// If it smells like a JSON object then give it a parse as an object and if it fails return the string
|
||||
if (
|
||||
(templated.startsWith('"') && templated.endsWith('"')) ||
|
||||
@@ -213,6 +208,10 @@ export class TaskWorker {
|
||||
// Keep track of all tmp dirs that are created by the action so we can remove them after
|
||||
const tmpDirs = new Array<string>();
|
||||
|
||||
this.options.logger.debug(`Running ${action.id} with input`, {
|
||||
input: JSON.stringify(input, null, 2),
|
||||
});
|
||||
|
||||
await action.handler({
|
||||
baseUrl: task.spec.baseUrl,
|
||||
logger: taskLogger,
|
||||
|
||||
@@ -1,10 +1,86 @@
|
||||
# Scaffolder Frontend
|
||||
|
||||
WORK IN PROGRESS
|
||||
This is the React frontend for the default Backstage [software
|
||||
templates](https://backstage.io/docs/features/software-templates/software-templates-index).
|
||||
This package supplies interfaces related to showing available templates in the
|
||||
Backstage catalog and the workflow to create software using those templates.
|
||||
|
||||
This is the frontend part of the default scaffolder plugin.
|
||||
## Installation
|
||||
|
||||
This `@backstage/plugin-scaffolder` package comes installed by default in any
|
||||
Backstage application created with `npx @backstage/create-app`, so installation
|
||||
is not usually required.
|
||||
|
||||
To check if you already have the package, look under
|
||||
`packages/app/package.json`, in the `dependencies` block, for
|
||||
`@backstage/plugin-scaffolder`. The instructions below walk through restoring
|
||||
the plugin, if you previously removed it.
|
||||
|
||||
### Install the package
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
cd packages/app
|
||||
yarn add @backstage/plugin-scaffolder
|
||||
```
|
||||
|
||||
### Add the plugin to your `packages/app`
|
||||
|
||||
Add the root page that the scaffolder plugin provides to your app. You can
|
||||
choose any path for the route, but we recommend the following:
|
||||
|
||||
```diff
|
||||
// packages/app/src/App.tsx
|
||||
+import { ScaffolderPage } from '@backstage/plugin-scaffolder';
|
||||
|
||||
|
||||
<FlatRoutes>
|
||||
<Route path="/catalog" element={<CatalogIndexPage />} />
|
||||
<Route path="/catalog/:namespace/:kind/:name" element={<CatalogEntityPage />}>
|
||||
{entityPage}
|
||||
</Route>
|
||||
+ <Route path="/create" element={<ScaffolderPage />} />;
|
||||
...
|
||||
</FlatRoutes>
|
||||
```
|
||||
|
||||
The scaffolder plugin also has one external route that needs to be bound for it
|
||||
to function: the `registerComponent` route which should link to the page where
|
||||
the user can register existing software component. In a typical setup, the
|
||||
register component route will be linked to the `catalog-import` plugin's import
|
||||
page:
|
||||
|
||||
```diff
|
||||
// packages/app/src/App.tsx
|
||||
+import { scaffolderPlugin } from '@backstage/plugin-scaffolder';
|
||||
+import { catalogImportPlugin } from '@backstage/plugin-catalog-import';
|
||||
|
||||
const app = createApp({
|
||||
// ...
|
||||
bindRoutes({ bind }) {
|
||||
+ bind(scaffolderPlugin.externalRoutes, {
|
||||
+ registerComponent: catalogImportPlugin.routes.importPage,
|
||||
+ });
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
You may also want to add a link to the scaffolder page to your application
|
||||
sidebar:
|
||||
|
||||
```diff
|
||||
// packages/app/src/components/Root/Root.tsx
|
||||
+import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
|
||||
|
||||
export const Root = ({ children }: PropsWithChildren<{}>) => (
|
||||
<SidebarPage>
|
||||
<Sidebar>
|
||||
+ <SidebarItem icon={CreateComponentIcon} to="create" text="Create..." />;
|
||||
...
|
||||
</Sidebar>
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
- [Backend part of the plugin](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend)
|
||||
- [The Backstage homepage](https://backstage.io)
|
||||
- [scaffolder-backend](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend)
|
||||
provides the backend API for this frontend.
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
## API Report File for "@backstage/plugin-scaffolder"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { DiscoveryApi } from '@backstage/core';
|
||||
import { EntityName } from '@backstage/catalog-model';
|
||||
import { Extension } from '@backstage/core';
|
||||
import { ExternalRouteRef } from '@backstage/core';
|
||||
import { FieldProps } from '@rjsf/core';
|
||||
import { FieldValidation } from '@rjsf/core';
|
||||
import { IdentityApi } from '@backstage/core';
|
||||
import { JsonObject } from '@backstage/config';
|
||||
import { JSONSchema } from '@backstage/catalog-model';
|
||||
import { JsonValue } from '@backstage/config';
|
||||
import { Observable } from '@backstage/core';
|
||||
import { default as React_2 } from 'react';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
|
||||
// @public (undocumented)
|
||||
export function createScaffolderFieldExtension<T = any>(options: FieldExtensionOptions<T>): Extension<() => null>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityPickerFieldExtension: () => null;
|
||||
|
||||
// @public (undocumented)
|
||||
export const OwnerPickerFieldExtension: () => null;
|
||||
|
||||
// @public (undocumented)
|
||||
export const RepoUrlPickerFieldExtension: () => null;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ScaffolderApi {
|
||||
// (undocumented)
|
||||
getIntegrationsList(options: {
|
||||
allowedHosts: string[];
|
||||
}): Promise<{
|
||||
type: string;
|
||||
title: string;
|
||||
host: string;
|
||||
}[]>;
|
||||
// (undocumented)
|
||||
getTask(taskId: string): Promise<ScaffolderTask>;
|
||||
// (undocumented)
|
||||
getTemplateParameterSchema(templateName: EntityName): Promise<TemplateParameterSchema>;
|
||||
// (undocumented)
|
||||
listActions(): Promise<ListActionsResponse>;
|
||||
scaffold(templateName: string, values: Record<string, any>): Promise<string>;
|
||||
// (undocumented)
|
||||
streamLogs({ taskId, after, }: {
|
||||
taskId: string;
|
||||
after?: number;
|
||||
}): Observable<LogEvent>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const scaffolderApiRef: ApiRef<ScaffolderApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class ScaffolderClient implements ScaffolderApi {
|
||||
constructor(options: {
|
||||
discoveryApi: DiscoveryApi;
|
||||
identityApi: IdentityApi;
|
||||
scmIntegrationsApi: ScmIntegrationRegistry;
|
||||
});
|
||||
// (undocumented)
|
||||
getIntegrationsList(options: {
|
||||
allowedHosts: string[];
|
||||
}): Promise<{
|
||||
type: string;
|
||||
title: string;
|
||||
host: string;
|
||||
}[]>;
|
||||
// (undocumented)
|
||||
getTask(taskId: string): Promise<any>;
|
||||
// (undocumented)
|
||||
getTemplateParameterSchema(templateName: EntityName): Promise<TemplateParameterSchema>;
|
||||
// (undocumented)
|
||||
listActions(): Promise<ListActionsResponse>;
|
||||
scaffold(templateName: string, values: Record<string, any>): Promise<string>;
|
||||
// (undocumented)
|
||||
streamLogs({ taskId, after, }: {
|
||||
taskId: string;
|
||||
after?: number;
|
||||
}): Observable<LogEvent>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const ScaffolderFieldExtensions: React_2.ComponentType;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ScaffolderPage: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
const scaffolderPlugin: BackstagePlugin<{
|
||||
root: RouteRef<undefined>;
|
||||
}, {
|
||||
registerComponent: ExternalRouteRef<undefined, true>;
|
||||
}>;
|
||||
|
||||
export { scaffolderPlugin as plugin }
|
||||
|
||||
export { scaffolderPlugin }
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -33,11 +33,12 @@
|
||||
"@backstage/catalog-client": "^0.3.13",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/core": "^0.7.12",
|
||||
"@backstage/integration": "^0.5.6",
|
||||
"@backstage/integration-react": "^0.1.3",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/core-plugin-api": "^0.1.2",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -46,10 +47,10 @@
|
||||
"@rjsf/material-ui": "^2.4.0",
|
||||
"@types/react": "^16.9",
|
||||
"classnames": "^2.2.6",
|
||||
"json-schema": "^0.3.0",
|
||||
"git-url-parse": "^11.4.4",
|
||||
"humanize-duration": "^3.25.1",
|
||||
"immer": "^9.0.1",
|
||||
"json-schema": "^0.3.0",
|
||||
"luxon": "^1.25.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
@@ -66,9 +67,9 @@
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@testing-library/react-hooks": "^3.3.0",
|
||||
"@testing-library/user-event": "^13.1.8",
|
||||
"@types/humanize-duration": "^3.18.1",
|
||||
"@testing-library/react-hooks": "^3.3.0",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/node": "^14.14.32",
|
||||
"cross-fetch": "^3.0.6",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import React from 'react';
|
||||
import { Routes, Route, useOutlet } from 'react-router';
|
||||
import { ScaffolderPage } from './ScaffolderPage';
|
||||
import { TemplatePage } from './TemplatePage';
|
||||
@@ -27,21 +27,24 @@ import {
|
||||
FIELD_EXTENSION_KEY,
|
||||
DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS,
|
||||
} from '../extensions';
|
||||
import { collectComponentData, collectChildren } from '../extensions/helpers';
|
||||
import { useElementFilter } from '@backstage/core-plugin-api';
|
||||
|
||||
export const Router = () => {
|
||||
const outlet = useOutlet();
|
||||
|
||||
const fieldExtensions = useMemo(() => {
|
||||
const registeredExtensions = collectComponentData<FieldExtensionOptions>(
|
||||
collectChildren(outlet, FIELD_EXTENSION_WRAPPER_KEY).flat(),
|
||||
FIELD_EXTENSION_KEY,
|
||||
);
|
||||
const foundExtensions = useElementFilter(outlet, elements =>
|
||||
elements
|
||||
.selectByComponentData({
|
||||
key: FIELD_EXTENSION_WRAPPER_KEY,
|
||||
})
|
||||
.findComponentData<FieldExtensionOptions>({
|
||||
key: FIELD_EXTENSION_KEY,
|
||||
}),
|
||||
);
|
||||
|
||||
return registeredExtensions.length
|
||||
? registeredExtensions
|
||||
: DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS;
|
||||
}, [outlet]);
|
||||
const fieldExtensions = foundExtensions.length
|
||||
? foundExtensions
|
||||
: DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS;
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* 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 { collectComponentData, collectChildren } from './helpers';
|
||||
import { attachComponentData } from '@backstage/core';
|
||||
|
||||
describe('Extension Helpers', () => {
|
||||
const createElementWithComponentData = ({
|
||||
type,
|
||||
data,
|
||||
}: {
|
||||
type: string;
|
||||
data: any;
|
||||
}) => {
|
||||
const element: React.ComponentType = () => null;
|
||||
attachComponentData(element, type, data);
|
||||
return element;
|
||||
};
|
||||
|
||||
describe('collectChildren', () => {
|
||||
it('should return the children of the component which has the correct componentData flag', () => {
|
||||
const SearchElement = createElementWithComponentData({
|
||||
type: 'find.me',
|
||||
data: {},
|
||||
});
|
||||
|
||||
const DontCareAboutme = createElementWithComponentData({
|
||||
type: 'dont.find.me',
|
||||
data: {},
|
||||
});
|
||||
|
||||
const child1 = (
|
||||
<div>
|
||||
<b>hello</b>
|
||||
</div>
|
||||
);
|
||||
|
||||
const child2 = (
|
||||
<div>
|
||||
<p>Hello2</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
const testCase = (
|
||||
<div>
|
||||
<SearchElement>
|
||||
{child1}
|
||||
{child1}
|
||||
</SearchElement>
|
||||
<SearchElement>{child2}</SearchElement>
|
||||
<DontCareAboutme>
|
||||
<p>Hello!</p>
|
||||
<SearchElement>{child1}</SearchElement>
|
||||
</DontCareAboutme>
|
||||
</div>
|
||||
);
|
||||
|
||||
const children = collectChildren(testCase, 'find.me');
|
||||
|
||||
expect(children).toEqual([[child1, child1], child2, child1]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('collectComponentData', () => {
|
||||
it('should return the componentData for particular nodes', () => {
|
||||
const componentData1 = { help: 'im something' };
|
||||
const componentData2 = { help: 'im something else' };
|
||||
|
||||
const FirstElement = createElementWithComponentData({
|
||||
type: 'find.me',
|
||||
data: componentData1,
|
||||
});
|
||||
|
||||
const SecondElement = createElementWithComponentData({
|
||||
type: 'dont.find.me',
|
||||
data: componentData2,
|
||||
});
|
||||
|
||||
const testCase = [
|
||||
<FirstElement />,
|
||||
<FirstElement />,
|
||||
<SecondElement />,
|
||||
<FirstElement />,
|
||||
];
|
||||
const returnedData = collectComponentData(testCase, 'find.me');
|
||||
|
||||
expect(returnedData).toEqual([
|
||||
componentData1,
|
||||
componentData1,
|
||||
componentData1,
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* 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 { getComponentData } from '@backstage/core';
|
||||
|
||||
export const collectComponentData = <T>(
|
||||
children: React.ReactNode,
|
||||
componentDataKey: string,
|
||||
) => {
|
||||
const stack = [children];
|
||||
const found: T[] = [];
|
||||
|
||||
while (stack.length) {
|
||||
const current: React.ReactNode = stack.pop()!;
|
||||
|
||||
React.Children.forEach(current, child => {
|
||||
if (!React.isValidElement(child)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = getComponentData<T>(child, componentDataKey);
|
||||
if (data) {
|
||||
found.push(data);
|
||||
}
|
||||
|
||||
if (child.props.children) {
|
||||
stack.push(child.props.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return found;
|
||||
};
|
||||
|
||||
export const collectChildren = (
|
||||
component: React.ReactNode,
|
||||
componentDataKey: string,
|
||||
) => {
|
||||
const stack = [component];
|
||||
const found: React.ReactNode[] = [];
|
||||
|
||||
while (stack.length) {
|
||||
const current: React.ReactNode = stack.pop()!;
|
||||
|
||||
React.Children.forEach(current, child => {
|
||||
if (!React.isValidElement(child)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (child.props.children) {
|
||||
if (getComponentData(child, componentDataKey)) {
|
||||
found.push(child.props.children);
|
||||
}
|
||||
stack.push(child.props.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return found;
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
## API Report File for "@backstage/plugin-search-backend-node"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { DocumentCollator } from '@backstage/search-common';
|
||||
import { DocumentDecorator } from '@backstage/search-common';
|
||||
import { IndexableDocument } from '@backstage/search-common';
|
||||
import { Logger } from 'winston';
|
||||
import { default as lunr_2 } from 'lunr';
|
||||
import { SearchQuery } from '@backstage/search-common';
|
||||
import { SearchResultSet } from '@backstage/search-common';
|
||||
|
||||
// @public (undocumented)
|
||||
export class IndexBuilder {
|
||||
constructor({ logger, searchEngine }: IndexBuilderOptions);
|
||||
addCollator({ collator, defaultRefreshIntervalSeconds, }: RegisterCollatorParameters): void;
|
||||
addDecorator({ decorator }: RegisterDecoratorParameters): void;
|
||||
build(): Promise<{
|
||||
scheduler: Scheduler;
|
||||
}>;
|
||||
// (undocumented)
|
||||
getSearchEngine(): SearchEngine;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class LunrSearchEngine implements SearchEngine {
|
||||
constructor({ logger }: {
|
||||
logger: Logger;
|
||||
});
|
||||
// (undocumented)
|
||||
protected docStore: Record<string, IndexableDocument>;
|
||||
// (undocumented)
|
||||
index(type: string, documents: IndexableDocument[]): void;
|
||||
// (undocumented)
|
||||
protected logger: Logger;
|
||||
// (undocumented)
|
||||
protected lunrIndices: Record<string, lunr_2.Index>;
|
||||
// (undocumented)
|
||||
query(query: SearchQuery): Promise<SearchResultSet>;
|
||||
// (undocumented)
|
||||
setTranslator(translator: LunrQueryTranslator): void;
|
||||
// (undocumented)
|
||||
protected translator: QueryTranslator;
|
||||
}
|
||||
|
||||
// @public
|
||||
export class Scheduler {
|
||||
constructor({ logger }: {
|
||||
logger: Logger;
|
||||
});
|
||||
addToSchedule(task: Function, interval: number): void;
|
||||
start(): void;
|
||||
stop(): void;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface SearchEngine {
|
||||
index(type: string, documents: IndexableDocument[]): void;
|
||||
query(query: SearchQuery): Promise<SearchResultSet>;
|
||||
setTranslator(translator: QueryTranslator): void;
|
||||
}
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,17 @@
|
||||
## API Report File for "@backstage/plugin-search-backend"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import express from 'express';
|
||||
import { Logger } from 'winston';
|
||||
import { SearchEngine } from '@backstage/plugin-search-backend-node';
|
||||
|
||||
// @public (undocumented)
|
||||
export function createRouter({ engine, logger, }: RouterOptions): Promise<express.Router>;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,102 @@
|
||||
## API Report File for "@backstage/plugin-search"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { AsyncState } from 'react-use/lib/useAsync';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { IndexableDocument } from '@backstage/search-common';
|
||||
import { JsonObject } from '@backstage/config';
|
||||
import { default as React_2 } from 'react';
|
||||
import { ReactElement } from 'react';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
import { SearchQuery } from '@backstage/search-common';
|
||||
import { SearchResult as SearchResult_2 } from '@backstage/search-common';
|
||||
import { SearchResultSet } from '@backstage/search-common';
|
||||
|
||||
// @public (undocumented)
|
||||
export const DefaultResultListItem: ({ result }: {
|
||||
result: IndexableDocument;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const Filters: ({ filters, filterOptions, resetFilters, updateSelected, updateChecked, }: FiltersProps) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const FiltersButton: ({ numberOfSelectedFilters, handleToggleFilters, }: FiltersButtonProps) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type FiltersState = {
|
||||
selected: string;
|
||||
checked: Array<string>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const Router: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const searchApiRef: ApiRef<SearchApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const SearchBar: ({ className, debounceTime }: Props) => JSX.Element;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export const SearchBarNext: ({ className, debounceTime }: {
|
||||
className?: string | undefined;
|
||||
debounceTime?: number | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const SearchContextProvider: ({ initialState, children, }: React_2.PropsWithChildren<{
|
||||
initialState?: SettableSearchContext | undefined;
|
||||
}>) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const SearchFilter: {
|
||||
({ component: Element, ...props }: Props_2): JSX.Element;
|
||||
Checkbox(props: Omit<Props_2, 'component'> & Component): JSX.Element;
|
||||
Select(props: Omit<Props_2, 'component'> & Component): JSX.Element;
|
||||
};
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export const SearchFilterNext: {
|
||||
({ component: Element, ...props }: Props_2): JSX.Element;
|
||||
Checkbox(props: Omit<Props_2, 'component'> & Component): JSX.Element;
|
||||
Select(props: Omit<Props_2, 'component'> & Component): JSX.Element;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const SearchPage: () => JSX.Element;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export const SearchPageNext: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
const searchPlugin: BackstagePlugin<{
|
||||
root: RouteRef<undefined>;
|
||||
nextRoot: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
export { searchPlugin as plugin }
|
||||
|
||||
export { searchPlugin }
|
||||
|
||||
// @public (undocumented)
|
||||
export const SearchResult: ({ children }: {
|
||||
children: (results: {
|
||||
results: SearchResult_2[];
|
||||
}) => JSX.Element;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const SidebarSearch: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const useSearch: () => SearchContextValue;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,100 @@
|
||||
## API Report File for "@backstage/plugin-sentry"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { DiscoveryApi } from '@backstage/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { InfoCardVariants } from '@backstage/core';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntitySentryCard: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntitySentryContent: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export class MockSentryApi implements SentryApi {
|
||||
// (undocumented)
|
||||
fetchIssues(): Promise<SentryIssue[]>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class ProductionSentryApi implements SentryApi {
|
||||
constructor(discoveryApi: DiscoveryApi, organization: string);
|
||||
// (undocumented)
|
||||
fetchIssues(project: string, statsFor: string): Promise<SentryIssue[]>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const Router: ({ entity }: {
|
||||
entity: Entity;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface SentryApi {
|
||||
// (undocumented)
|
||||
fetchIssues(project: string, statsFor: string): Promise<SentryIssue[]>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const sentryApiRef: ApiRef<SentryApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type SentryIssue = {
|
||||
platform: SentryPlatform;
|
||||
lastSeen: string;
|
||||
numComments: number;
|
||||
userCount: number;
|
||||
stats: {
|
||||
'24h'?: EventPoint[];
|
||||
'12h'?: EventPoint[];
|
||||
};
|
||||
culprit: string;
|
||||
title: string;
|
||||
id: string;
|
||||
assignedTo: any;
|
||||
logger: any;
|
||||
type: string;
|
||||
annotations: any[];
|
||||
metadata: SentryIssueMetadata;
|
||||
status: string;
|
||||
subscriptionDetails: any;
|
||||
isPublic: boolean;
|
||||
hasSeen: boolean;
|
||||
shortId: string;
|
||||
shareId: string | null;
|
||||
firstSeen: string;
|
||||
count: string;
|
||||
permalink: string;
|
||||
level: string;
|
||||
isSubscribed: boolean;
|
||||
isBookmarked: boolean;
|
||||
project: SentryProject;
|
||||
statusDetails: any;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const SentryIssuesWidget: ({ entity, statsFor, variant, }: {
|
||||
entity: Entity;
|
||||
statsFor?: "12h" | "24h" | undefined;
|
||||
variant?: InfoCardVariants | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
const sentryPlugin: BackstagePlugin<{
|
||||
root: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
export { sentryPlugin as plugin }
|
||||
|
||||
export { sentryPlugin }
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,56 @@
|
||||
## API Report File for "@backstage/plugin-shortcuts"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { Observable } from '@backstage/core';
|
||||
import ObservableImpl from 'zen-observable';
|
||||
import { StorageApi } from '@backstage/core';
|
||||
|
||||
// @public
|
||||
export class LocalStoredShortcuts implements ShortcutApi {
|
||||
constructor(storageApi: StorageApi);
|
||||
// (undocumented)
|
||||
add(shortcut: Omit<Shortcut, 'id'>): Promise<void>;
|
||||
// (undocumented)
|
||||
getColor(url: string): string;
|
||||
// (undocumented)
|
||||
remove(id: string): Promise<void>;
|
||||
// (undocumented)
|
||||
shortcut$(): ObservableImpl<Shortcut[]>;
|
||||
// (undocumented)
|
||||
update(shortcut: Shortcut): Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type Shortcut = {
|
||||
id: string;
|
||||
url: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ShortcutApi {
|
||||
add(shortcut: Omit<Shortcut, 'id'>): Promise<void>;
|
||||
getColor(url: string): string;
|
||||
remove(id: string): Promise<void>;
|
||||
shortcut$(): Observable<Shortcut[]>;
|
||||
update(shortcut: Shortcut): Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const Shortcuts: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const shortcutsApiRef: ApiRef<ShortcutApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const shortcutsPlugin: BackstagePlugin<{}, {}>;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,41 @@
|
||||
## API Report File for "@backstage/plugin-sonarqube"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { InfoCardVariants } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntitySonarQubeCard: ({ variant, duplicationRatings, }: {
|
||||
entity?: Entity| undefined;
|
||||
variant?: InfoCardVariants| undefined;
|
||||
duplicationRatings?: {
|
||||
greaterThan: number;
|
||||
rating: "1.0" | "2.0" | "3.0" | "4.0" | "5.0";
|
||||
}[] | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const isSonarQubeAvailable: (entity: Entity) => boolean;
|
||||
|
||||
// @public (undocumented)
|
||||
export const SonarQubeCard: ({ variant, duplicationRatings, }: {
|
||||
entity?: Entity | undefined;
|
||||
variant?: InfoCardVariants | undefined;
|
||||
duplicationRatings?: DuplicationRating[] | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
const sonarQubePlugin: BackstagePlugin<{}, {}>;
|
||||
|
||||
export { sonarQubePlugin as plugin }
|
||||
|
||||
export { sonarQubePlugin }
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,68 @@
|
||||
## API Report File for "@backstage/plugin-splunk-on-call"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { ConfigApi } from '@backstage/core';
|
||||
import { DiscoveryApi } from '@backstage/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntitySplunkOnCallCard: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const isSplunkOnCallAvailable: (entity: Entity) => boolean;
|
||||
|
||||
// @public (undocumented)
|
||||
export const splunkOnCallApiRef: ApiRef<SplunkOnCallApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class SplunkOnCallClient implements SplunkOnCallApi {
|
||||
constructor(config: ClientApiConfig);
|
||||
// (undocumented)
|
||||
static fromConfig(configApi: ConfigApi, discoveryApi: DiscoveryApi): SplunkOnCallClient;
|
||||
// (undocumented)
|
||||
getEscalationPolicies(): Promise<EscalationPolicyInfo[]>;
|
||||
// (undocumented)
|
||||
getIncidents(): Promise<Incident[]>;
|
||||
// (undocumented)
|
||||
getOnCallUsers(): Promise<OnCall[]>;
|
||||
// (undocumented)
|
||||
getTeams(): Promise<Team[]>;
|
||||
// (undocumented)
|
||||
getUsers(): Promise<User[]>;
|
||||
// (undocumented)
|
||||
incidentAction({ routingKey, incidentType, incidentId, incidentDisplayName, incidentMessage, incidentStartTime, }: TriggerAlarmRequest): Promise<Response>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const SplunkOnCallPage: {
|
||||
({ title, subtitle, pageTitle, }: SplunkOnCallPageProps): JSX.Element;
|
||||
defaultProps: {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
pageTitle: string;
|
||||
};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
const splunkOnCallPlugin: BackstagePlugin<{
|
||||
root: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
export { splunkOnCallPlugin as plugin }
|
||||
|
||||
export { splunkOnCallPlugin }
|
||||
|
||||
// @public (undocumented)
|
||||
export class UnauthorizedError extends Error {
|
||||
}
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,123 @@
|
||||
## API Report File for "@backstage/plugin-tech-radar"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RadarEntry {
|
||||
// (undocumented)
|
||||
description?: string;
|
||||
// (undocumented)
|
||||
id: string;
|
||||
// (undocumented)
|
||||
key: string;
|
||||
// (undocumented)
|
||||
quadrant: string;
|
||||
// (undocumented)
|
||||
timeline: Array<RadarEntrySnapshot>;
|
||||
// (undocumented)
|
||||
title: string;
|
||||
// (undocumented)
|
||||
url: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RadarEntrySnapshot {
|
||||
// (undocumented)
|
||||
date: Date;
|
||||
// (undocumented)
|
||||
description?: string;
|
||||
// (undocumented)
|
||||
moved?: MovedState;
|
||||
// (undocumented)
|
||||
ringId: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RadarQuadrant {
|
||||
// (undocumented)
|
||||
id: string;
|
||||
// (undocumented)
|
||||
name: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface RadarRing {
|
||||
// (undocumented)
|
||||
color: string;
|
||||
// (undocumented)
|
||||
id: string;
|
||||
// (undocumented)
|
||||
name: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const Router: {
|
||||
({ title, subtitle, pageTitle, ...props }: TechRadarPageProps): JSX.Element;
|
||||
defaultProps: {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
pageTitle: string;
|
||||
};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TechRadarApi {
|
||||
// (undocumented)
|
||||
load: () => Promise<TechRadarLoaderResponse>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const techRadarApiRef: ApiRef<TechRadarApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TechRadarComponent: (props: TechRadarComponentProps) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export interface TechRadarComponentProps {
|
||||
// (undocumented)
|
||||
height: number;
|
||||
// (undocumented)
|
||||
svgProps?: object;
|
||||
// (undocumented)
|
||||
width: number;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface TechRadarLoaderResponse {
|
||||
// (undocumented)
|
||||
entries: RadarEntry[];
|
||||
// (undocumented)
|
||||
quadrants: RadarQuadrant[];
|
||||
// (undocumented)
|
||||
rings: RadarRing[];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const TechRadarPage: {
|
||||
({ title, subtitle, pageTitle, ...props }: TechRadarPageProps): JSX.Element;
|
||||
defaultProps: {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
pageTitle: string;
|
||||
};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
const techRadarPlugin: BackstagePlugin<{
|
||||
root: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
export { techRadarPlugin as plugin }
|
||||
|
||||
export { techRadarPlugin }
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
## API Report File for "@backstage/plugin-techdocs-backend"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import { GeneratorBuilder } from '@backstage/techdocs-common';
|
||||
import { Knex } from 'knex';
|
||||
import { Logger } from 'winston';
|
||||
import { PluginEndpointDiscovery } from '@backstage/backend-common';
|
||||
import { PreparerBuilder } from '@backstage/techdocs-common';
|
||||
import { PublisherBase } from '@backstage/techdocs-common';
|
||||
|
||||
// @public (undocumented)
|
||||
export function createRouter({ preparers, generators, publisher, config, logger, discovery, }: RouterOptions): Promise<express.Router>;
|
||||
|
||||
|
||||
export * from "@backstage/techdocs-common";
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -68,7 +68,11 @@ export class DocsBuilder {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public async build(): Promise<void> {
|
||||
/**
|
||||
* Build the docs and return whether they have been newly generated or have been cached
|
||||
* @returns true, if the docs have been built. false, if the cached docs are still up-to-date.
|
||||
*/
|
||||
public async build(): Promise<boolean> {
|
||||
if (!this.entity.metadata.uid) {
|
||||
throw new Error(
|
||||
'Trying to build documentation for entity not in service catalog',
|
||||
@@ -125,7 +129,7 @@ export class DocsBuilder {
|
||||
this.entity,
|
||||
)} are unmodified. Using cache, skipping generate and prepare`,
|
||||
);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
throw new Error(err.message);
|
||||
}
|
||||
@@ -205,5 +209,7 @@ export class DocsBuilder {
|
||||
|
||||
// Update the last check time for the entity
|
||||
new BuildMetadataStorage(this.entity.metadata.uid).setLastUpdated();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import { PluginEndpointDiscovery } from '@backstage/backend-common';
|
||||
import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import { NotFoundError } from '@backstage/errors';
|
||||
import { NotFoundError, NotModifiedError } from '@backstage/errors';
|
||||
import {
|
||||
GeneratorBuilder,
|
||||
getLocationForEntity,
|
||||
@@ -172,10 +172,15 @@ export async function createRouter({
|
||||
case 'awsS3':
|
||||
case 'azureBlobStorage':
|
||||
case 'openStackSwift':
|
||||
case 'googleGcs':
|
||||
case 'googleGcs': {
|
||||
// This block should be valid for all storage implementations. So no need to duplicate in future,
|
||||
// add the publisher type in the list here.
|
||||
await docsBuilder.build();
|
||||
const updated = await docsBuilder.build();
|
||||
|
||||
if (!updated) {
|
||||
throw new NotModifiedError();
|
||||
}
|
||||
|
||||
// With a maximum of ~5 seconds wait, check if the files got published and if docs will be fetched
|
||||
// on the user's page. If not, respond with a message asking them to check back later.
|
||||
// The delay here is to make sure GCS/AWS/etc. registers newly uploaded files which is usually <1 second
|
||||
@@ -194,10 +199,13 @@ export async function createRouter({
|
||||
'Sorry! It took too long for the generated docs to show up in storage. Check back later.',
|
||||
);
|
||||
}
|
||||
|
||||
res
|
||||
.status(201)
|
||||
.json({ message: 'Docs updated or did not need updating' });
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new NotFoundError(
|
||||
`Publisher type ${publisherType} is not supported by techdocs-backend docs builder.`,
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
## API Report File for "@backstage/plugin-techdocs"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
|
||||
import { ApiRef } from '@backstage/core';
|
||||
import { BackstagePlugin } from '@backstage/core';
|
||||
import { Config } from '@backstage/config';
|
||||
import { CSSProperties } from '@material-ui/styles';
|
||||
import { DiscoveryApi } from '@backstage/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityName } from '@backstage/catalog-model';
|
||||
import { IdentityApi } from '@backstage/core';
|
||||
import { Location as Location_2 } from '@backstage/catalog-model';
|
||||
import { RouteRef } from '@backstage/core';
|
||||
|
||||
// @public (undocumented)
|
||||
export const DocsCardGrid: ({ entities, }: {
|
||||
entities: Entity[] | undefined;
|
||||
}) => JSX.Element | null;
|
||||
|
||||
// @public (undocumented)
|
||||
export const DocsTable: ({ entities, title, }: {
|
||||
entities: Entity[] | undefined;
|
||||
title?: string | undefined;
|
||||
}) => JSX.Element | null;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EmbeddedDocsRouter: (_props: Props) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityTechdocsContent: (_props: {
|
||||
entity?: Entity| undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type PanelType = 'DocsCardGrid' | 'DocsTable';
|
||||
|
||||
// @public (undocumented)
|
||||
export const Reader: ({ entityId, onReady }: Props_2) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const Router: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TechDocsApi {
|
||||
// (undocumented)
|
||||
getApiOrigin(): Promise<string>;
|
||||
// (undocumented)
|
||||
getEntityMetadata(entityId: EntityName): Promise<TechDocsEntityMetadata>;
|
||||
// (undocumented)
|
||||
getTechDocsMetadata(entityId: EntityName): Promise<TechDocsMetadata>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const techdocsApiRef: ApiRef<TechDocsApi>;
|
||||
|
||||
// @public
|
||||
export class TechDocsClient implements TechDocsApi {
|
||||
constructor({ configApi, discoveryApi, identityApi, }: {
|
||||
configApi: Config;
|
||||
discoveryApi: DiscoveryApi;
|
||||
identityApi: IdentityApi;
|
||||
});
|
||||
// (undocumented)
|
||||
configApi: Config;
|
||||
// (undocumented)
|
||||
discoveryApi: DiscoveryApi;
|
||||
// (undocumented)
|
||||
getApiOrigin(): Promise<string>;
|
||||
getEntityMetadata(entityId: EntityName): Promise<TechDocsEntityMetadata>;
|
||||
getTechDocsMetadata(entityId: EntityName): Promise<TechDocsMetadata>;
|
||||
// (undocumented)
|
||||
identityApi: IdentityApi;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const TechDocsCustomHome: ({ tabsConfig, }: {
|
||||
tabsConfig: TabsConfig;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TechdocsPage: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
const techdocsPlugin: BackstagePlugin<{
|
||||
root: RouteRef<undefined>;
|
||||
entityContent: RouteRef<undefined>;
|
||||
}, {}>;
|
||||
|
||||
export { techdocsPlugin as plugin }
|
||||
|
||||
export { techdocsPlugin }
|
||||
|
||||
// @public (undocumented)
|
||||
export const TechDocsReaderPage: () => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TechDocsStorageApi {
|
||||
// (undocumented)
|
||||
getApiOrigin(): Promise<string>;
|
||||
// (undocumented)
|
||||
getBaseUrl(oldBaseUrl: string, entityId: EntityName, path: string): Promise<string>;
|
||||
// (undocumented)
|
||||
getBuilder(): Promise<string>;
|
||||
// (undocumented)
|
||||
getEntityDocs(entityId: EntityName, path: string): Promise<string>;
|
||||
// (undocumented)
|
||||
getStorageUrl(): Promise<string>;
|
||||
// (undocumented)
|
||||
syncEntityDocs(entityId: EntityName): Promise<boolean>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const techdocsStorageApiRef: ApiRef<TechDocsStorageApi>;
|
||||
|
||||
// @public
|
||||
export class TechDocsStorageClient implements TechDocsStorageApi {
|
||||
constructor({ configApi, discoveryApi, identityApi, }: {
|
||||
configApi: Config;
|
||||
discoveryApi: DiscoveryApi;
|
||||
identityApi: IdentityApi;
|
||||
});
|
||||
// (undocumented)
|
||||
configApi: Config;
|
||||
// (undocumented)
|
||||
discoveryApi: DiscoveryApi;
|
||||
// (undocumented)
|
||||
getApiOrigin(): Promise<string>;
|
||||
// (undocumented)
|
||||
getBaseUrl(oldBaseUrl: string, entityId: EntityName, path: string): Promise<string>;
|
||||
// (undocumented)
|
||||
getBuilder(): Promise<string>;
|
||||
getEntityDocs(entityId: EntityName, path: string): Promise<string>;
|
||||
// (undocumented)
|
||||
getStorageUrl(): Promise<string>;
|
||||
// (undocumented)
|
||||
identityApi: IdentityApi;
|
||||
syncEntityDocs(entityId: EntityName): Promise<boolean>;
|
||||
}
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
```
|
||||
@@ -1,149 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 { EntityName } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import { DiscoveryApi, IdentityApi } from '@backstage/core';
|
||||
import { NotFoundError } from '@backstage/errors';
|
||||
import { TechDocsStorageApi } from '../src/api';
|
||||
|
||||
export class TechDocsDevStorageApi implements TechDocsStorageApi {
|
||||
public configApi: Config;
|
||||
public discoveryApi: DiscoveryApi;
|
||||
public identityApi: IdentityApi;
|
||||
|
||||
constructor({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
identityApi,
|
||||
}: {
|
||||
configApi: Config;
|
||||
discoveryApi: DiscoveryApi;
|
||||
identityApi: IdentityApi;
|
||||
}) {
|
||||
this.configApi = configApi;
|
||||
this.discoveryApi = discoveryApi;
|
||||
this.identityApi = identityApi;
|
||||
}
|
||||
|
||||
async getApiOrigin() {
|
||||
return (
|
||||
this.configApi.getOptionalString('techdocs.requestUrl') ??
|
||||
(await this.discoveryApi.getBaseUrl('techdocs'))
|
||||
);
|
||||
}
|
||||
|
||||
async getStorageUrl() {
|
||||
return (
|
||||
this.configApi.getOptionalString('techdocs.storageUrl') ??
|
||||
`${await this.discoveryApi.getBaseUrl('techdocs')}/static/docs`
|
||||
);
|
||||
}
|
||||
|
||||
async getBuilder() {
|
||||
return this.configApi.getString('techdocs.builder');
|
||||
}
|
||||
|
||||
async fetchUrl(url: string) {
|
||||
const token = await this.identityApi.getIdToken();
|
||||
return fetch(url, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
}
|
||||
|
||||
async getEntityDocs(entityId: EntityName, path: string) {
|
||||
const { kind, namespace, name } = entityId;
|
||||
|
||||
const storageUrl = await this.getStorageUrl();
|
||||
const url = `${storageUrl}/${namespace}/${kind}/${name}/${path}`;
|
||||
const token = await this.identityApi.getIdToken();
|
||||
|
||||
const request = await fetch(
|
||||
`${url.endsWith('/') ? url : `${url}/`}index.html`,
|
||||
{
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
},
|
||||
);
|
||||
|
||||
let errorMessage = '';
|
||||
switch (request.status) {
|
||||
case 404:
|
||||
errorMessage = 'Page not found. ';
|
||||
// path is empty for the home page of an entity's docs site
|
||||
if (!path) {
|
||||
errorMessage +=
|
||||
'This could be because there is no index.md file in the root of the docs directory of this repository.';
|
||||
}
|
||||
throw new NotFoundError(errorMessage);
|
||||
case 500:
|
||||
errorMessage =
|
||||
'Could not generate documentation or an error in the TechDocs backend. ';
|
||||
throw new Error(errorMessage);
|
||||
default:
|
||||
// Do nothing
|
||||
break;
|
||||
}
|
||||
|
||||
return request.text();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if docs are the latest version and trigger rebuilds if not
|
||||
*
|
||||
* @param {EntityName} entityId Object containing entity data like name, namespace, etc.
|
||||
* @returns {boolean} Whether documents are currently synchronized to newest version
|
||||
* @throws {Error} Throws error on error from sync endpoint
|
||||
*/
|
||||
async syncEntityDocs(entityId: EntityName) {
|
||||
const { kind, namespace, name } = entityId;
|
||||
|
||||
const apiOrigin = await this.getApiOrigin();
|
||||
const url = `${apiOrigin}/sync/${namespace}/${kind}/${name}`;
|
||||
let request;
|
||||
let attempts: number = 0;
|
||||
// retry if request times out, up to 5 times
|
||||
// can happen due to docs taking too long to generate
|
||||
while (!request || (request.status === 408 && attempts < 5)) {
|
||||
attempts++;
|
||||
request = await this.fetchUrl(
|
||||
`${url.endsWith('/') ? url : `${url}/`}index.html`,
|
||||
);
|
||||
}
|
||||
|
||||
switch (request.status) {
|
||||
case 404:
|
||||
throw (await request.json()).error;
|
||||
case 200:
|
||||
case 201:
|
||||
return true;
|
||||
// for timeout and misc errors, handle without error to allow viewing older docs
|
||||
// if older docs not available,
|
||||
// Reader will show 404 error coming from getEntityDocs
|
||||
case 408:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async getBaseUrl(
|
||||
oldBaseUrl: string,
|
||||
entityId: EntityName,
|
||||
path: string,
|
||||
): Promise<string> {
|
||||
const { name } = entityId;
|
||||
const apiOrigin = await this.getApiOrigin();
|
||||
return new URL(oldBaseUrl, `${apiOrigin}/${name}/${path}`).toString();
|
||||
}
|
||||
}
|
||||
+182
-11
@@ -14,11 +14,105 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { configApiRef, discoveryApiRef, identityApiRef } from '@backstage/core';
|
||||
import {
|
||||
configApiRef,
|
||||
discoveryApiRef,
|
||||
Header,
|
||||
identityApiRef,
|
||||
Page,
|
||||
TabbedLayout,
|
||||
} from '@backstage/core';
|
||||
import { createDevApp } from '@backstage/dev-utils';
|
||||
import { techdocsPlugin } from '../src/plugin';
|
||||
import { TechDocsDevStorageApi } from './api';
|
||||
import { techdocsStorageApiRef } from '../src';
|
||||
import { NotFoundError } from '@backstage/errors';
|
||||
import React from 'react';
|
||||
import {
|
||||
Reader,
|
||||
SyncResult,
|
||||
TechDocsStorageApi,
|
||||
techdocsStorageApiRef,
|
||||
} from '../src';
|
||||
|
||||
// used so each route can provide it's own implementation in the constructor of the react component
|
||||
let apiHolder: TechDocsStorageApi | undefined = undefined;
|
||||
|
||||
const apiBridge: TechDocsStorageApi = {
|
||||
getApiOrigin: async () => '',
|
||||
getBaseUrl: (...args) => apiHolder!.getBaseUrl(...args),
|
||||
getBuilder: () => apiHolder!.getBuilder(),
|
||||
getStorageUrl: () => apiHolder!.getStorageUrl(),
|
||||
getEntityDocs: (...args) => apiHolder!.getEntityDocs(...args),
|
||||
syncEntityDocs: (...args) => apiHolder!.syncEntityDocs(...args),
|
||||
};
|
||||
|
||||
const mockContent = `
|
||||
<h1>Hello World!</h1>
|
||||
<p>This is an example content that will actually be provided by a MkDocs powered site</p>
|
||||
`;
|
||||
|
||||
function createPage({
|
||||
entityDocs,
|
||||
syncDocs,
|
||||
syncDocsDelay,
|
||||
}: {
|
||||
entityDocs?: (props: {
|
||||
called: number;
|
||||
content: string;
|
||||
}) => string | Promise<string>;
|
||||
syncDocs: () => SyncResult;
|
||||
syncDocsDelay?: number;
|
||||
}) {
|
||||
class Api implements TechDocsStorageApi {
|
||||
private entityDocsCallCount: number = 0;
|
||||
|
||||
getApiOrigin = async () => '';
|
||||
getBaseUrl = async () => '';
|
||||
getBuilder = async () => 'local';
|
||||
getStorageUrl = async () => '';
|
||||
|
||||
async getEntityDocs() {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
if (!entityDocs) {
|
||||
return mockContent;
|
||||
}
|
||||
|
||||
return entityDocs({
|
||||
called: this.entityDocsCallCount++,
|
||||
content: mockContent,
|
||||
});
|
||||
}
|
||||
|
||||
async syncEntityDocs() {
|
||||
if (syncDocsDelay) {
|
||||
await new Promise(resolve => setTimeout(resolve, syncDocsDelay));
|
||||
}
|
||||
|
||||
return syncDocs();
|
||||
}
|
||||
}
|
||||
|
||||
class Component extends React.Component {
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
|
||||
apiHolder = new Api();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Reader
|
||||
entityId={{
|
||||
kind: 'Component',
|
||||
namespace: 'default',
|
||||
name: 'my-docs',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return <Component />;
|
||||
}
|
||||
|
||||
createDevApp()
|
||||
.registerApi({
|
||||
@@ -28,12 +122,89 @@ createDevApp()
|
||||
discoveryApi: discoveryApiRef,
|
||||
identityApi: identityApiRef,
|
||||
},
|
||||
factory: ({ configApi, discoveryApi, identityApi }) =>
|
||||
new TechDocsDevStorageApi({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
identityApi,
|
||||
}),
|
||||
factory: () => apiBridge,
|
||||
})
|
||||
|
||||
.addPage({
|
||||
title: 'TechDocs',
|
||||
element: (
|
||||
<Page themeId="home">
|
||||
<Header title="TechDocs" />
|
||||
<TabbedLayout>
|
||||
<TabbedLayout.Route path="/fresh" title="Fresh">
|
||||
{createPage({
|
||||
syncDocs: () => 'cached',
|
||||
})}
|
||||
</TabbedLayout.Route>
|
||||
|
||||
<TabbedLayout.Route path="/stale" title="Stale">
|
||||
{createPage({
|
||||
syncDocs: () => 'updated',
|
||||
syncDocsDelay: 2000,
|
||||
})}
|
||||
</TabbedLayout.Route>
|
||||
|
||||
<TabbedLayout.Route path="/initial" title="Initial Build">
|
||||
{createPage({
|
||||
entityDocs: ({ called, content }) => {
|
||||
if (called < 1) {
|
||||
throw new NotFoundError();
|
||||
}
|
||||
|
||||
return content;
|
||||
},
|
||||
syncDocs: () => 'updated',
|
||||
syncDocsDelay: 10000,
|
||||
})}
|
||||
</TabbedLayout.Route>
|
||||
|
||||
<TabbedLayout.Route path="/not-found" title="Not Found">
|
||||
{createPage({
|
||||
entityDocs: () => {
|
||||
throw new NotFoundError('Not found, some error message...');
|
||||
},
|
||||
syncDocs: () => 'cached',
|
||||
})}
|
||||
</TabbedLayout.Route>
|
||||
|
||||
<TabbedLayout.Route path="/error" title="Error">
|
||||
{createPage({
|
||||
entityDocs: () => {
|
||||
throw new Error('Another more critical error');
|
||||
},
|
||||
syncDocs: () => 'cached',
|
||||
})}
|
||||
</TabbedLayout.Route>
|
||||
|
||||
<TabbedLayout.Route path="/serror" title="Sync Error">
|
||||
{createPage({
|
||||
syncDocs: () => {
|
||||
throw new Error('Some random error');
|
||||
},
|
||||
syncDocsDelay: 2000,
|
||||
})}
|
||||
</TabbedLayout.Route>
|
||||
|
||||
<TabbedLayout.Route path="/berror" title="Both Error">
|
||||
{createPage({
|
||||
entityDocs: () => {
|
||||
throw new Error('Some random error');
|
||||
},
|
||||
syncDocs: () => {
|
||||
throw new Error('Some random error');
|
||||
},
|
||||
syncDocsDelay: 2000,
|
||||
})}
|
||||
</TabbedLayout.Route>
|
||||
|
||||
<TabbedLayout.Route path="/timeout" title="Sync Timeout">
|
||||
{createPage({
|
||||
syncDocs: () => 'timeout',
|
||||
syncDocsDelay: 2000,
|
||||
})}
|
||||
</TabbedLayout.Route>
|
||||
</TabbedLayout>
|
||||
</Page>
|
||||
),
|
||||
})
|
||||
.registerPlugin(techdocsPlugin)
|
||||
.render();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user