Merge branch 'master' into techdocs-common/case-sensitive-migration

Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
Eric Peterson
2021-08-12 16:28:40 +02:00
235 changed files with 5195 additions and 1068 deletions
+12
View File
@@ -1,5 +1,17 @@
# @backstage/plugin-api-docs
## 0.6.6
### Patch Changes
- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version.
- 0383314c9: Support deep linking in OpenAPI definitions.
- Updated dependencies
- @backstage/core-components@0.3.1
- @backstage/core-plugin-api@0.1.6
- @backstage/plugin-catalog@0.6.11
- @backstage/plugin-catalog-react@0.4.2
## 0.6.5
### Patch Changes
+9 -9
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-api-docs",
"version": "0.6.5",
"version": "0.6.6",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,10 +31,10 @@
"dependencies": {
"@asyncapi/react-component": "^0.23.0",
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/plugin-catalog": "^0.6.10",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/plugin-catalog": "^0.6.11",
"@backstage/plugin-catalog-react": "^0.4.2",
"@backstage/theme": "^0.2.9",
"@material-icons/font": "^1.0.2",
"@material-ui/core": "^4.12.2",
@@ -52,10 +52,10 @@
"swagger-ui-react": "^3.37.2"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
+11
View File
@@ -1,5 +1,16 @@
# @backstage/plugin-auth-backend
## 0.3.20
### Patch Changes
- 29f7cfffb: Added `resolveCatalogMembership` utility to query the catalog for additional authentication claims within sign-in resolvers.
- 8bedb75ae: Update Luxon dependency to 2.x
- bfe0ff93f: Add Sign In and Handler resolver for Okta provider
- Updated dependencies
- @backstage/backend-common@0.8.9
- @backstage/test-utils@0.1.17
## 0.3.19
### Patch Changes
+22
View File
@@ -98,6 +98,13 @@ export const createMicrosoftProvider: (
options?: MicrosoftProviderOptions | undefined,
) => AuthProviderFactory;
// Warning: (ae-missing-release-tag) "createOktaProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const createOktaProvider: (
_options?: OktaProviderOptions | undefined,
) => AuthProviderFactory;
// Warning: (ae-missing-release-tag) "createRouter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -303,6 +310,21 @@ export type OAuthState = {
env: string;
};
// Warning: (ae-missing-release-tag) "oktaEmailSignInResolver" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const oktaEmailSignInResolver: SignInResolver<OAuthResult>;
// Warning: (ae-missing-release-tag) "OktaProviderOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export type OktaProviderOptions = {
authHandler?: AuthHandler<OAuthResult>;
signIn?: {
resolver?: SignInResolver<OAuthResult>;
};
};
// Warning: (ae-missing-release-tag) "postMessageResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend",
"version": "0.3.19",
"version": "0.3.20",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -29,12 +29,12 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.8.7",
"@backstage/backend-common": "^0.8.9",
"@backstage/catalog-client": "^0.3.18",
"@backstage/catalog-model": "^0.9.0",
"@backstage/config": "^0.1.5",
"@backstage/errors": "^0.1.1",
"@backstage/test-utils": "^0.1.16",
"@backstage/test-utils": "^0.1.17",
"@types/express": "^4.17.6",
"@types/passport": "^1.0.3",
"compression": "^1.7.4",
@@ -50,7 +50,7 @@
"jose": "^1.27.1",
"jwt-decode": "^3.1.0",
"knex": "^0.95.1",
"luxon": "^1.25.0",
"luxon": "^2.0.2",
"morgan": "^1.10.0",
"node-cache": "^5.1.2",
"openid-client": "^4.2.1",
@@ -68,7 +68,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.7.6",
"@backstage/cli": "^0.7.8",
"@types/body-parser": "^1.19.0",
"@types/cookie-parser": "^1.4.2",
"@types/express-session": "^1.17.2",
@@ -15,7 +15,11 @@
*/
import { CatalogApi } from '@backstage/catalog-client';
import { UserEntity } from '@backstage/catalog-model';
import {
RELATION_MEMBER_OF,
UserEntity,
UserEntityV1alpha1,
} from '@backstage/catalog-model';
import { TokenIssuer } from '../../identity';
import { CatalogIdentityClient } from './CatalogIdentityClient';
@@ -37,12 +41,12 @@ describe('CatalogIdentityClient', () => {
afterEach(() => jest.resetAllMocks());
it('passes through the correct search params', async () => {
it('findUser passes through the correct search params', async () => {
catalogApi.getEntities.mockResolvedValueOnce({ items: [{} as UserEntity] });
tokenIssuer.issueToken.mockResolvedValue('my-token');
const client = new CatalogIdentityClient({
catalogApi: catalogApi,
tokenIssuer: tokenIssuer,
catalogApi,
tokenIssuer,
});
await client.findUser({ annotations: { key: 'value' } });
@@ -62,4 +66,88 @@ describe('CatalogIdentityClient', () => {
},
});
});
it('resolveCatalogMembership resolves membership', async () => {
const mockUsers: Array<UserEntityV1alpha1> = [
{
apiVersion: 'backstage.io/v1beta1',
kind: 'User',
metadata: {
name: 'inigom',
},
spec: {
memberOf: ['team-a'],
},
relations: [
{
type: RELATION_MEMBER_OF,
target: {
kind: 'Group',
namespace: 'default',
name: 'team-a',
},
},
],
},
{
apiVersion: 'backstage.io/v1beta1',
kind: 'User',
metadata: {
name: 'mpatinkin',
namespace: 'reality',
},
spec: {
memberOf: ['screen-actors-guild'],
},
relations: [
{
type: RELATION_MEMBER_OF,
target: {
kind: 'Group',
namespace: 'reality',
name: 'screen-actors-guild',
},
},
],
},
];
catalogApi.getEntities.mockResolvedValueOnce({ items: mockUsers });
const client = new CatalogIdentityClient({
catalogApi,
tokenIssuer,
});
const claims = await client.resolveCatalogMembership({
entityRefs: ['inigom', 'User:default/imontoya', 'User:reality/mpatinkin'],
});
expect(catalogApi.getEntities).toHaveBeenCalledWith({
filter: [
{
kind: 'user',
'metadata.namespace': 'default',
'metadata.name': 'inigom',
},
{
kind: 'user',
'metadata.namespace': 'default',
'metadata.name': 'imontoya',
},
{
kind: 'user',
'metadata.namespace': 'reality',
'metadata.name': 'mpatinkin',
},
],
});
expect(claims).toMatchObject([
'user:default/inigom',
'user:default/imontoya',
'user:reality/mpatinkin',
'group:default/team-a',
'group:reality/screen-actors-guild',
]);
});
});
@@ -14,15 +14,27 @@
* limitations under the License.
*/
import { Logger } from 'winston';
import { ConflictError, NotFoundError } from '@backstage/errors';
import { CatalogApi } from '@backstage/catalog-client';
import { UserEntity } from '@backstage/catalog-model';
import {
EntityName,
parseEntityRef,
RELATION_MEMBER_OF,
stringifyEntityRef,
UserEntity,
} from '@backstage/catalog-model';
import { TokenIssuer } from '../../identity';
type UserQuery = {
annotations: Record<string, string>;
};
type MemberClaimQuery = {
entityRefs: string[];
logger?: Logger;
};
/**
* A catalog client tailored for reading out identity data from the catalog.
*/
@@ -64,4 +76,62 @@ export class CatalogIdentityClient {
return items[0] as UserEntity;
}
/**
* Resolve additional entity claims from the catalog, using the passed-in entity names. Designed
* to be used within a `signInResolver` where additional entity claims might be provided, but
* group membership and transient group membership lean on imported catalog relations.
*
* Returns a superset of the entity names that can be passed directly to `issueToken` as `ent`.
*/
async resolveCatalogMembership({
entityRefs,
logger,
}: MemberClaimQuery): Promise<string[]> {
const resolvedEntityRefs = entityRefs
.map((ref: string) => {
try {
const parsedRef = parseEntityRef(ref.toLocaleLowerCase('en-US'), {
defaultKind: 'user',
defaultNamespace: 'default',
});
return parsedRef;
} catch {
logger?.warn(`Failed to parse entityRef from ${ref}, ignoring`);
return null;
}
})
.filter((ref): ref is EntityName => ref !== null);
const filter = resolvedEntityRefs.map(ref => ({
kind: ref.kind,
'metadata.namespace': ref.namespace,
'metadata.name': ref.name,
}));
const entities = await this.catalogApi
.getEntities({ filter })
.then(r => r.items);
if (entityRefs.length !== entities.length) {
const foundEntityNames = entities.map(stringifyEntityRef);
const missingEntityNames = resolvedEntityRefs
.map(stringifyEntityRef)
.filter(s => !foundEntityNames.includes(s));
logger?.debug(`Entities not found for refs ${missingEntityNames.join()}`);
}
const memberOf = entities.flatMap(
e =>
e!.relations
?.filter(r => r.type === RELATION_MEMBER_OF)
.map(r => r.target) ?? [],
);
const newEntityRefs = [
...new Set(resolvedEntityRefs.concat(memberOf).map(stringifyEntityRef)),
];
logger?.debug(`Found catalog membership: ${newEntityRefs.join()}`);
return newEntityRefs;
}
}
@@ -16,6 +16,7 @@
export * from './google';
export * from './microsoft';
export * from './okta';
export { factories as defaultAuthProviderFactories } from './factories';
// Export the minimal interface required for implementing a
@@ -14,5 +14,5 @@
* limitations under the License.
*/
export { createOktaProvider } from './provider';
export { createOktaProvider, oktaEmailSignInResolver } from './provider';
export type { OktaProviderOptions } from './provider';
@@ -0,0 +1,105 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { OktaAuthProvider } 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('createOktaProvider', () => {
it('should auth', async () => {
const tokenIssuer = {
issueToken: jest.fn(),
listPublicKeys: jest.fn(),
};
const catalogIdentityClient = {
findUser: jest.fn(),
};
const provider = new OktaAuthProvider({
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,
},
}),
audience: 'http://example.com',
clientId: 'mock',
clientSecret: 'mock',
callbackUrl: 'mock',
});
mockFrameHandler.mockResolvedValueOnce({
result: {
fullProfile: {
emails: [
{
type: 'work',
value: 'conrad@example.com',
},
],
displayName: 'Conrad',
name: {
familyName: 'Ribas',
givenName: 'Francisco',
},
id: 'conrad',
provider: 'okta',
photos: [
{
value: 'some-data',
},
],
},
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',
},
});
});
});
@@ -35,8 +35,16 @@ import {
executeFetchUserProfileStrategy,
PassportDoneCallback,
} from '../../lib/passport';
import { RedirectInfo, AuthProviderFactory } from '../types';
import {
AuthProviderFactory,
AuthHandler,
RedirectInfo,
SignInResolver,
} from '../types';
import { StateStore } from 'passport-oauth2';
import { CatalogIdentityClient, getEntityClaims } from '../../lib/catalog';
import { TokenIssuer } from '../../identity';
import { Logger } from 'winston';
type PrivateInfo = {
refreshToken: string;
@@ -44,10 +52,20 @@ type PrivateInfo = {
export type OktaAuthProviderOptions = OAuthProviderOptions & {
audience: string;
signInResolver?: SignInResolver<OAuthResult>;
authHandler: AuthHandler<OAuthResult>;
tokenIssuer: TokenIssuer;
catalogIdentityClient: CatalogIdentityClient;
logger: Logger;
};
export class OktaAuthProvider implements OAuthHandlers {
private readonly _strategy: any;
private readonly _signInResolver?: SignInResolver<OAuthResult>;
private readonly _authHandler: AuthHandler<OAuthResult>;
private readonly _tokenIssuer: TokenIssuer;
private readonly _catalogIdentityClient: CatalogIdentityClient;
private readonly _logger: Logger;
/**
* Due to passport-okta-oauth forcing options.state = true,
@@ -67,6 +85,12 @@ export class OktaAuthProvider implements OAuthHandlers {
};
constructor(options: OktaAuthProviderOptions) {
this._signInResolver = options.signInResolver;
this._authHandler = options.authHandler;
this._tokenIssuer = options.tokenIssuer;
this._catalogIdentityClient = options.catalogIdentityClient;
this._logger = options.logger;
this._strategy = new OktaStrategy(
{
clientID: options.clientId,
@@ -117,18 +141,8 @@ export class OktaAuthProvider implements OAuthHandlers {
PrivateInfo
>(req, this._strategy);
const profile = makeProfileInfo(result.fullProfile, result.params.id_token);
return {
response: await this.populateIdentity({
profile,
providerInfo: {
idToken: result.params.id_token,
accessToken: result.accessToken,
scope: result.params.scope,
expiresInSeconds: result.params.expires_in,
},
}),
response: await this.handleResult(result),
refreshToken: privateInfo.refreshToken,
};
}
@@ -144,52 +158,157 @@ export class OktaAuthProvider implements OAuthHandlers {
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('Okta 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,
};
if (this._signInResolver) {
response.backstageIdentity = await this._signInResolver(
{
result,
profile,
},
{
tokenIssuer: this._tokenIssuer,
catalogIdentityClient: this._catalogIdentityClient,
logger: this._logger,
},
);
}
// TODO(Rugvip): Hardcoded to the local part of the email for now
const id = profile.email.split('@')[0];
return { ...response, backstageIdentity: { id } };
return response;
}
}
export type OktaProviderOptions = {};
export const oktaEmailSignInResolver: SignInResolver<OAuthResult> = async (
info,
ctx,
) => {
const { profile } = info;
if (!profile.email) {
throw new Error('Okta profile contained no email');
}
const entity = await ctx.catalogIdentityClient.findUser({
annotations: {
'okta.com/email': profile.email,
},
});
const claims = getEntityClaims(entity);
const token = await ctx.tokenIssuer.issueToken({ claims });
return { id: entity.metadata.name, entity, token };
};
export const oktaDefaultSignInResolver: SignInResolver<OAuthResult> = async (
info,
ctx,
) => {
const { profile } = info;
if (!profile.email) {
throw new Error('Okta profile contained no email');
}
// TODO(Rugvip): Hardcoded to the local part of the email for now
const userId = profile.email.split('@')[0];
const token = await ctx.tokenIssuer.issueToken({
claims: { sub: userId, ent: [`user:default/${userId}`] },
});
return { id: userId, token };
};
export type OktaProviderOptions = {
/**
* 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 `okta.com/email` annotation.
*/
signIn?: {
resolver?: SignInResolver<OAuthResult>;
};
};
export const createOktaProvider = (
_options?: OktaProviderOptions,
): AuthProviderFactory => {
return ({ providerId, globalConfig, config, tokenIssuer }) =>
return ({
providerId,
globalConfig,
config,
tokenIssuer,
catalogApi,
logger,
}) =>
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const audience = envConfig.getString('audience');
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 ?? oktaDefaultSignInResolver;
const signInResolver: SignInResolver<OAuthResult> = info =>
signInResolverFn(info, {
catalogIdentityClient,
tokenIssuer,
logger,
});
const provider = new OktaAuthProvider({
audience,
clientId,
clientSecret,
callbackUrl,
authHandler,
signInResolver,
tokenIssuer,
catalogIdentityClient,
logger,
});
return OAuthAdapter.fromConfig(globalConfig, provider, {
+6 -6
View File
@@ -21,8 +21,8 @@
},
"dependencies": {
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/errors": "^0.1.1",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/theme": "^0.2.9",
@@ -35,10 +35,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
+10
View File
@@ -1,5 +1,15 @@
# @backstage/plugin-bitrise
## 0.1.10
### Patch Changes
- 8bedb75ae: Update Luxon dependency to 2.x
- Updated dependencies
- @backstage/core-components@0.3.1
- @backstage/core-plugin-api@0.1.6
- @backstage/plugin-catalog-react@0.4.2
## 0.1.9
### Patch Changes
+9 -9
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-bitrise",
"version": "0.1.9",
"version": "0.1.10",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,16 +21,16 @@
},
"dependencies": {
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/plugin-catalog-react": "^0.4.2",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"cross-fetch": "^3.0.6",
"lodash": "^4.17.21",
"luxon": "^1.26.0",
"luxon": "^2.0.2",
"qs": "^6.9.6",
"react": "^16.13.1",
"react-dom": "^16.13.1",
@@ -38,10 +38,10 @@
"recharts": "^1.8.5"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
+8
View File
@@ -1,5 +1,13 @@
# @backstage/plugin-catalog-backend
## 0.13.2
### Patch Changes
- Updated dependencies
- @backstage/integration@0.6.0
- @backstage/backend-common@0.8.9
## 0.13.1
### Patch Changes
+6 -6
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend",
"version": "0.13.1",
"version": "0.13.2",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -29,12 +29,12 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.8.6",
"@backstage/backend-common": "^0.8.9",
"@backstage/catalog-client": "^0.3.18",
"@backstage/catalog-model": "^0.9.0",
"@backstage/config": "^0.1.5",
"@backstage/errors": "^0.1.1",
"@backstage/integration": "^0.5.8",
"@backstage/integration": "^0.6.0",
"@backstage/plugin-search-backend-node": "^0.4.0",
"@backstage/search-common": "^0.1.2",
"@octokit/graphql": "^4.5.8",
@@ -61,9 +61,9 @@
"yup": "^0.29.3"
},
"devDependencies": {
"@backstage/backend-test-utils": "^0.1.4",
"@backstage/cli": "^0.7.6",
"@backstage/test-utils": "^0.1.16",
"@backstage/backend-test-utils": "^0.1.5",
"@backstage/cli": "^0.7.8",
"@backstage/test-utils": "^0.1.17",
"@types/core-js": "^2.5.4",
"@types/git-url-parse": "^9.0.0",
"@types/lodash": "^4.14.151",
+12
View File
@@ -1,5 +1,17 @@
# @backstage/plugin-catalog-import
## 0.5.17
### Patch Changes
- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version.
- Updated dependencies
- @backstage/integration@0.6.0
- @backstage/core-components@0.3.1
- @backstage/core-plugin-api@0.1.6
- @backstage/plugin-catalog-react@0.4.2
- @backstage/integration-react@0.1.7
## 0.5.16
### Patch Changes
+10 -10
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-import",
"version": "0.5.16",
"version": "0.5.17",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -32,11 +32,11 @@
"dependencies": {
"@backstage/catalog-client": "^0.3.18",
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/integration": "^0.5.9",
"@backstage/integration-react": "^0.1.6",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/integration": "^0.6.0",
"@backstage/integration-react": "^0.1.7",
"@backstage/plugin-catalog-react": "^0.4.2",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -54,10 +54,10 @@
"yaml": "^1.10.0"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^3.3.0",
@@ -17,7 +17,7 @@
const octokit = {
repos: {
get: () => Promise.resolve({ data: { default_branch: 'main' } }),
createOrUpdateFileContents: jest.fn().mockImplementation(async () => {}),
createOrUpdateFileContents: jest.fn(async () => {}),
},
search: {
code: jest.fn(),
@@ -26,10 +26,10 @@ const octokit = {
getRef: async () => ({
data: { object: { sha: 'any' } },
}),
createRef: jest.fn().mockImplementation(async () => {}),
createRef: jest.fn(async () => {}),
},
pulls: {
create: jest.fn().mockImplementation(async () => ({
create: jest.fn(async () => ({
data: {
html_url: 'http://pull/request/0',
},
@@ -46,6 +46,8 @@ jest.doMock('@octokit/rest', () => {
return { Octokit };
});
import { ConfigReader, UrlPatternDiscovery } from '@backstage/core-app-api';
import { OAuthApi } from '@backstage/core-plugin-api';
import { ScmIntegrations } from '@backstage/integration';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { msw } from '@backstage/test-utils';
@@ -54,9 +56,6 @@ import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { CatalogImportClient } from './CatalogImportClient';
import { ConfigReader, UrlPatternDiscovery } from '@backstage/core-app-api';
import { OAuthApi } from '@backstage/core-plugin-api';
describe('CatalogImportClient', () => {
const server = setupServer();
msw.setupDefaultHandlers(server);
@@ -119,6 +118,11 @@ describe('CatalogImportClient', () => {
});
});
afterEach(() => {
jest.restoreAllMocks();
jest.clearAllMocks();
});
describe('analyzeUrl', () => {
it('should add yaml location', async () => {
catalogApi.addLocation.mockResolvedValueOnce({
@@ -167,6 +171,53 @@ describe('CatalogImportClient', () => {
});
});
it('should add yaml location, if url includes query parameters', async () => {
catalogApi.addLocation.mockResolvedValueOnce({
location: {
id: 'id-0',
type: 'url',
target: 'http://example.com/folder/catalog-info.yaml?branch=test',
},
entities: [
{
apiVersion: '1',
kind: 'Component',
metadata: {
name: 'my-entity',
namespace: 'my-namespace',
},
},
],
});
await expect(
catalogImportClient.analyzeUrl(
'http://example.com/folder/catalog-info.yaml?branch=test',
),
).resolves.toEqual({
locations: [
{
entities: [
{
kind: 'Component',
name: 'my-entity',
namespace: 'my-namespace',
},
],
target: 'http://example.com/folder/catalog-info.yaml?branch=test',
},
],
type: 'locations',
});
expect(catalogApi.addLocation).toBeCalledTimes(1);
expect(catalogApi.addLocation.mock.calls[0][0]).toEqual({
type: 'url',
target: 'http://example.com/folder/catalog-info.yaml?branch=test',
dryRun: true,
});
});
it('should reject for integrations that are not github ones', async () => {
await expect(
catalogImportClient.analyzeUrl(
@@ -16,20 +16,20 @@
import { CatalogApi } from '@backstage/catalog-client';
import { EntityName } from '@backstage/catalog-model';
import {
GitHubIntegrationConfig,
ScmIntegrationRegistry,
} from '@backstage/integration';
import { Base64 } from 'js-base64';
import { Octokit } from '@octokit/rest';
import { PartialEntity } from '../types';
import { AnalyzeResult, CatalogImportApi } from './CatalogImportApi';
import { getGithubIntegrationConfig } from './GitHub';
import {
DiscoveryApi,
IdentityApi,
OAuthApi,
} from '@backstage/core-plugin-api';
import {
GitHubIntegrationConfig,
ScmIntegrationRegistry,
} from '@backstage/integration';
import { Octokit } from '@octokit/rest';
import { Base64 } from 'js-base64';
import { PartialEntity } from '../types';
import { AnalyzeResult, CatalogImportApi } from './CatalogImportApi';
import { getGithubIntegrationConfig } from './GitHub';
export class CatalogImportClient implements CatalogImportApi {
private readonly discoveryApi: DiscoveryApi;
@@ -53,7 +53,7 @@ export class CatalogImportClient implements CatalogImportApi {
}
async analyzeUrl(url: string): Promise<AnalyzeResult> {
if (url.match(/\.ya?ml$/)) {
if (new URL(url).pathname.match(/\.ya?ml$/)) {
const location = await this.catalogApi.addLocation({
type: 'url',
target: url,
+12
View File
@@ -1,5 +1,17 @@
# @backstage/plugin-catalog-react
## 0.4.2
### Patch Changes
- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version.
- a440d3b38: Move and rename `FavoriteEntity` component to `catalog-react`
- Updated dependencies
- @backstage/integration@0.6.0
- @backstage/core-app-api@0.1.8
- @backstage/core-components@0.3.1
- @backstage/core-plugin-api@0.1.6
## 0.4.1
### Patch Changes
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-react",
"version": "0.4.1",
"version": "0.4.2",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -30,10 +30,10 @@
"dependencies": {
"@backstage/catalog-client": "^0.3.18",
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-app-api": "^0.1.7",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/integration": "^0.5.9",
"@backstage/core-app-api": "^0.1.8",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/integration": "^0.6.0",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -47,9 +47,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^3.3.0",
+13
View File
@@ -1,5 +1,18 @@
# @backstage/plugin-catalog
## 0.6.11
### Patch Changes
- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version.
- a440d3b38: Move and rename `FavoriteEntity` component to `catalog-react`
- Updated dependencies
- @backstage/integration@0.6.0
- @backstage/core-components@0.3.1
- @backstage/core-plugin-api@0.1.6
- @backstage/plugin-catalog-react@0.4.2
- @backstage/integration-react@0.1.7
## 0.6.10
### Patch Changes
+10 -10
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog",
"version": "0.6.10",
"version": "0.6.11",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -32,12 +32,12 @@
"dependencies": {
"@backstage/catalog-client": "^0.3.18",
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/errors": "^0.1.1",
"@backstage/integration": "^0.5.9",
"@backstage/integration-react": "^0.1.6",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/integration": "^0.6.0",
"@backstage/integration-react": "^0.1.7",
"@backstage/plugin-catalog-react": "^0.4.2",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -54,10 +54,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^3.3.0",
+6 -6
View File
@@ -32,8 +32,8 @@
},
"dependencies": {
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
@@ -50,10 +50,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
+6 -6
View File
@@ -31,8 +31,8 @@
},
"dependencies": {
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
@@ -48,10 +48,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
@@ -1,5 +1,13 @@
# @backstage/plugin-code-coverage-backend
## 0.1.9
### Patch Changes
- Updated dependencies
- @backstage/integration@0.6.0
- @backstage/backend-common@0.8.9
## 0.1.8
### Patch Changes
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-code-coverage-backend",
"version": "0.1.8",
"version": "0.1.9",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -19,12 +19,12 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.8.5",
"@backstage/backend-common": "^0.8.9",
"@backstage/catalog-client": "^0.3.16",
"@backstage/catalog-model": "^0.9.0",
"@backstage/config": "^0.1.5",
"@backstage/errors": "^0.1.1",
"@backstage/integration": "^0.5.8",
"@backstage/integration": "^0.6.0",
"@types/express": "^4.17.6",
"cross-fetch": "^3.0.6",
"express": "^4.17.1",
@@ -36,7 +36,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.7.1",
"@backstage/cli": "^0.7.8",
"@types/express-xml-bodyparser": "^0.3.2",
"@types/supertest": "^2.0.8",
"msw": "^0.29.0",
+6 -6
View File
@@ -22,8 +22,8 @@
"dependencies": {
"@backstage/catalog-model": "^0.9.0",
"@backstage/config": "^0.1.6",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/errors": "^0.1.1",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/theme": "^0.2.9",
@@ -40,10 +40,10 @@
"recharts": "^1.8.5"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
+6 -6
View File
@@ -21,8 +21,8 @@
},
"dependencies": {
"@backstage/config": "^0.1.6",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/errors": "^0.1.1",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
@@ -35,10 +35,10 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
+10
View File
@@ -1,5 +1,15 @@
# @backstage/plugin-cost-insights
## 0.11.4
### Patch Changes
- 8bedb75ae: Update Luxon dependency to 2.x
- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version.
- Updated dependencies
- @backstage/core-components@0.3.1
- @backstage/core-plugin-api@0.1.6
## 0.11.3
### Patch Changes
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-cost-insights",
"version": "0.11.3",
"version": "0.11.4",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,8 +31,8 @@
},
"dependencies": {
"@backstage/config": "^0.1.6",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -42,7 +42,7 @@
"@types/recharts": "^1.8.14",
"classnames": "^2.2.6",
"history": "^5.0.0",
"luxon": "^1.26.0",
"luxon": "^2.0.2",
"pluralize": "^8.0.0",
"qs": "^6.9.4",
"react": "^16.13.1",
@@ -54,10 +54,10 @@
"yup": "^0.29.3"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
+10
View File
@@ -1,5 +1,15 @@
# @backstage/plugin-explore
## 0.3.13
### Patch Changes
- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version.
- Updated dependencies
- @backstage/core-components@0.3.1
- @backstage/core-plugin-api@0.1.6
- @backstage/plugin-catalog-react@0.4.2
## 0.3.12
### Patch Changes
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-explore",
"version": "0.3.12",
"version": "0.3.13",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,9 +31,9 @@
},
"dependencies": {
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/plugin-catalog-react": "^0.4.2",
"@backstage/plugin-explore-react": "^0.0.6",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
@@ -48,10 +48,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint')],
};
+59
View File
@@ -0,0 +1,59 @@
# FireHydrant Plugin
## Overview
The [FireHydrant](https://firehydrant.io) plugin brings incident management to Backstage, and it displays service incidents information such as active incidents and incident analytics. There are also quick action links that let you create and view incidents in FireHydrant.
<img src="./doc/firehydrant_plugin_screenshot.png" alt="FireHydrant plugin screenshot">
## Features
- View total active incidents for a service declared in FireHydrant
- Quick links to the top 5 most recent active incidents in FireHydrant
- View incident metrics for a given service within the last 30 days, including: healthiness, total time impacted, total number of incidents, and MTT* (Mean Time To *) data such as MTTD (detect), MTTA (acknowledge), MTTM (mitigate) and MTTR (resolve).
## Setup
1. Install the plugin:
```bash
# From your Backstage root directory
cd packages/app
yarn add @backstage/plugin-firehydrant
```
2. Add the plugin to `EntityPage.tsx`, inside the `const overviewContent`'s parent `<Grid>` component:
```ts
// In packages/app/src/components/catalog/EntityPage.tsx
import { FirehydrantCard } from '@backstage/plugin-firehydrant';
// Add to code as a grid item
<Grid item md={6}>
<FirehydrantCard />
</Grid>;
```
3. Add proxy configuration to `app-config.yaml`:
```yaml
proxy:
'/firehydrant/api':
target: 'https://api.firehydrant.io/v1/'
changeOrigin: true
headers:
# Supply the token you generated from https://app.firehydrant.io/organizations/bots
Authorization: Bearer ${FIREHYDRANT_BOT_TOKEN}
```
Note: if you are not using environment variables, you can directly type the API Bot Token into `app-config.yaml`:
```yaml
proxy:
'/firehydrant/api':
target: 'https://api.firehydrant.io/v1/'
changeOrigin: true
headers:
# Supply the token you generated from https://app.firehydrant.io/organizations/bots
Authorization: Bearer fhb-e4911b22bcd788c4a4afeb0c111ffbfa
```
+27
View File
@@ -0,0 +1,27 @@
## API Report File for "@backstage/plugin-firehydrant"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
/// <reference types="react" />
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { RouteRef } from '@backstage/core-plugin-api';
// Warning: (ae-missing-release-tag) "FirehydrantCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const FirehydrantCard: () => JSX.Element;
// Warning: (ae-missing-release-tag) "firehydrantPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const firehydrantPlugin: BackstagePlugin<
{
root: RouteRef<undefined>;
},
{}
>;
// (No @packageDocumentation comment for this package)
```
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createDevApp } from '@backstage/dev-utils';
import { firehydrantPlugin } from '../src/plugin';
createDevApp().registerPlugin(firehydrantPlugin).render();
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

+68
View File
@@ -0,0 +1,68 @@
{
"name": "@backstage/plugin-firehydrant",
"version": "0.1.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": false,
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"scripts": {
"build": "backstage-cli plugin:build",
"start": "backstage-cli plugin:serve",
"lint": "backstage-cli lint",
"test": "backstage-cli test",
"diff": "backstage-cli plugin:diff",
"prepack": "backstage-cli prepack",
"postpack": "backstage-cli postpack",
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"luxon": "^1.27.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
"@types/jest": "^26.0.7",
"@types/node": "^14.14.32",
"cross-fetch": "^3.0.6",
"msw": "^0.29.0"
},
"files": [
"dist"
],
"configSchema": {
"$schema": "https://backstage.io/schema/config-v1",
"title": "@backstage/plugin-firehydrant",
"type": "object",
"properties": {
"firehydrant": {
"type": "object",
"properties": {
"baseUrl": {
"type": "string",
"visibility": "frontend"
}
}
}
}
}
}
+136
View File
@@ -0,0 +1,136 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
ServiceAnalyticsResponse,
ServiceDetailsResponse,
ServiceIncidentsResponse,
} from './types';
import { Incident, Service } from '../components/types';
import { createApiRef, DiscoveryApi } from '@backstage/core-plugin-api';
export interface FireHydrantAPI {
getServiceAnalytics(options: {
serviceId: string;
startDate: string;
endDate: string;
}): Promise<ServiceAnalyticsResponse>;
getServiceDetails(options: {
serviceName: string;
}): Promise<ServiceDetailsResponse>;
getServiceIncidents(options: {
serviceId: string;
}): Promise<ServiceIncidentsResponse>;
}
export const fireHydrantApiRef = createApiRef<FireHydrantAPI>({
id: 'plugin.firehydrant.service',
description: 'Used by FireHydrant plugin for requests',
});
export type Options = {
discoveryApi: DiscoveryApi;
proxyPath?: string;
};
const DEFAULT_PROXY_PATH = '/firehydrant/api';
export class FireHydrantAPIClient implements FireHydrantAPI {
private readonly discoveryApi: DiscoveryApi;
private readonly proxyPath: string;
constructor(options: Options) {
this.discoveryApi = options.discoveryApi;
this.proxyPath = options.proxyPath ?? DEFAULT_PROXY_PATH;
}
async getServiceAnalytics(options: {
serviceId: string;
startDate: string;
endDate: string;
}): Promise<ServiceAnalyticsResponse> {
const proxyUrl = await this.getApiUrl();
const response = await fetch(
`${proxyUrl}/metrics/services/${options.serviceId}?start_date=${options.startDate}&end_date=${options.endDate}`,
);
if (!response.ok) {
throw new Error(
`There was a problem fetching FireHydrant analytics data: ${response.statusText}`,
);
}
const json = await response.json();
return json;
}
async getServiceDetails(options: {
serviceName: string;
}): Promise<ServiceDetailsResponse> {
const proxyUrl = await this.getApiUrl();
const response = await fetch(
`${proxyUrl}/services?query=${options.serviceName}`,
);
if (!response.ok) {
throw new Error(
`There was a problem fetching FireHydrant data: ${response.statusText}`,
);
}
const json = await response.json();
const servicesData: ServiceDetailsResponse = {
service: {} as Service,
incidents: [] as Incident[],
};
if (json.data?.length === 0) {
return servicesData;
}
servicesData.service = json.data[0];
const incidentsJson = await this.getServiceIncidents({
serviceId: json.data[0].id,
});
servicesData.incidents = incidentsJson;
return servicesData;
}
async getServiceIncidents(options: {
serviceId: string;
}): Promise<ServiceIncidentsResponse> {
const proxyUrl = await this.getApiUrl();
const response = await fetch(
`${proxyUrl}/incidents?services=${options.serviceId}&active=true`,
);
if (!response.ok) {
throw new Error(
`There was a problem fetching FireHydrant incidents data: ${response.statusText}`,
);
}
const json = await response.json();
return json.data;
}
private async getApiUrl() {
const proxyUrl = await this.discoveryApi.getBaseUrl('proxy');
return proxyUrl + this.proxyPath;
}
}
+33
View File
@@ -0,0 +1,33 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Incident, Service } from '../components/types';
export type ServiceDetailsResponse = {
service?: Service;
incidents?: ServiceIncidentsResponse;
};
export type ServiceAnalyticsResponse = {
id: string;
mttd?: number;
mtta?: number;
mttm?: number;
mttr?: number;
count: number;
total_time: number;
};
export type ServiceIncidentsResponse = Incident[];
@@ -0,0 +1,42 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { screen } from '@testing-library/react';
import { ServiceAnalytics } from './ServiceAnalytics';
import { renderInTestApp } from '@backstage/test-utils';
describe('ServiceAnalytics', () => {
it('renders service analytics', async () => {
await renderInTestApp(
<ServiceAnalytics
error={null}
loading={false}
value={{
id: 'b20de448-7855-4fb0-8d98-d9eafe82fc2c',
mttd: 32,
mtta: 44,
mttm: 33,
mttr: 23,
count: 0,
total_time: 0,
}}
/>,
);
expect(await screen.findByText(/32/)).toBeInTheDocument();
expect(await screen.findByText(/44/)).toBeInTheDocument();
});
});
@@ -0,0 +1,166 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import { DateTime } from 'luxon';
import {
ResponseErrorPanel,
Table,
TableColumn,
Progress,
} from '@backstage/core-components';
const useStyles = makeStyles({
container: {
overflow: 'auto',
width: '100%',
'& td': {
minWidth: '145px',
},
'& th': {
minWidth: '145px',
},
},
});
export const DenseTable = ({
service,
startDate,
endDate,
}: {
service: object;
startDate: DateTime;
endDate: DateTime;
}) => {
const classes = useStyles();
const columns: TableColumn[] = [
{ field: 'healthiness', title: 'Healthiness' },
{ field: 'impacted', title: 'Impacted' },
{ field: 'incidents', title: 'Incidents' },
{ field: 'mttd', title: 'MTTD' },
{ field: 'mtta', title: 'MTTA' },
{ field: 'mttm', title: 'MTTM' },
{ field: 'mttr', title: 'MTTR' },
];
return (
<div className={classes.container}>
<Table
title="Incident Analytics"
subtitle={`${startDate.toFormat('MMMM dd, yyyy')} - ${endDate.toFormat(
'MMMM dd, yyyy',
)}`}
options={{ paging: false, search: false }}
columns={columns}
data={[service]}
/>
</div>
);
};
export const secondsToDhms = (seconds: number) => {
const secs = Number(seconds);
const d = Math.floor(secs / (60 * 60 * 24));
const h = Math.floor((secs / (60 * 60)) % 24);
const m = Math.floor((secs / 60) % 60);
const s = secs % 60;
const dDisplay = d > 0 ? `${d}d ` : '';
const hDisplay = h > 0 ? `${h}h ` : '00h ';
const mDisplay = m > 0 ? `${m}m ` : '00m ';
const sDisplay = s > 0 ? `${s}s` : '00s';
return dDisplay + hDisplay + mDisplay + sDisplay;
};
export const truncateNum = (num: number) => {
const matcher = `^-?\\d+(?:\\.\\d{0,3})?`;
const re = new RegExp(matcher);
const match = num.toString().match(re);
const result = (match && match[0]) || '0'; // eslint-disable-line no-mixed-operators
return result;
};
export const calcHealthiness = ({
mttm,
incidents,
range,
}: {
mttm: number;
incidents: number;
range: number;
}) => {
const num = (1 - (mttm * incidents) / range) * 100;
return `${truncateNum(num)}%`;
};
type AnalyticsDataType = {
id?: string;
count?: number;
mttd?: number;
mtta?: number;
mttm?: number;
mttr?: number;
total_time?: number;
};
export const ServiceAnalytics = ({
value,
loading,
error,
}: {
value: AnalyticsDataType;
loading: boolean;
error: any;
}) => {
if (loading) {
return <Progress />;
} else if (error) {
return <ResponseErrorPanel error={error} />;
}
const startDate = DateTime.now().minus({ days: 30 }).toUTC();
const endDate = DateTime.now().toUTC();
// Transform and format the data to display in the table
if (value.id) {
const serviceData = {
healthiness:
value.mttm && value.count
? calcHealthiness({
mttm: value.mttm,
incidents: value.count,
range: endDate.diff(startDate, 'seconds').as('seconds'),
})
: '100%',
impacted: value.total_time ? secondsToDhms(value.total_time) : '-',
incidents: value.count,
mttd: value.mttd ? secondsToDhms(value.mttd) : '-',
mtta: value.mtta ? secondsToDhms(value.mtta) : '-',
mttm: value.mttm ? secondsToDhms(value.mttm) : '-',
mttr: value.mttr ? secondsToDhms(value.mttr) : '-',
};
return (
<DenseTable
service={serviceData}
startDate={startDate}
endDate={endDate}
/>
);
}
return null;
};
@@ -0,0 +1,118 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { fireHydrantApiRef } from '../../api';
import { screen } from '@testing-library/react';
import { ServiceDetailsCard } from './ServiceDetailsCard';
import { Service, Incident } from '../types';
import { renderInTestApp } from '@backstage/test-utils';
const mockFireHydrantApi = {
getServiceDetails: () => {},
getServiceAnalytics: () => {},
};
const apis = ApiRegistry.from([[fireHydrantApiRef, mockFireHydrantApi]]);
jest.mock('@backstage/plugin-catalog-react', () => ({
useEntity: () => {
return { entity: { metadata: { name: 'service-example' } } };
},
}));
describe('ServiceDetailsCard', () => {
it('renders service incidents', async () => {
mockFireHydrantApi.getServiceDetails = jest.fn().mockImplementationOnce(
async () =>
[
{
service: {
id: '12345',
name: 'service-example',
description: 'This is a sample description',
active_incidents: ['654321'],
} as Service,
incidents: [
{
id: '654321',
active: true,
description: 'test incident',
name: 'incident name here',
incident_url: 'http://example.com',
} as Incident,
],
},
][0],
);
mockFireHydrantApi.getServiceAnalytics = jest.fn().mockImplementationOnce(
async () =>
[
{
buckets: [
{
metrics: {
12345: {
count: 0,
mtta: null,
mttd: null,
mttm: null,
mttr: null,
total_time: null,
},
},
},
],
},
][0],
);
await renderInTestApp(
<ApiProvider apis={apis}>
<ServiceDetailsCard />
</ApiProvider>,
);
expect(
await screen.findByText(/View service incidents/),
).toBeInTheDocument();
expect(
await screen.findByText(/There is 1 active incident/),
).toBeInTheDocument();
expect(await screen.findByText(/incident name here/)).toBeInTheDocument();
});
it('handles empty response', async () => {
mockFireHydrantApi.getServiceDetails = jest
.fn()
.mockImplementationOnce(async () => []);
mockFireHydrantApi.getServiceAnalytics = jest
.fn()
.mockImplementationOnce(async () => []);
await renderInTestApp(
<ApiProvider apis={apis}>
<ServiceDetailsCard />
</ApiProvider>,
);
expect(
await screen.findByText(/This service does not exist in FireHydrant/),
).toBeInTheDocument();
});
});
@@ -0,0 +1,305 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useEffect, useState } from 'react';
import { DateTime } from 'luxon';
import { ServiceAnalytics } from '../ServiceAnalytics/ServiceAnalytics';
import {
Box,
Button as MaterialButton,
Typography,
makeStyles,
} from '@material-ui/core';
import ExitToAppIcon from '@material-ui/icons/ExitToApp';
import NotesIcon from '@material-ui/icons/Notes';
import WhatshotIcon from '@material-ui/icons/Whatshot';
import WarningIcon from '@material-ui/icons/Warning';
import AddIcon from '@material-ui/icons/Add';
import { useEntity } from '@backstage/plugin-catalog-react';
import { Incident } from '../types';
import { ServiceIncidentsResponse } from '../../api/types';
import { useServiceDetails } from '../serviceDetails';
import { useServiceAnalytics } from '../serviceAnalytics';
import {
InfoCard,
Link,
Progress,
ResponseErrorPanel,
} from '@backstage/core-components';
import { configApiRef, useApi } from '@backstage/core-plugin-api';
const useStyles = makeStyles(theme => ({
button: {
color: '#3b2492',
display: 'grid',
gridGap: '4px',
textAlign: 'center',
justifyItems: 'center',
width: '105px',
backgroundColor: theme.palette.type === 'dark' ? '#f1edff' : '',
'&:hover, &:focus': {
backgroundColor: '#f1edff',
color: '#614ab6',
},
'&:active': {
color: '#3b2492',
backgroundColor: '#b2a6e3',
boxShadow:
'rgb(59, 36, 146) 0px 0px 0px 1px inset, rgb(141, 134, 188) 3px 3px 0px 0px inset;',
},
border: '1px solid #3b2492',
borderRadius: '5px',
padding: '8px 10px',
textTransform: 'none',
},
buttonLink: {
backgroundColor: '#3b2492',
color: '#FFF',
textTransform: 'none',
'&:hover': {
backgroundColor: '#614ab6',
},
},
buttonContainer: {
display: 'grid',
gridGap: '24px',
gridAutoFlow: 'column',
gridAutoColumns: 'min-content',
},
icon: {
color: '#f1642d',
},
link: {
textDecoration: 'underline',
fontSize: '16px',
lineHeight: '27px',
color: '#3b2492',
'&:hover, &:focus': {
fontWeight: '500',
},
},
linksContainer: {
borderBottom: '1px solid #d5d5d5',
padding: '10px 0px 10px 20px',
backgroundColor: '#f1edff',
marginBottom: '20px',
},
table: {
width: '100%',
},
warning: {
display: 'flex',
alignItems: 'center',
padding: '10px',
background: '#f1edff',
color: '#3b2492',
},
}));
const ServiceAnalyticsView = ({
serviceId,
startDate,
endDate,
}: {
serviceId: string;
startDate: DateTime;
endDate: DateTime;
}) => {
const {
loading: analyticsLoading,
value: analyticsValue = {},
error: analyticsError,
} = useServiceAnalytics({
serviceId,
startDate: startDate.toFormat('YYYY-MM-DD'),
endDate: endDate.toFormat('YYYY-MM-DD'),
});
return (
<ServiceAnalytics
loading={analyticsLoading}
value={analyticsValue}
error={analyticsError}
/>
);
};
export const ServiceDetailsCard = () => {
const { entity } = useEntity();
const classes = useStyles();
const [showServiceDetails, setShowServiceDetails] = useState(false);
const configApi = useApi(configApiRef);
const BASE_URL =
configApi.getOptionalString('firehydrant.baseUrl') ||
'https://app.firehydrant.io';
const startDate = DateTime.now().minus({ days: 30 }).toUTC();
const endDate = DateTime.now().toUTC();
// The Backstage service name in FireHydrant is a unique formatted string
// that requires the entity's kind, name, and namespace.
const fireHydrantServiceName = `${entity?.kind}:${
entity?.metadata?.namespace ?? 'default'
}/${entity?.metadata?.name}`;
const { loading, value, error } = useServiceDetails({
serviceName: fireHydrantServiceName,
});
const activeIncidents: string[] = value?.service?.active_incidents ?? [];
const incidents: ServiceIncidentsResponse = value?.incidents ?? [];
const serviceId: string = value?.service?.id!;
useEffect(() => {
if (value?.service && Object.keys(value?.service).length > 0) {
setShowServiceDetails(true);
}
}, [value]);
if (loading) {
return <Progress />;
}
if (error) {
return <ResponseErrorPanel error={error} />;
}
const headerText: string = showServiceDetails
? `There ${activeIncidents?.length === 1 ? 'is' : 'are'} ${
activeIncidents?.length
} active incident${activeIncidents?.length === 1 ? '' : 's'}.`
: '';
const serviceIncidentsLink: string = `${BASE_URL}/incidents?search={"services":[{"label":${JSON.stringify(
value?.service?.name,
)},"value":${JSON.stringify(value?.service?.id)}}]}`;
return (
<InfoCard>
{!showServiceDetails && !loading && (
<div className={classes.warning}>
<WarningIcon />
&nbsp;&nbsp;<span>This service does not exist in FireHydrant.</span>
</div>
)}
{showServiceDetails && (
<Box
alignItems="center"
display="flex"
justifyContent="space-between"
borderBottom="1px solid #d5d5d5"
>
<Box>
<h2>{headerText}</h2>
</Box>
<Box>
<MaterialButton
className={classes.buttonLink}
color="default"
href={serviceIncidentsLink}
startIcon={<ExitToAppIcon />}
target="_blank"
variant="outlined"
>
View service incidents
</MaterialButton>
</Box>
</Box>
)}
{activeIncidents && activeIncidents?.length > 0 && (
<Box className={classes.linksContainer}>
{incidents &&
incidents?.slice(0, 5).map((incident: Incident, index: number) => (
<div key={index}>
<Link
className={classes.link}
to={incident.incident_url}
target="_blank"
rel="noopener noreferrer"
>
{incident.name}
</Link>
</div>
))}
</Box>
)}
<Box paddingLeft="16px" marginTop="10px">
<Typography variant="subtitle1">View in FireHydrant </Typography>
<Box className={classes.buttonContainer} marginTop="10px">
<MaterialButton
component={Link}
target="_blank"
rel="noopener"
className={classes.button}
to={`${BASE_URL}/incidents/new`}
>
<Box flexDirection="column">
<Box>
<AddIcon className={classes.icon} />
</Box>
<Box>
<span>Declare an incident</span>
</Box>
</Box>
</MaterialButton>
<MaterialButton
component={Link}
target="_blank"
rel="noopener"
className={classes.button}
to={`${BASE_URL}/incidents`}
>
<Box flexDirection="column">
<Box>
<WhatshotIcon className={classes.icon} />
</Box>
<Box>
<span>View all incidents</span>
</Box>
</Box>
</MaterialButton>
{showServiceDetails && (
<MaterialButton
component={Link}
target="_blank"
rel="noopener"
className={classes.button}
to={`${BASE_URL}/services/${value?.service?.id}`}
>
<Box flexDirection="column">
<Box>
<NotesIcon className={classes.icon} />
</Box>
<Box>
<span>View Service Details</span>
</Box>
</Box>
</MaterialButton>
)}
</Box>
</Box>
{showServiceDetails && (
<Box>
<ServiceAnalyticsView
serviceId={serviceId}
startDate={startDate}
endDate={endDate}
/>
</Box>
)}
</InfoCard>
);
};
@@ -0,0 +1,16 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { ServiceDetailsCard } from './ServiceDetailsCard';
@@ -0,0 +1,51 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useAsyncRetry } from 'react-use';
import { fireHydrantApiRef } from '../api';
import { errorApiRef, useApi } from '@backstage/core-plugin-api';
export const useServiceAnalytics = ({
serviceId,
startDate,
endDate,
}: {
serviceId: string;
startDate: string;
endDate: string;
}) => {
const api = useApi(fireHydrantApiRef);
const errorApi = useApi(errorApiRef);
const { loading, value, error, retry } = useAsyncRetry(async () => {
try {
return await api.getServiceAnalytics({
serviceId: serviceId,
startDate: startDate,
endDate: endDate,
});
} catch (e) {
errorApi.post(e);
return Promise.reject(e);
}
});
return {
loading,
value,
error,
retry,
};
};
@@ -0,0 +1,39 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useAsyncRetry } from 'react-use';
import { fireHydrantApiRef } from '../api';
import { errorApiRef, useApi } from '@backstage/core-plugin-api';
export const useServiceDetails = ({ serviceName }: { serviceName: string }) => {
const api = useApi(fireHydrantApiRef);
const errorApi = useApi(errorApiRef);
const { loading, value, error, retry } = useAsyncRetry(async () => {
try {
return await api.getServiceDetails({ serviceName: serviceName });
} catch (e) {
errorApi.post(e);
return Promise.reject(e);
}
});
return {
loading,
value,
error,
retry,
};
};
@@ -0,0 +1,29 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export type Service = {
id: string;
name: string;
description?: string;
active_incidents?: string[];
};
export type Incident = {
id: string;
active: boolean;
description?: string;
name: string;
incident_url: string;
};
+16
View File
@@ -0,0 +1,16 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { firehydrantPlugin, FirehydrantCard } from './plugin';
+22
View File
@@ -0,0 +1,22 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { firehydrantPlugin } from './plugin';
describe('firehydrant', () => {
it('should export plugin', () => {
expect(firehydrantPlugin).toBeDefined();
});
});
+49
View File
@@ -0,0 +1,49 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { fireHydrantApiRef, FireHydrantAPIClient } from './api';
import {
createApiFactory,
createPlugin,
discoveryApiRef,
createComponentExtension,
} from '@backstage/core-plugin-api';
import { rootRouteRef } from './routes';
export const firehydrantPlugin = createPlugin({
id: 'firehydrant',
apis: [
createApiFactory({
api: fireHydrantApiRef,
deps: { discoveryApi: discoveryApiRef },
factory: ({ discoveryApi }) => new FireHydrantAPIClient({ discoveryApi }),
}),
],
routes: {
root: rootRouteRef,
},
});
export const FirehydrantCard = firehydrantPlugin.provide(
createComponentExtension({
component: {
lazy: () =>
import('./components/ServiceDetailsCard').then(
m => m.ServiceDetailsCard,
),
},
}),
);
+20
View File
@@ -0,0 +1,20 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createRouteRef } from '@backstage/core-plugin-api';
export const rootRouteRef = createRouteRef({
title: 'firehydrant',
});
+17
View File
@@ -0,0 +1,17 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import '@testing-library/jest-dom';
import 'cross-fetch/polyfill';
+10
View File
@@ -1,5 +1,15 @@
# @backstage/plugin-fossa
## 0.2.14
### Patch Changes
- 8bedb75ae: Update Luxon dependency to 2.x
- Updated dependencies
- @backstage/core-components@0.3.1
- @backstage/core-plugin-api@0.1.6
- @backstage/plugin-catalog-react@0.4.2
## 0.2.13
### Patch Changes
+9 -9
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-fossa",
"version": "0.2.13",
"version": "0.2.14",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -32,26 +32,26 @@
},
"dependencies": {
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/errors": "^0.1.1",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/plugin-catalog-react": "^0.4.2",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"cross-fetch": "^3.0.6",
"luxon": "^1.26.0",
"luxon": "^2.0.2",
"p-limit": "^3.0.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
+6 -6
View File
@@ -30,8 +30,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -42,10 +42,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
+11
View File
@@ -1,5 +1,16 @@
# @backstage/plugin-git-release-manager
## 0.2.3
### Patch Changes
- 8bedb75ae: Update Luxon dependency to 2.x
- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version.
- Updated dependencies
- @backstage/integration@0.6.0
- @backstage/core-components@0.3.1
- @backstage/core-plugin-api@0.1.6
## 0.2.2
### Patch Changes
+9 -9
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-git-release-manager",
"version": "0.2.2",
"version": "0.2.3",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,16 +20,16 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/integration": "^0.5.9",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/integration": "^0.6.0",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@octokit/rest": "^18.5.3",
"@types/react": "*",
"luxon": "^1.26.0",
"luxon": "^2.0.2",
"qs": "^6.10.1",
"react-dom": "^16.13.1",
"react-router": "6.0.0-beta.0",
@@ -38,10 +38,10 @@
"recharts": "^1.8.5"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react-hooks": "^3.4.2",
"@testing-library/react": "^11.2.5",
+11
View File
@@ -1,5 +1,16 @@
# @backstage/plugin-github-actions
## 0.4.16
### Patch Changes
- 8bedb75ae: Update Luxon dependency to 2.x
- Updated dependencies
- @backstage/integration@0.6.0
- @backstage/core-components@0.3.1
- @backstage/core-plugin-api@0.1.6
- @backstage/plugin-catalog-react@0.4.2
## 0.4.15
### Patch Changes
+10 -10
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-github-actions",
"version": "0.4.15",
"version": "0.4.16",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -33,16 +33,16 @@
},
"dependencies": {
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/integration": "^0.5.9",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/integration": "^0.6.0",
"@backstage/plugin-catalog-react": "^0.4.2",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@octokit/rest": "^18.5.3",
"luxon": "^1.27.0",
"luxon": "^2.0.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-lazylog": "^4.5.3",
@@ -51,10 +51,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
+12
View File
@@ -1,5 +1,17 @@
# @backstage/plugin-github-deployments
## 0.1.14
### Patch Changes
- 8bedb75ae: Update Luxon dependency to 2.x
- Updated dependencies
- @backstage/integration@0.6.0
- @backstage/core-components@0.3.1
- @backstage/core-plugin-api@0.1.6
- @backstage/plugin-catalog-react@0.4.2
- @backstage/integration-react@0.1.7
## 0.1.13
### Patch Changes
+11 -11
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-github-deployments",
"version": "0.1.13",
"version": "0.1.14",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,27 +21,27 @@
},
"dependencies": {
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/errors": "^0.1.1",
"@backstage/integration": "^0.5.9",
"@backstage/integration-react": "^0.1.6",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/integration": "^0.6.0",
"@backstage/integration-react": "^0.1.7",
"@backstage/plugin-catalog-react": "^0.4.2",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@octokit/graphql": "^4.5.8",
"luxon": "^1.26.0",
"luxon": "^2.0.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
+6 -6
View File
@@ -31,8 +31,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -43,10 +43,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
+6 -6
View File
@@ -31,8 +31,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -44,10 +44,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
+10
View File
@@ -1,5 +1,15 @@
# @backstage/plugin-ilert
## 0.1.9
### Patch Changes
- 8bedb75ae: Update Luxon dependency to 2.x
- Updated dependencies
- @backstage/core-components@0.3.1
- @backstage/core-plugin-api@0.1.6
- @backstage/plugin-catalog-react@0.4.2
## 0.1.8
### Patch Changes
+9 -9
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-ilert",
"version": "0.1.8",
"version": "0.1.9",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,10 +21,10 @@
},
"dependencies": {
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/errors": "^0.1.1",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/plugin-catalog-react": "^0.4.2",
"@backstage/theme": "^0.2.9",
"@date-io/luxon": "2.x",
"@material-ui/core": "^4.12.2",
@@ -32,16 +32,16 @@
"@material-ui/lab": "4.0.0-alpha.45",
"@material-ui/pickers": "^3.3.10",
"humanize-duration": "^3.26.0",
"luxon": "^1.26.0",
"luxon": "^2.0.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
+10
View File
@@ -1,5 +1,15 @@
# @backstage/plugin-jenkins
## 0.5.3
### Patch Changes
- 8bedb75ae: Update Luxon dependency to 2.x
- Updated dependencies
- @backstage/core-components@0.3.1
- @backstage/core-plugin-api@0.1.6
- @backstage/plugin-catalog-react@0.4.2
## 0.5.2
### Patch Changes
+9 -9
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-jenkins",
"version": "0.5.2",
"version": "0.5.3",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -32,14 +32,14 @@
},
"dependencies": {
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/plugin-catalog-react": "^0.4.2",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"luxon": "^1.25.0",
"luxon": "^2.0.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router": "6.0.0-beta.0",
@@ -47,10 +47,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
+6 -6
View File
@@ -21,8 +21,8 @@
},
"dependencies": {
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
@@ -34,10 +34,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^3.4.2",
+8
View File
@@ -1,5 +1,13 @@
# @backstage/plugin-kubernetes-backend
## 0.3.14
### Patch Changes
- bbcd92afa: Adds ability to send an ExternalId with the assume role request to AWS
- Updated dependencies
- @backstage/backend-common@0.8.9
## 0.3.13
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-kubernetes-backend",
"version": "0.3.13",
"version": "0.3.14",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,7 +31,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.8.8",
"@backstage/backend-common": "^0.8.9",
"@backstage/catalog-model": "^0.9.0",
"@backstage/config": "^0.1.6",
"@backstage/plugin-kubernetes-common": "^0.1.3",
@@ -53,7 +53,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/cli": "^0.7.8",
"@types/aws4": "^1.5.1",
"supertest": "^6.1.3",
"aws-sdk-mock": "^5.2.1",
+10
View File
@@ -1,5 +1,15 @@
# @backstage/plugin-kubernetes
## 0.4.11
### Patch Changes
- 8bedb75ae: Update Luxon dependency to 2.x
- Updated dependencies
- @backstage/core-components@0.3.1
- @backstage/core-plugin-api@0.1.6
- @backstage/plugin-catalog-react@0.4.2
## 0.4.10
### Patch Changes
+9 -9
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-kubernetes",
"version": "0.4.10",
"version": "0.4.11",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -32,9 +32,9 @@
"dependencies": {
"@backstage/catalog-model": "^0.9.0",
"@backstage/config": "^0.1.6",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/plugin-catalog-react": "^0.4.2",
"@backstage/plugin-kubernetes-common": "^0.1.3",
"@backstage/theme": "^0.2.9",
"@kubernetes/client-node": "^0.15.0",
@@ -43,17 +43,17 @@
"@material-ui/lab": "4.0.0-alpha.45",
"js-yaml": "^4.0.0",
"lodash": "^4.17.21",
"luxon": "^1.26.0",
"luxon": "^2.0.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^3.4.2",
+10
View File
@@ -1,5 +1,15 @@
# @backstage/plugin-lighthouse
## 0.2.23
### Patch Changes
- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version.
- Updated dependencies
- @backstage/core-components@0.3.1
- @backstage/core-plugin-api@0.1.6
- @backstage/plugin-catalog-react@0.4.2
## 0.2.22
### Patch Changes
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-lighthouse",
"version": "0.2.22",
"version": "0.2.23",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -33,9 +33,9 @@
"dependencies": {
"@backstage/catalog-model": "^0.9.0",
"@backstage/config": "^0.1.6",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/plugin-catalog-react": "^0.4.2",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -47,10 +47,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
+6 -6
View File
@@ -31,8 +31,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -42,10 +42,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
+6 -6
View File
@@ -21,8 +21,8 @@
},
"dependencies": {
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
@@ -35,10 +35,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
+11
View File
@@ -1,5 +1,16 @@
# @backstage/plugin-pagerduty
## 0.3.11
### Patch Changes
- 8bedb75ae: Update Luxon dependency to 2.x
- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version.
- Updated dependencies
- @backstage/core-components@0.3.1
- @backstage/core-plugin-api@0.1.6
- @backstage/plugin-catalog-react@0.4.2
## 0.3.10
### Patch Changes
+9 -9
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-pagerduty",
"version": "0.3.10",
"version": "0.3.11",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,26 +31,26 @@
},
"dependencies": {
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/plugin-catalog-react": "^0.4.2",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@types/react": "*",
"classnames": "^2.2.6",
"luxon": "1.25.0",
"luxon": "2.0.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
+10
View File
@@ -1,5 +1,15 @@
# @backstage/plugin-rollbar
## 0.3.12
### Patch Changes
- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version.
- Updated dependencies
- @backstage/core-components@0.3.1
- @backstage/core-plugin-api@0.1.6
- @backstage/plugin-catalog-react@0.4.2
## 0.3.11
### Patch Changes
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-rollbar",
"version": "0.3.11",
"version": "0.3.12",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -32,9 +32,9 @@
},
"dependencies": {
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/core-components": "^0.3.1",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/plugin-catalog-react": "^0.4.2",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -48,10 +48,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@backstage/cli": "^0.7.8",
"@backstage/core-app-api": "^0.1.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^3.3.0",
@@ -0,0 +1,10 @@
# @backstage/plugin-scaffolder-backend-module-cookiecutter
## 0.1.1
### Patch Changes
- Updated dependencies
- @backstage/integration@0.6.0
- @backstage/plugin-scaffolder-backend@0.15.0
- @backstage/backend-common@0.8.9
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-backend-module-cookiecutter",
"version": "0.1.0",
"version": "0.1.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -19,10 +19,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.8.6",
"@backstage/backend-common": "^0.8.9",
"@backstage/errors": "^0.1.1",
"@backstage/integration": "^0.5.7",
"@backstage/plugin-scaffolder-backend": "^0.14.0",
"@backstage/integration": "^0.6.0",
"@backstage/plugin-scaffolder-backend": "^0.15.0",
"@backstage/config": "^0.1.5",
"command-exists": "^1.2.9",
"fs-extra": "10.0.0",
@@ -31,7 +31,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.7.3",
"@backstage/cli": "^0.7.8",
"@types/fs-extra": "^9.0.1",
"@types/mock-fs": "^4.13.0",
"@types/jest": "^26.0.7",
@@ -1,5 +1,14 @@
# @backstage/plugin-scaffolder-backend-module-rails
## 0.1.4
### Patch Changes
- Updated dependencies
- @backstage/integration@0.6.0
- @backstage/plugin-scaffolder-backend@0.15.0
- @backstage/backend-common@0.8.9
## 0.1.3
### Patch Changes
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-backend-module-rails",
"version": "0.1.3",
"version": "0.1.4",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,16 +20,16 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.8.6",
"@backstage/plugin-scaffolder-backend": "^0.14.0",
"@backstage/backend-common": "^0.8.9",
"@backstage/plugin-scaffolder-backend": "^0.15.0",
"@backstage/config": "^0.1.5",
"@backstage/errors": "^0.1.1",
"@backstage/integration": "^0.5.8",
"@backstage/integration": "^0.6.0",
"command-exists": "^1.2.9",
"fs-extra": "^9.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.7.4",
"@backstage/cli": "^0.7.8",
"@types/jest": "^26.0.7",
"@types/node": "^14.14.32",
"@types/command-exists": "^1.2.0",
+14
View File
@@ -1,5 +1,19 @@
# @backstage/plugin-scaffolder-backend
## 0.15.0
### Minor Changes
- e30646aeb: Add Bitbucket workspace and project fields to RepoUrlPicker to support Bitbucket cloud and server
### Patch Changes
- 8bedb75ae: Update Luxon dependency to 2.x
- Updated dependencies
- @backstage/integration@0.6.0
- @backstage/backend-common@0.8.9
- @backstage/plugin-scaffolder-backend-module-cookiecutter@0.1.1
## 0.14.2
### Patch Changes
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-backend",
"version": "0.14.2",
"version": "0.15.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -29,13 +29,13 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.8.8",
"@backstage/backend-common": "^0.8.9",
"@backstage/catalog-client": "^0.3.17",
"@backstage/catalog-model": "^0.9.0",
"@backstage/config": "^0.1.6",
"@backstage/errors": "^0.1.1",
"@backstage/integration": "^0.5.9",
"@backstage/plugin-scaffolder-backend-module-cookiecutter": "^0.1.0",
"@backstage/integration": "^0.6.0",
"@backstage/plugin-scaffolder-backend-module-cookiecutter": "^0.1.1",
"@gitbeaker/core": "^30.2.0",
"@gitbeaker/node": "^30.2.0",
"@octokit/rest": "^18.5.3",
@@ -57,7 +57,7 @@
"jsonschema": "^1.2.6",
"knex": "^0.95.1",
"lodash": "^4.17.21",
"luxon": "^1.26.0",
"luxon": "^2.0.2",
"morgan": "^1.10.0",
"nunjucks": "^3.2.3",
"octokit-plugin-create-pull-request": "^3.9.3",
@@ -66,8 +66,8 @@
"yaml": "^1.10.0"
},
"devDependencies": {
"@backstage/cli": "^0.7.7",
"@backstage/test-utils": "^0.1.15",
"@backstage/cli": "^0.7.8",
"@backstage/test-utils": "^0.1.17",
"@types/command-exists": "^1.2.0",
"@types/fs-extra": "^9.0.1",
"@types/git-url-parse": "^9.0.0",
@@ -0,0 +1,78 @@
apiVersion: backstage.io/v1beta2
kind: Template
metadata:
name: bitbucket-demo
title: Test Bitbucket RepoUrlPicker template
description: scaffolder v1beta2 template demo publishing to bitbucket
spec:
owner: backstage/techdocs-core
type: service
parameters:
- title: Choose a location
required:
- repoUrl
properties:
repoUrl:
title: Repository Location
type: string
ui:field: RepoUrlPicker
ui:options:
allowedHosts:
- bitbucket.org
- server.bitbucket.com
- title: Fill in some steps
required:
- name
- owner
properties:
name:
title: Name
type: string
description: Unique name of the component
ui:autofocus: true
ui:options:
rows: 5
owner:
title: Owner
type: string
description: Owner of the component
ui:field: OwnerPicker
ui:options:
allowedKinds:
- Group
steps:
- id: fetch-base
name: Fetch Base
action: fetch:cookiecutter
input:
url: ./template
values:
name: '{{ parameters.name }}'
owner: '{{ parameters.owner }}'
- id: fetch-docs
name: Fetch Docs
action: fetch:plain
input:
targetPath: ./community
url: https://github.com/backstage/community/tree/main/backstage-community-sessions
- id: publish
name: Publish
action: publish:bitbucket
input:
description: 'This is {{ parameters.name }}'
repoUrl: '{{ parameters.repoUrl }}'
- id: register
name: Register
action: catalog:register
input:
repoContentsUrl: '{{ steps.publish.output.repoContentsUrl }}'
catalogInfoPath: '/catalog-info.yaml'
output:
remoteUrl: '{{ steps.publish.output.remoteUrl }}'
entityRef: '{{ steps.register.output.entityRef }}'
@@ -0,0 +1,8 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: {{cookiecutter.name | jsonify}}
spec:
type: website
lifecycle: experimental
owner: {{cookiecutter.owner | jsonify}}
@@ -69,7 +69,13 @@ export function createGithubActionsDispatchAction(options: {
async handler(ctx) {
const { repoUrl, workflowId, branchOrTagName } = ctx.input;
const { owner, repo, host } = parseRepoUrl(repoUrl);
const { owner, repo, host } = parseRepoUrl(repoUrl, integrations);
if (!owner) {
throw new InputError(
`No owner provided for host: ${host}, and repo ${repo}`,
);
}
ctx.logger.info(
`Dispatching workflow ${workflowId} for repo ${repoUrl} on ${branchOrTagName}`,
@@ -68,21 +68,21 @@ describe('publish:azure', () => {
await expect(
action.handler({
...mockContext,
input: { repoUrl: 'azure.com?repo=bob' },
input: { repoUrl: 'dev.azure.com?repo=bob' },
}),
).rejects.toThrow(/missing owner/);
await expect(
action.handler({
...mockContext,
input: { repoUrl: 'azure.com?owner=owner' },
input: { repoUrl: 'dev.azure.com?owner=owner' },
}),
).rejects.toThrow(/missing repo/);
await expect(
action.handler({
...mockContext,
input: { repoUrl: 'azure.com?owner=owner&repo=repo' },
input: { repoUrl: 'dev.azure.com?owner=owner&repo=repo' },
}),
).rejects.toThrow(/missing organization/);
});
@@ -80,7 +80,10 @@ export function createPublishAzureAction(options: {
async handler(ctx) {
const { repoUrl, defaultBranch = 'master' } = ctx.input;
const { owner, repo, host, organization } = parseRepoUrl(repoUrl);
const { owner, repo, host, organization } = parseRepoUrl(
repoUrl,
integrations,
);
if (!organization) {
throw new InputError(
@@ -50,7 +50,7 @@ describe('publish:bitbucket', () => {
const action = createPublishBitbucketAction({ integrations, config });
const mockContext = {
input: {
repoUrl: 'bitbucket.org?repo=repo&owner=owner',
repoUrl: 'bitbucket.org?workspace=workspace&project=project&repo=repo',
repoVisibility: 'private',
},
workspacePath: 'lol',
@@ -70,14 +70,21 @@ describe('publish:bitbucket', () => {
await expect(
action.handler({
...mockContext,
input: { repoUrl: 'bitbucket.com?repo=bob' },
input: { repoUrl: 'bitbucket.org?project=project&repo=repo' },
}),
).rejects.toThrow(/missing owner/);
).rejects.toThrow(/missing workspace/);
await expect(
action.handler({
...mockContext,
input: { repoUrl: 'bitbucket.com?owner=owner' },
input: { repoUrl: 'bitbucket.org?workspace=workspace&repo=repo' },
}),
).rejects.toThrow(/missing project/);
await expect(
action.handler({
...mockContext,
input: { repoUrl: 'bitbucket.org?workspace=workspace&project=project' },
}),
).rejects.toThrow(/missing repo/);
});
@@ -86,7 +93,9 @@ describe('publish:bitbucket', () => {
await expect(
action.handler({
...mockContext,
input: { repoUrl: 'missing.com?repo=bob&owner=owner' },
input: {
repoUrl: 'missing.com?workspace=workspace&project=project&repo=repo',
},
}),
).rejects.toThrow(/No matching integration configuration/);
});
@@ -96,7 +105,8 @@ describe('publish:bitbucket', () => {
action.handler({
...mockContext,
input: {
repoUrl: 'notoken.bitbucket.com?repo=bob&owner=owner',
repoUrl:
'notoken.bitbucket.com?workspace=workspace&project=project&repo=repo',
},
}),
).rejects.toThrow(/Authorization has not been provided for Bitbucket/);
@@ -106,22 +116,26 @@ describe('publish:bitbucket', () => {
expect.assertions(2);
server.use(
rest.post(
'https://api.bitbucket.org/2.0/repositories/owner/repo',
'https://api.bitbucket.org/2.0/repositories/workspace/repo',
(req, res, ctx) => {
expect(req.headers.get('Authorization')).toBe('Bearer tokenlols');
expect(req.body).toEqual({ is_private: true, scm: 'git' });
expect(req.body).toEqual({
is_private: true,
scm: 'git',
project: { key: 'project' },
});
return res(
ctx.status(200),
ctx.set('Content-Type', 'application/json'),
ctx.json({
links: {
html: {
href: 'https://bitbucket.org/owner/repo',
href: 'https://bitbucket.org/workspace/repo',
},
clone: [
{
name: 'https',
href: 'https://bitbucket.org/owner/repo',
href: 'https://bitbucket.org/workspace/repo',
},
],
},
@@ -131,14 +145,20 @@ describe('publish:bitbucket', () => {
),
);
await action.handler(mockContext);
await action.handler({
...mockContext,
input: {
...mockContext.input,
repoUrl: 'bitbucket.org?workspace=workspace&project=project&repo=repo',
},
});
});
it('should call the correct APIs when the host is hosted bitbucket', async () => {
expect.assertions(2);
server.use(
rest.post(
'https://hosted.bitbucket.com/rest/api/1.0/projects/owner/repos',
'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos',
(req, res, ctx) => {
expect(req.headers.get('Authorization')).toBe('Bearer thing');
expect(req.body).toEqual({ public: false, name: 'repo' });
@@ -169,7 +189,7 @@ describe('publish:bitbucket', () => {
...mockContext,
input: {
...mockContext.input,
repoUrl: 'hosted.bitbucket.com?owner=owner&repo=repo',
repoUrl: 'hosted.bitbucket.com?project=project&repo=repo',
},
});
});
@@ -195,7 +215,7 @@ describe('publish:bitbucket', () => {
expect.assertions(1);
server.use(
rest.post(
'https://hosted.bitbucket.com/rest/api/1.0/projects/owner/repos',
'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos',
(_, res, ctx) => {
return res(
ctx.status(201),
@@ -205,7 +225,7 @@ describe('publish:bitbucket', () => {
},
),
rest.put(
'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/owner/repos/repo/enabled',
'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled',
(req, res, ctx) => {
expect(req.headers.get('Authorization')).toBe('Bearer thing');
return res(ctx.status(204));
@@ -217,7 +237,7 @@ describe('publish:bitbucket', () => {
...mockContext,
input: {
...mockContext.input,
repoUrl: 'hosted.bitbucket.com?owner=owner&repo=repo',
repoUrl: 'hosted.bitbucket.com?project=project&repo=repo',
enableLFS: true,
},
});
@@ -226,7 +246,7 @@ describe('publish:bitbucket', () => {
it('should report an error if enabling LFS fails', async () => {
server.use(
rest.post(
'https://hosted.bitbucket.com/rest/api/1.0/projects/owner/repos',
'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos',
(_, res, ctx) => {
return res(
ctx.status(201),
@@ -236,7 +256,7 @@ describe('publish:bitbucket', () => {
},
),
rest.put(
'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/owner/repos/repo/enabled',
'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled',
(_, res, ctx) => {
return res(ctx.status(500));
},
@@ -248,7 +268,7 @@ describe('publish:bitbucket', () => {
...mockContext,
input: {
...mockContext.input,
repoUrl: 'hosted.bitbucket.com?owner=owner&repo=repo',
repoUrl: 'hosted.bitbucket.com?project=project&repo=repo',
enableLFS: true,
},
}),
@@ -259,7 +279,7 @@ describe('publish:bitbucket', () => {
it('should call initAndPush with the correct values', async () => {
server.use(
rest.post(
'https://api.bitbucket.org/2.0/repositories/owner/repo',
'https://api.bitbucket.org/2.0/repositories/workspace/repo',
(_, res, ctx) =>
res(
ctx.status(200),
@@ -267,12 +287,12 @@ describe('publish:bitbucket', () => {
ctx.json({
links: {
html: {
href: 'https://bitbucket.org/owner/repo',
href: 'https://bitbucket.org/workspace/repo',
},
clone: [
{
name: 'https',
href: 'https://bitbucket.org/owner/cloneurl',
href: 'https://bitbucket.org/workspace/cloneurl',
},
],
},
@@ -285,7 +305,7 @@ describe('publish:bitbucket', () => {
expect(initRepoAndPush).toHaveBeenCalledWith({
dir: mockContext.workspacePath,
remoteUrl: 'https://bitbucket.org/owner/cloneurl',
remoteUrl: 'https://bitbucket.org/workspace/cloneurl',
defaultBranch: 'master',
auth: { username: 'x-token-auth', password: 'tokenlols' },
logger: mockContext.logger,
@@ -296,7 +316,7 @@ describe('publish:bitbucket', () => {
it('should call initAndPush with the correct default branch', async () => {
server.use(
rest.post(
'https://api.bitbucket.org/2.0/repositories/owner/repo',
'https://api.bitbucket.org/2.0/repositories/workspace/repo',
(_, res, ctx) =>
res(
ctx.status(200),
@@ -304,12 +324,12 @@ describe('publish:bitbucket', () => {
ctx.json({
links: {
html: {
href: 'https://bitbucket.org/owner/repo',
href: 'https://bitbucket.org/workspace/repo',
},
clone: [
{
name: 'https',
href: 'https://bitbucket.org/owner/cloneurl',
href: 'https://bitbucket.org/workspace/cloneurl',
},
],
},
@@ -328,7 +348,7 @@ describe('publish:bitbucket', () => {
expect(initRepoAndPush).toHaveBeenCalledWith({
dir: mockContext.workspacePath,
remoteUrl: 'https://bitbucket.org/owner/cloneurl',
remoteUrl: 'https://bitbucket.org/workspace/cloneurl',
defaultBranch: 'main',
auth: { username: 'x-token-auth', password: 'tokenlols' },
logger: mockContext.logger,
@@ -371,7 +391,7 @@ describe('publish:bitbucket', () => {
server.use(
rest.post(
'https://api.bitbucket.org/2.0/repositories/owner/repo',
'https://api.bitbucket.org/2.0/repositories/workspace/repo',
(_, res, ctx) =>
res(
ctx.status(200),
@@ -379,12 +399,12 @@ describe('publish:bitbucket', () => {
ctx.json({
links: {
html: {
href: 'https://bitbucket.org/owner/repo',
href: 'https://bitbucket.org/workspace/repo',
},
clone: [
{
name: 'https',
href: 'https://bitbucket.org/owner/cloneurl',
href: 'https://bitbucket.org/workspace/cloneurl',
},
],
},
@@ -397,7 +417,7 @@ describe('publish:bitbucket', () => {
expect(initRepoAndPush).toHaveBeenCalledWith({
dir: mockContext.workspacePath,
remoteUrl: 'https://bitbucket.org/owner/cloneurl',
remoteUrl: 'https://bitbucket.org/workspace/cloneurl',
auth: { username: 'x-token-auth', password: 'tokenlols' },
logger: mockContext.logger,
defaultBranch: 'master',
@@ -437,7 +457,7 @@ describe('publish:bitbucket', () => {
server.use(
rest.post(
'https://api.bitbucket.org/2.0/repositories/owner/repo',
'https://api.bitbucket.org/2.0/repositories/workspace/repo',
(_, res, ctx) =>
res(
ctx.status(200),
@@ -445,12 +465,12 @@ describe('publish:bitbucket', () => {
ctx.json({
links: {
html: {
href: 'https://bitbucket.org/owner/repo',
href: 'https://bitbucket.org/workspace/repo',
},
clone: [
{
name: 'https',
href: 'https://bitbucket.org/owner/cloneurl',
href: 'https://bitbucket.org/workspace/cloneurl',
},
],
},
@@ -463,7 +483,7 @@ describe('publish:bitbucket', () => {
expect(initRepoAndPush).toHaveBeenCalledWith({
dir: mockContext.workspacePath,
remoteUrl: 'https://bitbucket.org/owner/cloneurl',
remoteUrl: 'https://bitbucket.org/workspace/cloneurl',
auth: { username: 'x-token-auth', password: 'tokenlols' },
logger: mockContext.logger,
defaultBranch: 'master',
@@ -475,7 +495,7 @@ describe('publish:bitbucket', () => {
it('should call outputs with the correct urls', async () => {
server.use(
rest.post(
'https://api.bitbucket.org/2.0/repositories/owner/repo',
'https://api.bitbucket.org/2.0/repositories/workspace/repo',
(_, res, ctx) =>
res(
ctx.status(200),
@@ -483,12 +503,12 @@ describe('publish:bitbucket', () => {
ctx.json({
links: {
html: {
href: 'https://bitbucket.org/owner/repo',
href: 'https://bitbucket.org/workspace/repo',
},
clone: [
{
name: 'https',
href: 'https://bitbucket.org/owner/cloneurl',
href: 'https://bitbucket.org/workspace/cloneurl',
},
],
},
@@ -501,11 +521,11 @@ describe('publish:bitbucket', () => {
expect(mockContext.output).toHaveBeenCalledWith(
'remoteUrl',
'https://bitbucket.org/owner/cloneurl',
'https://bitbucket.org/workspace/cloneurl',
);
expect(mockContext.output).toHaveBeenCalledWith(
'repoContentsUrl',
'https://bitbucket.org/owner/repo/src/master',
'https://bitbucket.org/workspace/repo/src/master',
);
});
});
@@ -26,13 +26,21 @@ import { getRepoSourceDirectory, parseRepoUrl } from './util';
import { Config } from '@backstage/config';
const createBitbucketCloudRepository = async (opts: {
owner: string;
workspace: string;
project: string;
repo: string;
description: string;
repoVisibility: 'private' | 'public';
authorization: string;
}) => {
const { owner, repo, description, repoVisibility, authorization } = opts;
const {
workspace,
project,
repo,
description,
repoVisibility,
authorization,
} = opts;
const options: RequestInit = {
method: 'POST',
@@ -40,6 +48,7 @@ const createBitbucketCloudRepository = async (opts: {
scm: 'git',
description: description,
is_private: repoVisibility === 'private',
project: { key: project },
}),
headers: {
Authorization: authorization,
@@ -50,7 +59,7 @@ const createBitbucketCloudRepository = async (opts: {
let response: Response;
try {
response = await fetch(
`https://api.bitbucket.org/2.0/repositories/${owner}/${repo}`,
`https://api.bitbucket.org/2.0/repositories/${workspace}/${repo}`,
options,
);
} catch (e) {
@@ -80,7 +89,7 @@ const createBitbucketCloudRepository = async (opts: {
const createBitbucketServerRepository = async (opts: {
host: string;
owner: string;
project: string;
repo: string;
description: string;
repoVisibility: 'private' | 'public';
@@ -89,7 +98,7 @@ const createBitbucketServerRepository = async (opts: {
}) => {
const {
host,
owner,
project,
repo,
description,
authorization,
@@ -113,7 +122,7 @@ const createBitbucketServerRepository = async (opts: {
try {
const baseUrl = apiBaseUrl ? apiBaseUrl : `https://${host}/rest/api/1.0`;
response = await fetch(`${baseUrl}/projects/${owner}/repos`, options);
response = await fetch(`${baseUrl}/projects/${project}/repos`, options);
} catch (e) {
throw new Error(`Unable to create repository, ${e}`);
}
@@ -160,10 +169,10 @@ const getAuthorizationHeader = (config: BitbucketIntegrationConfig) => {
const performEnableLFS = async (opts: {
authorization: string;
host: string;
owner: string;
project: string;
repo: string;
}) => {
const { authorization, host, owner, repo } = opts;
const { authorization, host, project, repo } = opts;
const options: RequestInit = {
method: 'PUT',
@@ -173,7 +182,7 @@ const performEnableLFS = async (opts: {
};
const { ok, status, statusText } = await fetch(
`https://${host}/rest/git-lfs/admin/projects/${owner}/repos/${repo}/enabled`,
`https://${host}/rest/git-lfs/admin/projects/${project}/repos/${repo}/enabled`,
options,
);
@@ -258,7 +267,26 @@ export function createPublishBitbucketAction(options: {
enableLFS = false,
} = ctx.input;
const { owner, repo, host } = parseRepoUrl(repoUrl);
const { workspace, project, repo, host } = parseRepoUrl(
repoUrl,
integrations,
);
// Workspace is only required for bitbucket cloud
if (host === 'bitbucket.org') {
if (!workspace) {
throw new InputError(
`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing workspace`,
);
}
}
// Project is required for both bitbucket cloud and bitbucket server
if (!project) {
throw new InputError(
`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing project`,
);
}
const integrationConfig = integrations.bitbucket.byHost(host);
@@ -279,7 +307,8 @@ export function createPublishBitbucketAction(options: {
const { remoteUrl, repoContentsUrl } = await createMethod({
authorization,
host,
owner,
workspace: workspace || '',
project,
repo,
repoVisibility,
description,
@@ -311,7 +340,7 @@ export function createPublishBitbucketAction(options: {
});
if (enableLFS && host !== 'bitbucket.org') {
await performEnableLFS({ authorization, host, owner, repo });
await performEnableLFS({ authorization, host, project, repo });
}
ctx.output('remoteUrl', remoteUrl);
@@ -144,7 +144,13 @@ export function createPublishGithubAction(options: {
topics,
} = ctx.input;
const { owner, repo, host } = parseRepoUrl(repoUrl);
const { owner, repo, host } = parseRepoUrl(repoUrl, integrations);
if (!owner) {
throw new InputError(
`No owner provided for host: ${host}, and repo ${repo}`,
);
}
const credentialsProvider = credentialsProviders.get(host);
const integrationConfig = integrations.github.byHost(host);
@@ -173,7 +173,13 @@ export const createPublishGithubPullRequestAction = ({
sourcePath,
} = ctx.input;
const { owner, repo, host } = parseRepoUrl(repoUrl);
const { owner, repo, host } = parseRepoUrl(repoUrl, integrations);
if (!owner) {
throw new InputError(
`No owner provided for host: ${host}, and repo ${repo}`,
);
}
const client = await clientFactory({ integrations, host, owner, repo });
const fileRoot = sourcePath
@@ -84,7 +84,13 @@ export function createPublishGitlabAction(options: {
defaultBranch = 'master',
} = ctx.input;
const { owner, repo, host } = parseRepoUrl(repoUrl);
const { owner, repo, host } = parseRepoUrl(repoUrl, integrations);
if (!owner) {
throw new InputError(
`No owner provided for host: ${host}, and repo ${repo}`,
);
}
const integrationConfig = integrations.gitlab.byHost(host);

Some files were not shown because too many files have changed in this diff Show More