Merge branch 'backstage:master' into community-page

This commit is contained in:
Suzanne Daniels
2022-03-18 15:41:19 +01:00
committed by GitHub
55 changed files with 347 additions and 518 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog': patch
---
Corrected replacements for depreciated FilteredEntityLayout items
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-search-backend': minor
---
**BREAKING**: The `authorization` property is no longer returned on search results when queried. Note: this will only result in a breaking change if you have custom code in your frontend that relies on the `authorization.resourceRef` property on documents.
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/plugin-user-settings': patch
---
Wired up the OneLogin provider to be visible in the Settings UI when configured correctly.
Previously it wasn't visible at all.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-auth-node': minor
---
**BREAKING**: Removed the deprecated `id` and `entity` fields from `BackstageSignInResult`.
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/plugin-search-common': patch
---
- Introduce `SearchDocument` type. This type contains the subset of `IndexableDocument` properties relevant to the frontend, and is intended to be used for documents returned to the frontend from the search API.
- `SearchResultSet` is now a wrapper for documents of type `SearchDocument`, and is intended to be used in the frontend. This isn't a breaking change, since `IndexableDocument`s are valid `SearchDocument`s, so the old and new types are compatible.
- Introduce `IndexableResultSet` type, which wraps `IndexableDocument` instances in the same way as `SearchResultSet`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-auth-backend': minor
---
**BREAKING**: All sign-in resolvers must now return a `token` in their sign-in result. Returning an `id` is no longer supported.
+8
View File
@@ -0,0 +1,8 @@
---
'@backstage/plugin-search-backend': patch
'@backstage/plugin-search-backend-node': patch
'@backstage/plugin-search-backend-module-elasticsearch': patch
'@backstage/plugin-search-backend-module-pg': patch
---
Use new `IndexableResultSet` type as return type of query method in `SearchEngine` implementation.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-search': patch
---
Switch to `SearchDocument` type in `DefaultResultListItem` props
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-auth-backend': patch
---
**DEPRECATION**: The `tokenIssuer` option for `OAuthAdapter` is no longer needed and has been deprecated.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-react': patch
---
Add support for string refs to the `EntityRefLinks` component
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-components': patch
---
fix support config ref to use backstage/backstage
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes': patch
---
Fix division by zero in currentToDeclaredResourceToPerc when pod resources weren't set
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-common': patch
---
Updated the visibility of database connection fields in config to be secret
+1
View File
@@ -106,3 +106,4 @@ _If you're using Backstage in your organization, please try to add your company
| [Alliander](https://www.alliander.com/) | [@leon-vg](https://github.com/leon-vg), [@gieljl](https://github.com/gieljl), [@niekteg](https://github.com/niekteg) | Developer portal - software catalog, technical documentation, software templates, tech radar and exploration of used tools/services |
| [VIA](https://www.via.com.br) | [@vagnerguedes](https://github.com/vagnerguedes) | Centralized Developer Experience portal - Software catalog and documentation platform, software templates, techdocs, scaffolding, self-service infrastructure |
| [Surevine](https://www.surevine.com/) | [@DJDANNY123](https://github.com/djdanny123) | Developer portal for software catalog, discovery and a view of the technologies we are using across the organisation, we are looking to explore how we can enrich our entities in Backstage by integrating a software bill of materials. |
| [Bonial International GmbH](https://www.bonial.com/) | [@pjungermann](https://github.com/pjungermann) | Centralized developer portal with software catalog, tech docs, templates, and more. |
@@ -74,7 +74,7 @@ spec:
backend:
{{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }}
service:
name: {{ include "frontend.serviceName" . }}
name: {{ include "backend.serviceName" . }}
port:
number: 80
{{- else -}}
@@ -364,7 +364,7 @@ spec:
secretsKey: USER_OAUTH_TOKEN
additionalScopes:
github:
- workflow:write
- workflow
allowedHosts:
- github.com
...
+1 -1
View File
@@ -45,7 +45,7 @@ In terms of maturity and stability, the road ahead for Backstage is bright and b
You can read more about Backstages roadmap [here](https://backstage.io/docs/overview/roadmap), but in terms of maturity and stability we would like to highlight the following:
- CNCF incubation: The Backstage project [recently moved from Sandbox to Incubation](https://www.cncf.io/blog/2022/03/15/backstage-project-joins-the-cncf-incubator/) phase within the [CNCF maturity levels](https://www.cncf.io/projects/#:~:text=Maturity%20levels,should%20be%20adopting%20different%20projects.).
- Security auditing and strategy: With the CNCFs support,, the project maintainers are working on a security audit initiative to make the platform more secure and will share more details soon. The audit is part of an overall security strategy being developed this quarter.
- Security auditing and strategy: With the CNCFs support, the project maintainers are working on a security audit initiative to make the platform more secure and will share more details soon. The audit is part of an overall security strategy being developed this quarter.
- Paid plugins for advanced and enterprise use cases: Last year, [Spotify announced its work on paid plugins](https://backstage.spotify.com/blog/paid-plugins-announcement/) built on top of the Backstage open source platform. This is another example of the platforms maturity and Spotifys continued commitment to the project, with a focus on customers and use cases requiring enterprise-focused support and plugins developed and proven by Spotify.
# Some links to peruse
+4 -4
View File
@@ -60,10 +60,10 @@
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.57",
"@octokit/rest": "^18.5.3",
"@roadiehq/backstage-plugin-buildkite": "^1.4.0",
"@roadiehq/backstage-plugin-github-insights": "^1.6.0",
"@roadiehq/backstage-plugin-github-pull-requests": "^1.5.0",
"@roadiehq/backstage-plugin-travis-ci": "^1.4.0",
"@roadiehq/backstage-plugin-buildkite": "^2.0.0",
"@roadiehq/backstage-plugin-github-insights": "^2.0.0",
"@roadiehq/backstage-plugin-github-pull-requests": "^2.0.0",
"@roadiehq/backstage-plugin-travis-ci": "^2.0.0",
"history": "^5.0.0",
"prop-types": "^15.7.2",
"react": "^17.0.2",
@@ -180,7 +180,7 @@ export const cicdContent = (
<EntityJenkinsContent />
</EntitySwitch.Case>
<EntitySwitch.Case if={isBuildkiteAvailable as (e: Entity) => boolean}>
<EntitySwitch.Case if={isBuildkiteAvailable}>
<EntityBuildkiteContent />
</EntitySwitch.Case>
@@ -192,7 +192,7 @@ export const cicdContent = (
<EntityCloudbuildContent />
</EntitySwitch.Case>
<EntitySwitch.Case if={isTravisciAvailable as (e: Entity) => boolean}>
<EntitySwitch.Case if={isTravisciAvailable}>
<EntityTravisCIContent />
</EntitySwitch.Case>
@@ -327,9 +327,7 @@ const overviewContent = (
{cicdCard}
<EntitySwitch>
<EntitySwitch.Case
if={isGithubInsightsAvailable as (e: Entity) => boolean}
>
<EntitySwitch.Case if={isGithubInsightsAvailable}>
<Grid item md={6}>
<EntityGithubInsightsLanguagesCard />
<EntityGithubInsightsReleasesCard />
@@ -349,9 +347,7 @@ const overviewContent = (
</EntitySwitch>
<EntitySwitch>
<EntitySwitch.Case
if={isGithubPullRequestsAvailable as (e: Entity) => boolean}
>
<EntitySwitch.Case if={isGithubPullRequestsAvailable}>
<Grid item sm={4}>
<EntityGithubPullRequestsOverviewCard />
</Grid>
+4 -4
View File
@@ -73,7 +73,7 @@ export interface Config {
client: 'better-sqlite3' | 'sqlite3' | 'pg';
/**
* Base database connection string or Knex object
* @secret
* @visibility secret
*/
connection: string | object;
/** Database name prefix override */
@@ -109,7 +109,7 @@ export interface Config {
client?: 'better-sqlite3' | 'sqlite3' | 'pg';
/**
* Database connection string or Knex object override
* @secret
* @visibility secret
*/
connection?: string | object;
/**
@@ -138,7 +138,7 @@ export interface Config {
store: 'redis';
/**
* A redis connection string in the form `redis://user:pass@host:port`.
* @secret
* @visibility secret
*/
connection: string;
}
@@ -146,7 +146,7 @@ export interface Config {
store: 'memcache';
/**
* A memcache connection string in the form `user:pass@host:port`.
* @secret
* @visibility secret
*/
connection: string;
};
@@ -42,7 +42,7 @@ const DEFAULT_SUPPORT_CONFIG: SupportConfig = {
{
// TODO: Update to dedicated support page on backstage.io/docs
title: 'Add `app.support` config key',
url: 'https://github.com/andrewthauer/backstage/blob/master/app-config.yaml',
url: 'https://github.com/backstage/backstage/blob/master/app-config.yaml',
},
],
},
@@ -153,11 +153,11 @@ describe('ScmAuth', () => {
githubAuth.getCredentials({
url: 'http://example.com',
additionalScope: {
customScopes: { github: ['org:read', 'workflow:write'] },
customScopes: { github: ['org:read', 'workflow'] },
},
}),
).resolves.toMatchObject({
token: 'repo read:org read:user org:read workflow:write',
token: 'repo read:org read:user org:read workflow',
});
const gitlabAuth = ScmAuth.forGitlab(mockAuthApi);
@@ -50,7 +50,6 @@ describe('oauth helpers', () => {
email: 'foo@bar.com',
},
backstageIdentity: {
id: 'a',
token: 'a.b.c',
identity: {
type: 'user',
@@ -110,7 +109,6 @@ describe('oauth helpers', () => {
email: 'foo@bar.com',
},
backstageIdentity: {
id: 'a',
token: 'a.b.c',
identity: {
type: 'user',
@@ -157,7 +155,6 @@ describe('oauth helpers', () => {
displayName: "Adam l'Hôpital",
},
backstageIdentity: {
id: 'a',
token: 'a.b.c',
identity: {
type: 'user',
@@ -17,7 +17,7 @@
import express from 'express';
import { THOUSAND_DAYS_MS, TEN_MINUTES_MS, OAuthAdapter } from './OAuthAdapter';
import { encodeState } from './helpers';
import { OAuthHandlers, OAuthResponse, OAuthState } from './types';
import { OAuthHandlers, OAuthState } from './types';
const mockResponseData = {
providerInfo: {
@@ -30,18 +30,11 @@ const mockResponseData = {
email: 'foo@bar.com',
},
backstageIdentity: {
id: 'foo',
token:
'eyblob.eyJzdWIiOiJqaW1teW1hcmt1bSIsImVudCI6WyJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iXX0=.eyblob',
'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob',
},
};
function mkTokenBody(payload: unknown): string {
return Buffer.from(JSON.stringify(payload), 'utf8')
.toString('base64')
.replace(/=/g, '');
}
describe('OAuthAdapter', () => {
class MyAuthProvider implements OAuthHandlers {
async start() {
@@ -324,13 +317,11 @@ describe('OAuthAdapter', () => {
expect(mockResponse.json).toHaveBeenCalledWith({
...mockResponseData,
backstageIdentity: {
id: mockResponseData.backstageIdentity.id,
token: mockResponseData.backstageIdentity.token,
idToken: mockResponseData.backstageIdentity.token,
identity: {
ownershipEntityRefs: ['user:default/jimmymarkum'],
type: 'user',
userEntityRef: 'user:default/jimmymarkum',
ownershipEntityRefs: ['user:default/jimmymarkum'],
},
},
});
@@ -356,95 +347,6 @@ describe('OAuthAdapter', () => {
);
});
it('correctly populates incomplete identities', async () => {
const mockRefresh = jest.fn<
Promise<{ response: OAuthResponse }>,
[express.Request]
>();
const oauthProvider = new OAuthAdapter(
{
refresh: mockRefresh,
start: jest.fn(),
handler: jest.fn(),
} as OAuthHandlers,
{
...oAuthProviderOptions,
tokenIssuer: {
issueToken: async ({ claims }) => `a.${mkTokenBody(claims)}.a`,
listPublicKeys: async () => ({ keys: [] }),
},
disableRefresh: false,
isOriginAllowed: () => false,
},
);
const mockRequest = {
header: () => 'XMLHttpRequest',
cookies: {
'test-provider-refresh-token': 'token',
},
query: {},
} as unknown as express.Request;
const mockResponse = {
json: jest.fn().mockReturnThis(),
status: jest.fn().mockReturnThis(),
} as unknown as express.Response;
// Without a token
mockRefresh.mockResolvedValueOnce({
response: {
...mockResponseData,
backstageIdentity: {
id: 'foo',
token: '',
},
},
});
await oauthProvider.refresh(mockRequest, mockResponse);
expect(mockResponse.json).toHaveBeenCalledTimes(1);
expect(mockResponse.json).toHaveBeenLastCalledWith({
...mockResponseData,
backstageIdentity: {
id: 'foo',
token: `a.${mkTokenBody({ sub: 'user:default/foo' })}.a`,
idToken: `a.${mkTokenBody({ sub: 'user:default/foo' })}.a`,
identity: {
type: 'user',
userEntityRef: 'user:default/foo',
ownershipEntityRefs: [],
},
},
});
// With a token
mockRefresh.mockResolvedValueOnce({
response: {
...mockResponseData,
backstageIdentity: {
id: 'foo',
token: `z.${mkTokenBody({ sub: 'user:my-ns/foo' })}.z`,
},
},
});
await oauthProvider.refresh(mockRequest, mockResponse);
expect(mockResponse.json).toHaveBeenCalledTimes(2);
expect(mockResponse.json).toHaveBeenLastCalledWith({
...mockResponseData,
backstageIdentity: {
id: 'foo',
token: `z.${mkTokenBody({ sub: 'user:my-ns/foo' })}.z`,
idToken: `z.${mkTokenBody({ sub: 'user:my-ns/foo' })}.z`,
identity: {
type: 'user',
userEntityRef: 'user:my-ns/foo',
ownershipEntityRefs: [],
},
},
});
});
it('sets the correct cookie configuration using a callbackUrl', async () => {
const config = {
baseUrl: 'http://domain.org/auth',
@@ -17,11 +17,6 @@
import express, { CookieOptions } from 'express';
import crypto from 'crypto';
import { URL } from 'url';
import {
DEFAULT_NAMESPACE,
parseEntityRef,
stringifyEntityRef,
} from '@backstage/catalog-model';
import {
BackstageIdentityResponse,
BackstageSignInResult,
@@ -58,7 +53,8 @@ export type Options = {
cookieDomain: string;
cookiePath: string;
appOrigin: string;
tokenIssuer: TokenIssuer;
/** @deprecated This option is no longer needed */
tokenIssuer?: TokenIssuer;
isOriginAllowed: (origin: string) => boolean;
callbackUrl: string;
};
@@ -263,22 +259,11 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
if (!identity) {
return undefined;
}
if (identity.token) {
return prepareBackstageIdentityResponse(identity);
if (!identity.token) {
throw new InputError(`Identity response must return a token`);
}
const userEntityRef = stringifyEntityRef(
parseEntityRef(identity.id, {
defaultKind: 'user',
defaultNamespace: DEFAULT_NAMESPACE,
}),
);
const token = await this.options.tokenIssuer.issueToken({
claims: { sub: userEntityRef },
});
return prepareBackstageIdentityResponse({ ...identity, token });
return prepareBackstageIdentityResponse(identity);
}
private setNonceCookie = (res: express.Response, nonce: string) => {
@@ -123,9 +123,8 @@ describe('AwsAlbAuthProvider', () => {
}),
signInResolver: async () => {
return {
id: 'user.name',
token:
'eyblob.eyJzdWIiOiJqaW1teW1hcmt1bSIsImVudCI6WyJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iXX0=.eyblob',
'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob',
};
},
});
@@ -136,11 +135,8 @@ describe('AwsAlbAuthProvider', () => {
expect(mockResponse.json).toHaveBeenCalledWith({
backstageIdentity: {
id: 'user.name',
token:
'eyblob.eyJzdWIiOiJqaW1teW1hcmt1bSIsImVudCI6WyJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iXX0=.eyblob',
idToken:
'eyblob.eyJzdWIiOiJqaW1teW1hcmt1bSIsImVudCI6WyJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iXX0=.eyblob',
'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob',
identity: {
ownershipEntityRefs: ['user:default/jimmymarkum'],
type: 'user',
@@ -45,7 +45,7 @@ describe('GcpIapProvider', () => {
const iapToken = { sub: 's', email: 'e@mail.com' };
authHandler.mockResolvedValueOnce({ email: 'e@mail.com' });
signInResolver.mockResolvedValueOnce({ id: 'i', token: backstageToken });
signInResolver.mockResolvedValueOnce({ token: backstageToken });
tokenValidator.mockResolvedValueOnce(iapToken);
const app = express();
@@ -61,8 +61,6 @@ describe('GcpIapProvider', () => {
);
expect(response.body).toEqual({
backstageIdentity: {
id: 'i',
idToken: backstageToken,
token: backstageToken,
identity: {
type: 'user',
@@ -43,7 +43,7 @@ import {
describe('Oauth2ProxyAuthProvider', () => {
const mockToken =
'eyblob.eyJzdWIiOiJqaW1teW1hcmt1bSIsImVudCI6WyJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iXX0=.eyblob';
'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob';
let provider: Oauth2ProxyAuthProvider<any>;
let logger: jest.Mocked<Logger>;
@@ -122,7 +122,6 @@ describe('Oauth2ProxyAuthProvider', () => {
profile: {},
});
signInResolver.mockResolvedValue({
id: 'some-id',
token: mockToken,
});
@@ -142,7 +141,6 @@ describe('Oauth2ProxyAuthProvider', () => {
const profile = { displayName: 'some value' };
mockRequest.header.mockReturnValue(`Bearer token`);
signInResolver.mockResolvedValue({
id: 'some-id',
token: mockToken,
});
authHandler.mockResolvedValue({ profile: profile });
@@ -162,12 +160,10 @@ describe('Oauth2ProxyAuthProvider', () => {
);
expect(mockResponse.json).toHaveBeenCalledWith({
backstageIdentity: {
id: 'some-id',
idToken: mockToken,
identity: {
ownershipEntityRefs: ['user:default/jimmymarkum'],
type: 'user',
userEntityRef: 'user:default/jimmymarkum',
ownershipEntityRefs: ['user:default/jimmymarkum'],
},
token: mockToken,
},
@@ -186,7 +182,6 @@ describe('Oauth2ProxyAuthProvider', () => {
profile: {},
});
signInResolver.mockResolvedValue({
id: 'some-id',
token: mockToken,
});
});
@@ -27,13 +27,10 @@ describe('prepareBackstageIdentityResponse', () => {
const token = mkToken({ sub: 'k:ns/n', ent: ['k:ns/o'] });
expect(
prepareBackstageIdentityResponse({
id: 'x',
token,
}),
).toEqual({
id: 'x',
token,
idToken: token,
identity: {
type: 'user',
userEntityRef: 'k:ns/n',
@@ -41,67 +38,4 @@ describe('prepareBackstageIdentityResponse', () => {
},
});
});
it('populates incomplete identities', () => {
expect(
prepareBackstageIdentityResponse({
id: 'x',
token: mkToken({ sub: 'n' }),
}),
).toEqual({
id: 'x',
token: expect.any(String),
idToken: expect.any(String),
identity: {
type: 'user',
userEntityRef: 'user:default/n',
ownershipEntityRefs: [],
},
});
expect(
prepareBackstageIdentityResponse({
id: 'x',
token: mkToken({ sub: 'k:n' }),
}),
).toEqual({
id: 'x',
token: expect.any(String),
idToken: expect.any(String),
identity: {
type: 'user',
userEntityRef: 'k:default/n',
ownershipEntityRefs: [],
},
});
expect(
prepareBackstageIdentityResponse({
id: 'x',
token: mkToken({ sub: 'ns/n' }),
}),
).toEqual({
id: 'x',
token: expect.any(String),
idToken: expect.any(String),
identity: {
type: 'user',
userEntityRef: 'user:ns/n',
ownershipEntityRefs: [],
},
});
expect(
prepareBackstageIdentityResponse({
id: 'x',
token: mkToken({ sub: 'n', ent: ['k:ns/o'] }),
}),
).toEqual({
id: 'x',
token: expect.any(String),
idToken: expect.any(String),
identity: {
type: 'user',
userEntityRef: 'user:default/n',
ownershipEntityRefs: ['k:ns/o'],
},
});
});
});
@@ -14,11 +14,6 @@
* limitations under the License.
*/
import {
DEFAULT_NAMESPACE,
parseEntityRef,
stringifyEntityRef,
} from '@backstage/catalog-model';
import {
BackstageIdentityResponse,
BackstageSignInResult,
@@ -41,21 +36,11 @@ export function prepareBackstageIdentityResponse(
): BackstageIdentityResponse {
const { sub, ent } = parseJwtPayload(result.token);
const userEntityRef = stringifyEntityRef(
parseEntityRef(sub, {
defaultKind: 'user',
defaultNamespace: DEFAULT_NAMESPACE,
}),
);
return {
...{
// TODO: idToken is for backwards compatibility and can be removed in the future
idToken: result.token,
...result,
},
...result,
identity: {
type: 'user',
userEntityRef,
userEntityRef: sub,
ownershipEntityRefs: ent ?? [],
},
};
-5
View File
@@ -3,7 +3,6 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { Entity } from '@backstage/catalog-model';
import { PluginEndpointDiscovery } from '@backstage/backend-common';
// @public
@@ -13,10 +12,6 @@ export interface BackstageIdentityResponse extends BackstageSignInResult {
// @public
export interface BackstageSignInResult {
// @deprecated
entity?: Entity;
// @deprecated
id: string;
token: string;
}
-1
View File
@@ -24,7 +24,6 @@
},
"dependencies": {
"@backstage/backend-common": "^0.13.1",
"@backstage/catalog-model": "^1.0.0",
"@backstage/config": "^1.0.0",
"@backstage/errors": "^1.0.0",
"jose": "^1.27.1",
+2 -4
View File
@@ -135,12 +135,11 @@ describe('IdentityClient', () => {
const token = await factory.issueToken({ claims: { sub: 'foo' } });
const response = await client.authenticate(token);
expect(response).toEqual({
id: 'foo',
token: token,
identity: {
ownershipEntityRefs: [],
type: 'user',
userEntityRef: 'foo',
ownershipEntityRefs: [],
},
});
});
@@ -202,12 +201,11 @@ describe('IdentityClient', () => {
const token = await factory.issueToken({ claims: { sub: 'foo' } });
const response = await client.authenticate(token);
expect(response).toEqual({
id: 'foo',
token: token,
identity: {
ownershipEntityRefs: [],
type: 'user',
userEntityRef: 'foo',
ownershipEntityRefs: [],
},
});
});
-1
View File
@@ -87,7 +87,6 @@ export class IdentityClient {
}
const user: BackstageIdentityResponse = {
id: decoded.sub,
token,
identity: {
type: 'user',
-21
View File
@@ -14,8 +14,6 @@
* limitations under the License.
*/
import { Entity } from '@backstage/catalog-model';
/**
* A representation of a successful Backstage sign-in.
*
@@ -25,25 +23,6 @@ import { Entity } from '@backstage/catalog-model';
* @public
*/
export interface BackstageSignInResult {
/**
* An opaque ID that uniquely identifies the user within Backstage.
*
* This is typically the same as the user entity `metadata.name`.
*
* @deprecated Use the `identity` field instead
*/
id: string;
/**
* The entity that the user is represented by within Backstage.
*
* This entity may or may not exist within the Catalog, and it can be used
* to read and store additional metadata about the user.
*
* @deprecated Use the `identity` field instead.
*/
entity?: Entity;
/**
* The token used to authenticate the user within Backstage.
*/
+2 -6
View File
@@ -261,15 +261,11 @@ export type EntityRefLinkProps = {
} & Omit<LinkProps, 'to'>;
// @public
export const EntityRefLinks: ({
entityRefs,
defaultKind,
...linkProps
}: EntityRefLinksProps) => JSX.Element;
export function EntityRefLinks(props: EntityRefLinksProps): JSX.Element;
// @public
export type EntityRefLinksProps = {
entityRefs: (Entity | CompoundEntityRef)[];
entityRefs: (string | Entity | CompoundEntityRef)[];
defaultKind?: string;
} & Omit<LinkProps, 'to'>;
@@ -25,7 +25,7 @@ import { LinkProps } from '@backstage/core-components';
* @public
*/
export type EntityRefLinksProps = {
entityRefs: (Entity | CompoundEntityRef)[];
entityRefs: (string | Entity | CompoundEntityRef)[];
defaultKind?: string;
} & Omit<LinkProps, 'to'>;
@@ -34,17 +34,20 @@ export type EntityRefLinksProps = {
*
* @public
*/
export const EntityRefLinks = ({
entityRefs,
defaultKind,
...linkProps
}: EntityRefLinksProps) => (
<>
{entityRefs.map((r, i) => (
<React.Fragment key={i}>
{i > 0 && ', '}
<EntityRefLink {...linkProps} entityRef={r} defaultKind={defaultKind} />
</React.Fragment>
))}
</>
);
export function EntityRefLinks(props: EntityRefLinksProps) {
const { entityRefs, defaultKind, ...linkProps } = props;
return (
<>
{entityRefs.map((r, i) => (
<React.Fragment key={i}>
{i > 0 && ', '}
<EntityRefLink
{...linkProps}
entityRef={r}
defaultKind={defaultKind}
/>
</React.Fragment>
))}
</>
);
}
@@ -18,7 +18,7 @@ import { CatalogFilterLayout } from '@backstage/plugin-catalog-react';
/**
* @public
* @deprecated Use `FilteredCatalogLayout` from `@backstage/plugin-catalog-react` instead.
* @deprecated Use `CatalogFilterLayout` from `@backstage/plugin-catalog-react` instead.
*/
export const FilteredEntityLayout = CatalogFilterLayout as (props: {
children: React.ReactNode;
@@ -26,12 +26,12 @@ export const FilteredEntityLayout = CatalogFilterLayout as (props: {
/**
* @public
* @deprecated Use `FilteredCatalogLayout.Filters` from `@backstage/plugin-catalog-react` instead.
* @deprecated Use `CatalogFilterLayout.Filters` from `@backstage/plugin-catalog-react` instead.
*/
export const FilterContainer = CatalogFilterLayout.Filters;
/**
* @public
* @deprecated Use `FilteredCatalogLayout.Content` from `@backstage/plugin-catalog-react` instead.
* @deprecated Use `CatalogFilterLayout.Content` from `@backstage/plugin-catalog-react` instead.
*/
export const EntityListContainer = CatalogFilterLayout.Content;
+2
View File
@@ -118,6 +118,8 @@ export const currentToDeclaredResourceToPerc = (
current: number | string,
resource: number | string,
): string => {
if (Number(resource) === 0) return `0%`;
if (typeof current === 'number' && typeof resource === 'number') {
return `${Math.round((current / resource) * 100)}%`;
}
@@ -138,7 +138,7 @@ describe('RepoUrlPicker', () => {
'ui:options': {
requestUserCredentials: {
secretsKey: 'testKey',
additionalScopes: { github: ['workflow:write'] },
additionalScopes: { github: ['workflow'] },
},
},
}}
@@ -164,7 +164,7 @@ describe('RepoUrlPicker', () => {
additionalScope: {
repoWrite: true,
customScopes: {
github: ['workflow:write'],
github: ['workflow'],
},
},
});
@@ -10,10 +10,10 @@ import { Client } from '@elastic/elasticsearch';
import { Config } from '@backstage/config';
import type { ConnectionOptions } from 'tls';
import { IndexableDocument } from '@backstage/plugin-search-common';
import { IndexableResultSet } from '@backstage/plugin-search-common';
import { Logger } from 'winston';
import { SearchEngine } from '@backstage/plugin-search-common';
import { SearchQuery } from '@backstage/plugin-search-common';
import { SearchResultSet } from '@backstage/plugin-search-common';
// Warning: (ae-missing-release-tag) "ElasticSearchClientOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/plugin-search-backend-module-elasticsearch" does not have an export "ElasticSearchEngine"
@@ -119,7 +119,7 @@ export class ElasticSearchSearchEngine implements SearchEngine {
getIndexer(type: string): Promise<ElasticSearchSearchEngineIndexer>;
newClient<T>(create: (options: ElasticSearchClientOptions) => T): T;
// (undocumented)
query(query: SearchQuery): Promise<SearchResultSet>;
query(query: SearchQuery): Promise<IndexableResultSet>;
// Warning: (ae-forgotten-export) The symbol "ElasticSearchQueryTranslator" needs to be exported by the entry point index.d.ts
//
// (undocumented)
@@ -21,9 +21,9 @@ import {
import { Config } from '@backstage/config';
import {
IndexableDocument,
IndexableResultSet,
SearchEngine,
SearchQuery,
SearchResultSet,
} from '@backstage/plugin-search-common';
import { Client } from '@elastic/elasticsearch';
import esb from 'elastic-builder';
@@ -192,7 +192,7 @@ export class ElasticSearchSearchEngine implements SearchEngine {
return indexer;
}
async query(query: SearchQuery): Promise<SearchResultSet> {
async query(query: SearchQuery): Promise<IndexableResultSet> {
const { elasticSearchQuery, documentTypes, pageSize } =
this.translator(query);
const queryIndices = documentTypes
@@ -5,11 +5,11 @@
```ts
import { BatchSearchEngineIndexer } from '@backstage/plugin-search-backend-node';
import { IndexableDocument } from '@backstage/plugin-search-common';
import { IndexableResultSet } from '@backstage/plugin-search-common';
import { Knex } from 'knex';
import { PluginDatabaseManager } from '@backstage/backend-common';
import { SearchEngine } from '@backstage/plugin-search-backend-node';
import { SearchQuery } from '@backstage/plugin-search-common';
import { SearchResultSet } from '@backstage/plugin-search-common';
// Warning: (ae-missing-release-tag) "ConcretePgSearchQuery" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -88,7 +88,7 @@ export class PgSearchEngine implements SearchEngine {
// (undocumented)
getIndexer(type: string): Promise<PgSearchEngineIndexer>;
// (undocumented)
query(query: SearchQuery): Promise<SearchResultSet>;
query(query: SearchQuery): Promise<IndexableResultSet>;
// (undocumented)
setTranslator(
translator: (query: SearchQuery) => ConcretePgSearchQuery,
@@ -15,7 +15,10 @@
*/
import { PluginDatabaseManager } from '@backstage/backend-common';
import { SearchEngine } from '@backstage/plugin-search-backend-node';
import { SearchQuery, SearchResultSet } from '@backstage/plugin-search-common';
import {
SearchQuery,
IndexableResultSet,
} from '@backstage/plugin-search-common';
import { PgSearchEngineIndexer } from './PgSearchEngineIndexer';
import {
DatabaseDocumentStore,
@@ -81,7 +84,7 @@ export class PgSearchEngine implements SearchEngine {
});
}
async query(query: SearchQuery): Promise<SearchResultSet> {
async query(query: SearchQuery): Promise<IndexableResultSet> {
const { pgQuery, pageSize } = this.translator(query);
const rows = await this.databaseStore.transaction(async tx =>
+2 -2
View File
@@ -9,13 +9,13 @@ import { DocumentCollatorFactory } from '@backstage/plugin-search-common';
import { DocumentDecoratorFactory } from '@backstage/plugin-search-common';
import { DocumentTypeInfo } from '@backstage/plugin-search-common';
import { IndexableDocument } from '@backstage/plugin-search-common';
import { IndexableResultSet } from '@backstage/plugin-search-common';
import { Logger } from 'winston';
import { default as lunr_2 } from 'lunr';
import { QueryTranslator } from '@backstage/plugin-search-common';
import { Readable } from 'stream';
import { SearchEngine } from '@backstage/plugin-search-common';
import { SearchQuery } from '@backstage/plugin-search-common';
import { SearchResultSet } from '@backstage/plugin-search-common';
import { Transform } from 'stream';
import { Writable } from 'stream';
@@ -87,7 +87,7 @@ export class LunrSearchEngine implements SearchEngine {
// (undocumented)
protected lunrIndices: Record<string, lunr_2.Index>;
// (undocumented)
query(query: SearchQuery): Promise<SearchResultSet>;
query(query: SearchQuery): Promise<IndexableResultSet>;
// (undocumented)
setTranslator(translator: LunrQueryTranslator): void;
// (undocumented)
@@ -16,8 +16,8 @@
import {
IndexableDocument,
IndexableResultSet,
SearchQuery,
SearchResultSet,
QueryTranslator,
SearchEngine,
} from '@backstage/plugin-search-common';
@@ -147,7 +147,7 @@ export class LunrSearchEngine implements SearchEngine {
return indexer;
}
async query(query: SearchQuery): Promise<SearchResultSet> {
async query(query: SearchQuery): Promise<IndexableResultSet> {
const { lunrQueryBuilder, documentTypes, pageSize } = this.translator(
query,
) as ConcreteLunrQuery;
@@ -196,8 +196,8 @@ export class LunrSearchEngine implements SearchEngine {
? encodePageCursor({ page: page - 1 })
: undefined;
// Translate results into SearchResultSet
const realResultSet: SearchResultSet = {
// Translate results into IndexableResultSet
const realResultSet: IndexableResultSet = {
results: results.slice(offset, offset + pageSize).map(d => {
return { type: d.type, document: this.docStore[d.result.ref] };
}),
@@ -25,12 +25,12 @@ import {
} from '@backstage/plugin-permission-common';
import {
DocumentTypeInfo,
IndexableResult,
IndexableResultSet,
QueryRequestOptions,
QueryTranslator,
SearchEngine,
SearchQuery,
SearchResult,
SearchResultSet,
} from '@backstage/plugin-search-common';
import { Config } from '@backstage/config';
import { InputError } from '@backstage/errors';
@@ -85,7 +85,7 @@ export class AuthorizedSearchEngine implements SearchEngine {
async query(
query: SearchQuery,
options: QueryRequestOptions,
): Promise<SearchResultSet> {
): Promise<IndexableResultSet> {
const queryStartTime = Date.now();
const authorizer = new DataLoader(
@@ -144,7 +144,7 @@ export class AuthorizedSearchEngine implements SearchEngine {
const { page } = decodePageCursor(query.pageCursor);
const targetResults = (page + 1) * this.pageSize;
let filteredResults: SearchResult[] = [];
let filteredResults: IndexableResult[] = [];
let nextPageCursor: string | undefined;
let latencyBudgetExhausted = false;
@@ -183,7 +183,7 @@ export class AuthorizedSearchEngine implements SearchEngine {
}
private async filterResults(
results: SearchResult[],
results: IndexableResult[],
typeDecisions: Record<string, AuthorizeDecision>,
authorizer: DataLoader<AuthorizeQuery, AuthorizeDecision>,
) {
@@ -19,7 +19,6 @@ import { ConfigReader } from '@backstage/config';
import { PermissionAuthorizer } from '@backstage/plugin-permission-common';
import {
IndexBuilder,
LunrSearchEngine,
SearchEngine,
} from '@backstage/plugin-search-backend-node';
import express from 'express';
@@ -39,8 +38,19 @@ describe('createRouter', () => {
beforeAll(async () => {
const logger = getVoidLogger();
const searchEngine = new LunrSearchEngine({ logger });
const indexBuilder = new IndexBuilder({ logger, searchEngine });
mockSearchEngine = {
getIndexer: jest.fn(),
setTranslator: jest.fn(),
query: jest.fn().mockResolvedValue({
results: [],
nextPageCursor: '',
previousPageCursor: '',
}),
};
const indexBuilder = new IndexBuilder({
logger,
searchEngine: mockSearchEngine,
});
const router = await createRouter({
engine: indexBuilder.getSearchEngine(),
@@ -56,7 +66,7 @@ describe('createRouter', () => {
});
beforeEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
});
describe('GET /query', () => {
@@ -101,6 +111,42 @@ describe('createRouter', () => {
});
});
it('removes backend-only properties from search documents', async () => {
mockSearchEngine.query.mockResolvedValue({
results: [
{
type: 'software-catalog',
document: {
text: 'foo',
title: 'bar baz',
location: '/catalog/default/component/example',
authorization: {
resourceRef: 'component:default/example',
},
},
},
],
nextPageCursor: '',
previousPageCursor: '',
});
const response = await request(app).get('/query');
expect(response.status).toEqual(200);
expect(response.body).toMatchObject({
results: [
{
type: 'software-catalog',
document: {
text: 'foo',
title: 'bar baz',
location: '/catalog/default/component/example',
},
},
],
});
});
describe('search result filtering', () => {
beforeAll(async () => {
const logger = getVoidLogger();
+13 -1
View File
@@ -26,6 +26,7 @@ import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-no
import { PermissionAuthorizer } from '@backstage/plugin-permission-common';
import {
DocumentTypeInfo,
IndexableResultSet,
SearchResultSet,
} from '@backstage/plugin-search-common';
import { SearchEngine } from '@backstage/plugin-search-backend-node';
@@ -89,6 +90,17 @@ export async function createRouter(
}),
});
const toSearchResults = (resultSet: IndexableResultSet): SearchResultSet => ({
...resultSet,
results: resultSet.results.map(result => ({
...result,
document: {
...result.document,
authorization: undefined,
},
})),
});
const router = Router();
router.get(
'/query',
@@ -116,7 +128,7 @@ export async function createRouter(
try {
const resultSet = await engine?.query(query, { token });
res.send(filterResultSet(resultSet));
res.send(filterResultSet(toSearchResults(resultSet)));
} catch (err) {
throw new Error(
`There was a problem performing the search query. ${err}`,
+36 -20
View File
@@ -30,14 +30,17 @@ export type DocumentTypeInfo = {
};
// @beta
export interface IndexableDocument {
export type IndexableDocument = SearchDocument & {
authorization?: {
resourceRef: string;
};
location: string;
text: string;
title: string;
}
};
// @beta (undocumented)
export type IndexableResult = Result<IndexableDocument>;
// @beta (undocumented)
export type IndexableResultSet = ResultSet<IndexableDocument>;
// @beta
export type QueryRequestOptions = {
@@ -47,13 +50,38 @@ export type QueryRequestOptions = {
// @beta
export type QueryTranslator = (query: SearchQuery) => unknown;
// @beta (undocumented)
export interface Result<TDocument extends SearchDocument> {
// (undocumented)
document: TDocument;
// (undocumented)
type: string;
}
// @beta (undocumented)
export interface ResultSet<TDocument extends SearchDocument> {
// (undocumented)
nextPageCursor?: string;
// (undocumented)
previousPageCursor?: string;
// (undocumented)
results: Result<TDocument>[];
}
// @beta
export interface SearchDocument {
location: string;
text: string;
title: string;
}
// @beta
export interface SearchEngine {
getIndexer(type: string): Promise<Writable>;
query(
query: SearchQuery,
options?: QueryRequestOptions,
): Promise<SearchResultSet>;
): Promise<IndexableResultSet>;
setTranslator(translator: QueryTranslator): void;
}
@@ -70,20 +98,8 @@ export interface SearchQuery {
}
// @beta (undocumented)
export interface SearchResult {
// (undocumented)
document: IndexableDocument;
// (undocumented)
type: string;
}
export type SearchResult = Result<SearchDocument>;
// @beta (undocumented)
export interface SearchResultSet {
// (undocumented)
nextPageCursor?: string;
// (undocumented)
previousPageCursor?: string;
// (undocumented)
results: SearchResult[];
}
export type SearchResultSet = ResultSet<SearchDocument>;
```
+37 -9
View File
@@ -31,26 +31,45 @@ export interface SearchQuery {
/**
* @beta
*/
export interface SearchResult {
export interface Result<TDocument extends SearchDocument> {
type: string;
document: IndexableDocument;
document: TDocument;
}
/**
* @beta
*/
export interface SearchResultSet {
results: SearchResult[];
export interface ResultSet<TDocument extends SearchDocument> {
results: Result<TDocument>[];
nextPageCursor?: string;
previousPageCursor?: string;
}
/**
* Base properties that all indexed documents must include, as well as some
* common properties that documents are encouraged to use where appropriate.
* @beta
*/
export interface IndexableDocument {
export type SearchResult = Result<SearchDocument>;
/**
* @beta
*/
export type SearchResultSet = ResultSet<SearchDocument>;
/**
* @beta
*/
export type IndexableResult = Result<IndexableDocument>;
/**
* @beta
*/
export type IndexableResultSet = ResultSet<IndexableDocument>;
/**
* Base properties that all search documents must include.
* @beta
*/
export interface SearchDocument {
/**
* The primary name of the document (e.g. name, title, identifier, etc).
*/
@@ -66,7 +85,16 @@ export interface IndexableDocument {
* is clicked).
*/
location: string;
}
/**
* Properties related to indexing of documents. This type is only useful for
* backends working directly with documents being inserted or retrieved from
* search indexes. When dealing with documents in the frontend, use
* {@link SearchDocument}.
* @beta
*/
export type IndexableDocument = SearchDocument & {
/**
* Optional authorization information to be used when determining whether this
* search result should be visible to a given user.
@@ -77,7 +105,7 @@ export interface IndexableDocument {
*/
resourceRef: string;
};
}
};
/**
* Information about a specific document type. Intended to be used in the
@@ -178,5 +206,5 @@ export interface SearchEngine {
query(
query: SearchQuery,
options?: QueryRequestOptions,
): Promise<SearchResultSet>;
): Promise<IndexableResultSet>;
}
+2 -2
View File
@@ -9,13 +9,13 @@ import { ApiRef } from '@backstage/core-plugin-api';
import { AsyncState } from 'react-use/lib/useAsync';
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { IconComponent } from '@backstage/core-plugin-api';
import { IndexableDocument } from '@backstage/plugin-search-common';
import { InputBaseProps } from '@material-ui/core';
import { JsonObject } from '@backstage/types';
import { default as React_2 } from 'react';
import { ReactElement } from 'react';
import { ReactNode } from 'react';
import { RouteRef } from '@backstage/core-plugin-api';
import { SearchDocument } from '@backstage/plugin-search-common';
import { SearchQuery } from '@backstage/plugin-search-common';
import { SearchResult as SearchResult_2 } from '@backstage/plugin-search-common';
import { SearchResultSet } from '@backstage/plugin-search-common';
@@ -31,7 +31,7 @@ export const DefaultResultListItem: ({
}: {
icon?: ReactNode;
secondaryAction?: ReactNode;
result: IndexableDocument;
result: SearchDocument;
lineClamp?: number | undefined;
}) => JSX.Element;
@@ -15,7 +15,7 @@
*/
import React, { ReactNode } from 'react';
import { IndexableDocument } from '@backstage/plugin-search-common';
import { SearchDocument } from '@backstage/plugin-search-common';
import {
ListItem,
ListItemIcon,
@@ -29,7 +29,7 @@ import TextTruncate from 'react-text-truncate';
type Props = {
icon?: ReactNode;
secondaryAction?: ReactNode;
result: IndexableDocument;
result: SearchDocument;
lineClamp?: number;
};
@@ -24,6 +24,7 @@ import {
microsoftAuthApiRef,
bitbucketAuthApiRef,
atlassianAuthApiRef,
oneloginAuthApiRef,
} from '@backstage/core-plugin-api';
type Props = {
@@ -80,6 +81,14 @@ export const DefaultProviderSettings = ({ configuredProviders }: Props) => (
icon={Star}
/>
)}
{configuredProviders.includes('onelogin') && (
<ProviderSettingsItem
title="OneLogin"
description="Provides authentication towards OneLogin APIs"
apiRef={oneloginAuthApiRef}
icon={Star}
/>
)}
{configuredProviders.includes('atlassian') && (
<ProviderSettingsItem
title="Atlassian"
+41 -158
View File
@@ -1358,123 +1358,6 @@
"@babel/helper-validator-identifier" "^7.16.7"
to-fast-properties "^2.0.0"
"@backstage/catalog-client@^0.9.0":
version "0.9.0"
resolved "https://registry.npmjs.org/@backstage/catalog-client/-/catalog-client-0.9.0.tgz#3e1024fab13fd8e2000d33833d2463ea9be5df9d"
integrity sha512-mr2wrb+7uAPczOOYt07C4AbXIeX0mu+Iw6RPDNg67GvPvQVcbKCrjth9VEj8rScFGmJAn66ZcronbcZSS0MJww==
dependencies:
"@backstage/catalog-model" "^0.13.0"
"@backstage/errors" "^0.2.2"
cross-fetch "^3.1.5"
"@backstage/catalog-model@^0.13.0":
version "0.13.0"
resolved "https://registry.npmjs.org/@backstage/catalog-model/-/catalog-model-0.13.0.tgz#abeb91522ac7ef7907907ad5bc889803131db209"
integrity sha512-Q+0C0LJ8niNQ4TXduUNTycZ2PLMbhKmfgWywunJLO34DZAoFnTWJEAdEYF536YyLciHhLO0dHRUaHY29Sdr+Xg==
dependencies:
"@backstage/config" "^0.1.15"
"@backstage/errors" "^0.2.2"
"@backstage/types" "^0.1.3"
ajv "^7.0.3"
json-schema "^0.4.0"
lodash "^4.17.21"
uuid "^8.0.0"
"@backstage/config@^0.1.15":
version "0.1.15"
resolved "https://registry.npmjs.org/@backstage/config/-/config-0.1.15.tgz#4bad122ad861be5bd61a60639f92d2494fa245c5"
integrity sha512-eNJEYYSEu9MkrkBYiMpUBWEc3Bu64YgB9pZZGCMW7/9350tV2wbylEdoBJHslilJlJhiUyTXBckn8Ua7DOH7rw==
dependencies:
"@backstage/types" "^0.1.3"
lodash "^4.17.21"
"@backstage/core-plugin-api@^0.8.0":
version "0.8.0"
resolved "https://registry.npmjs.org/@backstage/core-plugin-api/-/core-plugin-api-0.8.0.tgz#e2096bff679183168a7f9b47ed27c50a01970e32"
integrity sha512-hHTrh0lui7TAUnLdqyvNznBuoh3PNEwHnlWCcnp2wfvLc7syG5bjnzorATknCixIiWsPfod5qtSSNeO4hQoFVQ==
dependencies:
"@backstage/config" "^0.1.15"
"@backstage/types" "^0.1.3"
"@backstage/version-bridge" "^0.1.2"
history "^5.0.0"
prop-types "^15.7.2"
react-router-dom "6.0.0-beta.0"
zen-observable "^0.8.15"
"@backstage/errors@^0.2.2":
version "0.2.2"
resolved "https://registry.npmjs.org/@backstage/errors/-/errors-0.2.2.tgz#2113e0bc859e645b8b59bfcb435f7535739b02f8"
integrity sha512-s6Ru3NL4oFiHwNsMY1uiwqNKuOu9KQWkpse3adWZsDu1fCQKOw2omQ2qBHZ4Uj1xJ6x9Ye0yNTJ4vfHfdwiaEg==
dependencies:
"@backstage/types" "^0.1.3"
cross-fetch "^3.1.5"
serialize-error "^8.0.1"
"@backstage/integration-react@^0.1.10":
version "0.1.25"
resolved "https://registry.npmjs.org/@backstage/integration-react/-/integration-react-0.1.25.tgz#ebbdd30d66e1d210b7cd33a682ad2be0d5ea5fc0"
integrity sha512-WI0UuXcX3m1OotfvqCVtQgyish9Gk+YkLVyrwWbzHFZ0isbT2I2i6VD/iau1X55oZpaubxLZ5CzYzA4QcAdm4w==
dependencies:
"@backstage/config" "^0.1.15"
"@backstage/core-components" "^0.9.1"
"@backstage/core-plugin-api" "^0.8.0"
"@backstage/integration" "^0.8.0"
"@backstage/theme" "^0.2.15"
"@material-ui/core" "^4.12.2"
"@material-ui/icons" "^4.9.1"
"@material-ui/lab" "4.0.0-alpha.57"
react-use "^17.2.4"
"@backstage/integration@^0.8.0":
version "0.8.0"
resolved "https://registry.npmjs.org/@backstage/integration/-/integration-0.8.0.tgz#d74131ad347272b4935973aa4bd098fad9548ce6"
integrity sha512-GfNJRdcU7NMFjVqUSJOFRRrqEjKRGmgsL+s3TDtqmqqji7XPttTKD43+Ou50hRf0dkIETe5m1XHr1Fd5ksMt9A==
dependencies:
"@backstage/config" "^0.1.15"
"@octokit/auth-app" "^3.4.0"
"@octokit/rest" "^18.5.3"
cross-fetch "^3.1.5"
git-url-parse "^11.6.0"
lodash "^4.17.21"
luxon "^2.0.2"
"@backstage/plugin-catalog-react@^0.9.0":
version "0.9.0"
resolved "https://registry.npmjs.org/@backstage/plugin-catalog-react/-/plugin-catalog-react-0.9.0.tgz#ff8c09ec455655fadb2c2fafd578908138b858bc"
integrity sha512-qSiz/q7B/IlkHkx3Tcrbokm+jIOPMGjSzxdnKWv5c6KcqF+XLiVr6TavLWcAjaxk81skF8N3RCDG68tA1lL0HA==
dependencies:
"@backstage/catalog-client" "^0.9.0"
"@backstage/catalog-model" "^0.13.0"
"@backstage/core-components" "^0.9.1"
"@backstage/core-plugin-api" "^0.8.0"
"@backstage/errors" "^0.2.2"
"@backstage/integration" "^0.8.0"
"@backstage/plugin-permission-common" "^0.5.2"
"@backstage/plugin-permission-react" "^0.3.3"
"@backstage/types" "^0.1.3"
"@backstage/version-bridge" "^0.1.2"
"@material-ui/core" "^4.12.2"
"@material-ui/icons" "^4.9.1"
"@material-ui/lab" "4.0.0-alpha.57"
classnames "^2.2.6"
jwt-decode "^3.1.0"
lodash "^4.17.21"
qs "^6.9.4"
react-router "6.0.0-beta.0"
react-use "^17.2.4"
yaml "^1.10.0"
zen-observable "^0.8.15"
"@backstage/types@^0.1.3":
version "0.1.3"
resolved "https://registry.npmjs.org/@backstage/types/-/types-0.1.3.tgz#6613d8cbdf97d42d31cd1e66a833df533e7ccf14"
integrity sha512-fJVi4oVrlO+G3PRv1fYSll9/X4pE11HLnkI//Geare9sP6wSfp/2zXpLYfKVsG0e24jOl7Swkc8lwLkQ90zMaQ==
"@backstage/version-bridge@^0.1.2":
version "0.1.2"
resolved "https://registry.npmjs.org/@backstage/version-bridge/-/version-bridge-0.1.2.tgz#a24f42e0f383d497576f8c9d43851c6538345c03"
integrity sha512-hwThlwfVCXaHva6IBakbHlWt3c1ev3KIwukpBlU3tDgi6tTvG8tPDe2aRwz7zzPHZRtgEw6JPvjA9gn9vSPbzQ==
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@@ -2111,9 +1994,9 @@
xcase "^2.0.1"
"@google-cloud/common@^3.7.0":
version "3.7.0"
resolved "https://registry.npmjs.org/@google-cloud/common/-/common-3.7.0.tgz#ee3fba75aeaa614978aebf8740380670026592aa"
integrity sha512-oFgpKLjH9JTOAyQd3kB36iSuH8wNSpDKb1TywlB6zcsG0xmJFxLutmfPhz03KUxRMNQOZ1K1Gc9BYvJifVnGVA==
version "3.10.0"
resolved "https://registry.npmjs.org/@google-cloud/common/-/common-3.10.0.tgz#454d1155bb512109cd83c6183aabbd39f9aabda7"
integrity sha512-XMbJYMh/ZSaZnbnrrOFfR/oQrb0SxG4qh6hDisWCoEbFcBHV0qHQo4uXfeMCzolx2Mfkh6VDaOGg+hyJsmxrlw==
dependencies:
"@google-cloud/projectify" "^2.0.0"
"@google-cloud/promisify" "^2.0.0"
@@ -2121,7 +2004,7 @@
duplexify "^4.1.1"
ent "^2.2.0"
extend "^3.0.2"
google-auth-library "^7.0.2"
google-auth-library "^7.14.0"
retry-request "^4.2.2"
teeny-request "^7.0.0"
@@ -4972,15 +4855,15 @@
resolved "https://registry.npmjs.org/@rjsf/material-ui/-/material-ui-3.2.1.tgz#84fbf322485aee3a84101e189161f0687779ec8d"
integrity sha512-8UiDeDbjCImFSfOegGu13otQ7OdP9FOYpcLjeouppnhs+MPeIEAtYS+jCcBKmi3reyTagC15/KVSRhde1wS1vg==
"@roadiehq/backstage-plugin-buildkite@^1.4.0":
version "1.4.0"
resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-buildkite/-/backstage-plugin-buildkite-1.4.0.tgz#f268ba2b6f43f68158af379d55fc7e19842c1b43"
integrity sha512-bG16kyn5e/QzceGy7PXs/wb09/p7W1O+kev7V/PGECHlxgj/Gg575maFsyZU/bIGTQM3cUavwvj0pzfdXjFD3Q==
"@roadiehq/backstage-plugin-buildkite@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-buildkite/-/backstage-plugin-buildkite-2.0.0.tgz#e5da049a2e15c533228e26da1af537b3783fe5de"
integrity sha512-qurSWt+n505BXFBTzoTSXn9BPmhtLjt+SPiVYjD3KSqJ7hwOoJg3rr1wuCWigUDj1R0Kx9TgaxNscETGBzUUgQ==
dependencies:
"@backstage/catalog-model" "^0.13.0"
"@backstage/catalog-model" "^1.0.0"
"@backstage/core-components" "^0.9.0"
"@backstage/core-plugin-api" "^0.8.0"
"@backstage/plugin-catalog-react" "^0.9.0"
"@backstage/core-plugin-api" "^1.0.0"
"@backstage/plugin-catalog-react" "^1.0.0"
"@backstage/theme" "^0.2.6"
"@material-ui/core" "^4.12.1"
"@material-ui/icons" "^4.11.2"
@@ -4991,16 +4874,16 @@
react-router-dom "6.0.0-beta.0"
react-use "^17.2.4"
"@roadiehq/backstage-plugin-github-insights@^1.6.0":
version "1.6.0"
resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-insights/-/backstage-plugin-github-insights-1.6.0.tgz#0ccd63ff08f720f09d0076ba3210ef2373bb62b2"
integrity sha512-TV7hrxxhJy0yN+jERVKALYW36nwvNWCpCOcBGgoBepbFCOhiUgc3g9EjRp0b0z+uUSywFkEPUYH1B1CXGzDLdw==
"@roadiehq/backstage-plugin-github-insights@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-insights/-/backstage-plugin-github-insights-2.0.0.tgz#1137f4f3ad5744559bf095ee868371f5fd8c60ec"
integrity sha512-6rLMWq8F6hYNAkuF+gOKyAZ/A2MGVHuNJArBJCUVhw6b/tmSxQcj78y62GaXB1Zz9KERhSk8OZf94m0+EE8C5g==
dependencies:
"@backstage/catalog-model" "^0.13.0"
"@backstage/catalog-model" "^1.0.0"
"@backstage/core-components" "^0.9.0"
"@backstage/core-plugin-api" "^0.8.0"
"@backstage/integration-react" "^0.1.10"
"@backstage/plugin-catalog-react" "^0.9.0"
"@backstage/core-plugin-api" "^1.0.0"
"@backstage/integration-react" "^1.0.0"
"@backstage/plugin-catalog-react" "^1.0.0"
"@backstage/theme" "^0.2.7"
"@date-io/core" "2.10.7"
"@material-ui/core" "^4.11.0"
@@ -5015,15 +4898,15 @@
react-use "^17.2.4"
zustand "3.6.9"
"@roadiehq/backstage-plugin-github-pull-requests@^1.5.0":
version "1.5.0"
resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-pull-requests/-/backstage-plugin-github-pull-requests-1.5.0.tgz#30266e037831c11842cd8e1456008fede2c6b580"
integrity sha512-jlNYV/RC0HVwkp8lCmPAhps6KpRP4OuTrsrIXt557EHWb+kuwEAV5jKaSkINvUOjIPAnOB7SA83xXWZW1Y9lig==
"@roadiehq/backstage-plugin-github-pull-requests@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-pull-requests/-/backstage-plugin-github-pull-requests-2.0.0.tgz#99ba670e1bd9bdc954b43901883aaf9832ae8942"
integrity sha512-3R9MmWGqmd7cVEZtiUShr10M8j+aM0SQUaZG1EuW+AzlErA2Zo1pcSBwKuIOcotWbyQzVsruKEPI2BrvJkJikQ==
dependencies:
"@backstage/catalog-model" "^0.13.0"
"@backstage/catalog-model" "^1.0.0"
"@backstage/core-components" "^0.9.0"
"@backstage/core-plugin-api" "^0.8.0"
"@backstage/plugin-catalog-react" "^0.9.0"
"@backstage/core-plugin-api" "^1.0.0"
"@backstage/plugin-catalog-react" "^1.0.0"
"@material-ui/core" "^4.11.0"
"@material-ui/icons" "^4.9.1"
"@octokit/rest" "^18.5.3"
@@ -5036,15 +4919,15 @@
react-router "6.0.0-beta.0"
react-use "^17.2.4"
"@roadiehq/backstage-plugin-travis-ci@^1.4.0":
version "1.4.0"
resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-travis-ci/-/backstage-plugin-travis-ci-1.4.0.tgz#165b401b4b4eaae93ebb1148854c8343392f9950"
integrity sha512-tYNpfybmKiOULYoL7YV2DJ1l0e8UcbZsc3B0pSUbchPedoWlvgpXoG5BmttTZcCW8MI0e1kK2FWqwnVkvygRhQ==
"@roadiehq/backstage-plugin-travis-ci@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-travis-ci/-/backstage-plugin-travis-ci-2.0.0.tgz#d86eba878bbaa6ada77e9eda932ff25bd9121021"
integrity sha512-nrqWysSLBq4t9jVkQb5rz1pfNt2Ojlj+s0004FSNzPa8PPD+5XUhFZiS9Mi4kJVUC9BLW5SVdJzWtWEGbxyLPw==
dependencies:
"@backstage/catalog-model" "^0.13.0"
"@backstage/catalog-model" "^1.0.0"
"@backstage/core-components" "^0.9.0"
"@backstage/core-plugin-api" "^0.8.0"
"@backstage/plugin-catalog-react" "^0.9.0"
"@backstage/core-plugin-api" "^1.0.0"
"@backstage/plugin-catalog-react" "^1.0.0"
"@backstage/theme" "^0.2.9"
"@material-ui/core" "^4.11.3"
"@material-ui/icons" "^4.11.2"
@@ -12106,10 +11989,10 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
"@material-ui/icons" "^4.9.1"
"@material-ui/lab" "4.0.0-alpha.57"
"@octokit/rest" "^18.5.3"
"@roadiehq/backstage-plugin-buildkite" "^1.4.0"
"@roadiehq/backstage-plugin-github-insights" "^1.6.0"
"@roadiehq/backstage-plugin-github-pull-requests" "^1.5.0"
"@roadiehq/backstage-plugin-travis-ci" "^1.4.0"
"@roadiehq/backstage-plugin-buildkite" "^2.0.0"
"@roadiehq/backstage-plugin-github-insights" "^2.0.0"
"@roadiehq/backstage-plugin-github-pull-requests" "^2.0.0"
"@roadiehq/backstage-plugin-travis-ci" "^2.0.0"
history "^5.0.0"
prop-types "^15.7.2"
react "^17.0.2"
@@ -13357,10 +13240,10 @@ globby@^7.1.1:
pify "^3.0.0"
slash "^1.0.0"
google-auth-library@^7.0.0, google-auth-library@^7.0.2, google-auth-library@^7.6.1:
version "7.12.0"
resolved "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.12.0.tgz#7965db6bc20cb31f2df05a08a296bbed6af69426"
integrity sha512-RS/whvFPMoF1hQNxnoVET3DWKPBt1Xgqe2rY0k+Jn7TNhoHlwdnSe7Rlcbo2Nub3Mt2lUVz26X65aDQrWp6x8w==
google-auth-library@^7.0.0, google-auth-library@^7.14.0, google-auth-library@^7.6.1:
version "7.14.0"
resolved "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.0.tgz#9d6a20592f7b4d4c463cd3e93934c4b1711d5dc6"
integrity sha512-or8r7qUqGVI3W8lVSdPh0ZpeFyQHeE73g5c0p+bLNTTUFXJ+GSeDQmZRZ2p4H8cF/RJYa4PNvi/A1ar1uVNLFA==
dependencies:
arrify "^2.0.0"
base64-js "^1.3.0"