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 11:27:43 +02:00
635 changed files with 9792 additions and 6037 deletions
+11
View File
@@ -1,5 +1,16 @@
# @backstage/plugin-api-docs
## 0.6.5
### Patch Changes
- 7b8aa8d0d: Move the `CreateComponentButton` from the catalog plugin to the `core-components` & rename it to `CreateButton` to be reused inside the api-docs plugin & scaffolder plugin, but also future plugins. Additionally, improve responsiveness of `CreateButton` & `SupportButton` by shrinking them to `IconButtons` on smaller screens.
- Updated dependencies
- @backstage/plugin-catalog@0.6.10
- @backstage/core-components@0.3.0
- @backstage/core-plugin-api@0.1.5
- @backstage/plugin-catalog-react@0.4.1
## 0.6.4
### Patch Changes
+8 -8
View File
@@ -30,19 +30,19 @@ import openapiApiEntity from './openapi-example-api.yaml';
import otherApiEntity from './other-example-api.yaml';
import { Content, Header, Page } from '@backstage/core-components';
const mockEntities = ([
const mockEntities = [
openapiApiEntity,
asyncapiApiEntity,
graphqlApiEntity,
otherApiEntity,
] as unknown) as Entity[];
] as unknown as Entity[];
createDevApp()
.registerApi({
api: catalogApiRef,
deps: {},
factory: () =>
(({
({
async getEntities() {
return {
items: mockEntities.slice(),
@@ -51,7 +51,7 @@ createDevApp()
async getEntityByName(name: string) {
return mockEntities.find(e => e.metadata.name === name);
},
} as unknown) as typeof catalogApiRef.T),
} as unknown as typeof catalogApiRef.T),
})
.registerApi({
api: apiDocsConfigRef,
@@ -72,7 +72,7 @@ createDevApp()
<Page themeId="home">
<Header title="OpenAPI" />
<Content>
<EntityProvider entity={(openapiApiEntity as any) as Entity}>
<EntityProvider entity={openapiApiEntity as any as Entity}>
<EntityApiDefinitionCard />
</EntityProvider>
</Content>
@@ -85,7 +85,7 @@ createDevApp()
<Page themeId="home">
<Header title="AsyncAPI" />
<Content>
<EntityProvider entity={(asyncapiApiEntity as any) as Entity}>
<EntityProvider entity={asyncapiApiEntity as any as Entity}>
<EntityApiDefinitionCard />
</EntityProvider>
</Content>
@@ -98,7 +98,7 @@ createDevApp()
<Page themeId="home">
<Header title="GraphQL" />
<Content>
<EntityProvider entity={(graphqlApiEntity as any) as Entity}>
<EntityProvider entity={graphqlApiEntity as any as Entity}>
<EntityApiDefinitionCard />
</EntityProvider>
</Content>
@@ -111,7 +111,7 @@ createDevApp()
<Page themeId="home">
<Header title="Other" />
<Content>
<EntityProvider entity={(otherApiEntity as any) as Entity}>
<EntityProvider entity={otherApiEntity as any as Entity}>
<EntityApiDefinitionCard />
</EntityProvider>
</Content>
+9 -9
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-api-docs",
"version": "0.6.4",
"version": "0.6.5",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,16 +31,16 @@
"dependencies": {
"@asyncapi/react-component": "^0.23.0",
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-components": "^0.2.0",
"@backstage/core-plugin-api": "^0.1.4",
"@backstage/plugin-catalog": "^0.6.9",
"@backstage/plugin-catalog-react": "^0.4.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/theme": "^0.2.9",
"@material-icons/font": "^1.0.2",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@types/react": "^16.9",
"@types/react": "*",
"graphiql": "^1.0.0-alpha.10",
"graphql": "^15.3.0",
"isomorphic-form-data": "^2.0.0",
@@ -52,9 +52,9 @@
"swagger-ui-react": "^3.37.2"
},
"devDependencies": {
"@backstage/cli": "^0.7.6",
"@backstage/core-app-api": "^0.1.6",
"@backstage/dev-utils": "^0.2.4",
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
@@ -88,13 +88,15 @@ const useStyles = makeStyles(theme => ({
'& .asyncapi__enum': {
color: theme.palette.secondary.main,
},
'& .asyncapi__info, .asyncapi__channel, .asyncapi__channels > div, .asyncapi__schema, .asyncapi__channel-operations-list .asyncapi__messages-list-item .asyncapi__message, .asyncapi__message, .asyncapi__server, .asyncapi__servers > div, .asyncapi__messages > div, .asyncapi__schemas > div': {
'background-color': 'inherit',
},
'& .asyncapi__channel-parameters-header, .asyncapi__channel-operations-header, .asyncapi__channel-operation-oneOf-subscribe-header, .asyncapi__channel-operation-oneOf-publish-header, .asyncapi__channel-operation-message-header, .asyncapi__message-header, .asyncapi__message-header-title, .asyncapi__message-header-title > h3, .asyncapi__bindings, .asyncapi__bindings-header, .asyncapi__bindings-header > h4': {
'background-color': 'inherit',
color: theme.palette.text.primary,
},
'& .asyncapi__info, .asyncapi__channel, .asyncapi__channels > div, .asyncapi__schema, .asyncapi__channel-operations-list .asyncapi__messages-list-item .asyncapi__message, .asyncapi__message, .asyncapi__server, .asyncapi__servers > div, .asyncapi__messages > div, .asyncapi__schemas > div':
{
'background-color': 'inherit',
},
'& .asyncapi__channel-parameters-header, .asyncapi__channel-operations-header, .asyncapi__channel-operation-oneOf-subscribe-header, .asyncapi__channel-operation-oneOf-publish-header, .asyncapi__channel-operation-message-header, .asyncapi__message-header, .asyncapi__message-header-title, .asyncapi__message-header-title > h3, .asyncapi__bindings, .asyncapi__bindings-header, .asyncapi__bindings-header > h4':
{
'background-color': 'inherit',
color: theme.palette.text.primary,
},
'& .asyncapi__additional-properties-notice': {
color: theme.palette.text.hint,
},
@@ -104,10 +106,11 @@ const useStyles = makeStyles(theme => ({
'& .asyncapi__schema-example-header-title': {
color: theme.palette.text.secondary,
},
'& .asyncapi__message-headers-header, .asyncapi__message-payload-header, .asyncapi__server-variables-header, .asyncapi__server-security-header': {
'background-color': 'inherit',
color: theme.palette.text.secondary,
},
'& .asyncapi__message-headers-header, .asyncapi__message-payload-header, .asyncapi__server-variables-header, .asyncapi__server-security-header':
{
'background-color': 'inherit',
color: theme.palette.text.secondary,
},
'& .asyncapi__table-header': {
background: theme.palette.background.default,
},
@@ -14,8 +14,8 @@
* limitations under the License.
*/
import React, { useEffect, useState } from 'react';
import { makeStyles } from '@material-ui/core/styles';
import React, { useEffect, useState } from 'react';
import SwaggerUI from 'swagger-ui-react';
import 'swagger-ui-react/swagger-ui.css';
@@ -30,22 +30,26 @@ const useStyles = makeStyles(theme => ({
'& .scheme-container': {
'background-color': theme.palette.background.default,
},
'& .opblock-tag, .opblock-tag small, table thead tr td, table thead tr th': {
color: theme.palette.text.primary,
'border-color': theme.palette.divider,
},
'& .opblock-tag, .opblock-tag small, table thead tr td, table thead tr th':
{
color: theme.palette.text.primary,
'border-color': theme.palette.divider,
},
'& section.models, section.models.is-open h4': {
'border-color': theme.palette.divider,
},
'& .opblock .opblock-summary-description, .parameter__type, table.headers td, .model-title, .model .property.primitive, section h3': {
color: theme.palette.text.secondary,
},
'& .opblock .opblock-summary-operation-id, .opblock .opblock-summary-path, .opblock .opblock-summary-path__deprecated, .opblock .opblock-section-header h4, .parameter__name, .response-col_status, .response-col_links, .responses-inner h4, .swagger-ui .responses-inner h5, .opblock-section-header .btn, .tab li, .info li, .info p, .info table, section.models h4, .info .title, table.model tr.description, .property-row': {
color: theme.palette.text.primary,
},
'& .opblock .opblock-section-header, .model-box, section.models .model-container': {
background: theme.palette.background.default,
},
'& .opblock .opblock-summary-description, .parameter__type, table.headers td, .model-title, .model .property.primitive, section h3':
{
color: theme.palette.text.secondary,
},
'& .opblock .opblock-summary-operation-id, .opblock .opblock-summary-path, .opblock .opblock-summary-path__deprecated, .opblock .opblock-section-header h4, .parameter__name, .response-col_status, .response-col_links, .responses-inner h4, .swagger-ui .responses-inner h5, .opblock-section-header .btn, .tab li, .info li, .info p, .info table, section.models h4, .info .title, table.model tr.description, .property-row':
{
color: theme.palette.text.primary,
},
'& .opblock .opblock-section-header, .model-box, section.models .model-container':
{
background: theme.palette.background.default,
},
'& .prop-format, .parameter__in': {
color: theme.palette.text.disabled,
},
@@ -53,9 +57,10 @@ const useStyles = makeStyles(theme => ({
color: theme.palette.text.primary,
'border-color': theme.palette.divider,
},
'& .opblock-description-wrapper p, .opblock-external-docs-wrapper p, .opblock-title_normal p, .response-control-media-type__accept-message, .opblock .opblock-section-header>label, .scheme-container .schemes>label, .info .base-url, .model': {
color: theme.palette.text.hint,
},
'& .opblock-description-wrapper p, .opblock-external-docs-wrapper p, .opblock-title_normal p, .response-control-media-type__accept-message, .opblock .opblock-section-header>label, .scheme-container .schemes>label, .info .base-url, .model':
{
color: theme.palette.text.hint,
},
'& .parameter__name.required:after': {
color: theme.palette.warning.dark,
},
@@ -83,7 +88,7 @@ export const OpenApiDefinitionWidget = ({ definition }: Props) => {
return (
<div className={classes.root}>
<SwaggerUI spec={def} />
<SwaggerUI spec={def} deepLinking />
</div>
);
};
+1 -1
View File
@@ -22,7 +22,7 @@ import { injectConfig } from './config';
jest.mock('fs-extra');
const fsMock = fs as jest.Mocked<typeof fs>;
const readFileMock = (fsMock.readFile as unknown) as jest.MockedFunction<
const readFileMock = fsMock.readFile as unknown as jest.MockedFunction<
(name: string) => Promise<string>
>;
+1 -2
View File
@@ -104,8 +104,7 @@ export async function createRouter(
// The Cache-Control header instructs the browser to not cache html files since it might
// link to static assets from recently deployed versions.
if (
((express.static.mime as unknown) as Mime).lookup(path) ===
'text/html'
(express.static.mime as unknown as Mime).lookup(path) === 'text/html'
) {
res.setHeader('Cache-Control', 'no-store, max-age=0');
}
+1 -3
View File
@@ -223,9 +223,7 @@ export class OAuthEnvironmentHandler implements AuthProviderRouteHandlers {
export interface OAuthHandlers {
// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
// Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}'
handler(
req: express.Request,
): Promise<{
handler(req: express.Request): Promise<{
response: AuthResponse<OAuthProviderInfo>;
refreshToken?: string;
}>;
@@ -150,7 +150,7 @@ export class TokenFactory implements TokenIssuer {
// the new one. This also needs to be implemented cross-service though, meaning new services
// that boot up need to be able to grab an existing key to use for signing.
this.logger.info(`Created new signing key ${key.kid}`);
await this.keyStore.addKey((key.toJWK(false) as unknown) as AnyJWK);
await this.keyStore.addKey(key.toJWK(false) as unknown as AnyJWK);
// At this point we are allowed to start using the new key
return key as JSONWebKey;
@@ -32,10 +32,10 @@ describe('oauth helpers', () => {
describe('postMessageResponse', () => {
const appOrigin = 'http://localhost:3000';
it('should post a message back with payload success', () => {
const mockResponse = ({
const mockResponse = {
end: jest.fn().mockReturnThis(),
setHeader: jest.fn().mockReturnThis(),
} as unknown) as express.Response;
} as unknown as express.Response;
const data: WebMessageResponse = {
type: 'authorization_response',
@@ -64,10 +64,10 @@ describe('oauth helpers', () => {
});
it('should post a message back with payload error', () => {
const mockResponse = ({
const mockResponse = {
end: jest.fn().mockReturnThis(),
setHeader: jest.fn().mockReturnThis(),
} as unknown) as express.Response;
} as unknown as express.Response;
const data: WebMessageResponse = {
type: 'authorization_response',
@@ -84,13 +84,13 @@ describe('oauth helpers', () => {
it('should call postMessage twice but only one of them with target *', () => {
let responseBody = '';
const mockResponse = ({
const mockResponse = {
end: jest.fn(body => {
responseBody = body;
return this;
}),
setHeader: jest.fn().mockReturnThis(),
} as unknown) as express.Response;
} as unknown as express.Response;
const data: WebMessageResponse = {
type: 'authorization_response',
@@ -128,10 +128,10 @@ describe('oauth helpers', () => {
});
it('handles single quotes and unicode chars safely', () => {
const mockResponse = ({
const mockResponse = {
end: jest.fn().mockReturnThis(),
setHeader: jest.fn().mockReturnThis(),
} as unknown) as express.Response;
} as unknown as express.Response;
const data: WebMessageResponse = {
type: 'authorization_response',
@@ -164,23 +164,23 @@ describe('oauth helpers', () => {
describe('ensuresXRequestedWith', () => {
it('should return false if no header present', () => {
const mockRequest = ({
const mockRequest = {
header: () => jest.fn(),
} as unknown) as express.Request;
} as unknown as express.Request;
expect(ensuresXRequestedWith(mockRequest)).toBe(false);
});
it('should return false if header present with incorrect value', () => {
const mockRequest = ({
const mockRequest = {
header: () => 'INVALID',
} as unknown) as express.Request;
} as unknown as express.Request;
expect(ensuresXRequestedWith(mockRequest)).toBe(false);
});
it('should return true if header present with correct value', () => {
const mockRequest = ({
const mockRequest = {
header: () => 'XMLHttpRequest',
} as unknown) as express.Request;
} as unknown as express.Request;
expect(ensuresXRequestedWith(mockRequest)).toBe(true);
});
});
@@ -71,19 +71,19 @@ describe('OAuthAdapter', () => {
providerInstance,
oAuthProviderOptions,
);
const mockRequest = ({
const mockRequest = {
query: {
scope: 'user',
env: 'development',
},
} as unknown) as express.Request;
} as unknown as express.Request;
const mockResponse = ({
const mockResponse = {
cookie: jest.fn().mockReturnThis(),
end: jest.fn().mockReturnThis(),
setHeader: jest.fn().mockReturnThis(),
statusCode: jest.fn().mockReturnThis(),
} as unknown) as express.Response;
} as unknown as express.Response;
await oauthProvider.start(mockRequest, mockResponse);
// nonce cookie checks
@@ -108,20 +108,20 @@ describe('OAuthAdapter', () => {
});
const state = { nonce: 'nonce', env: 'development' };
const mockRequest = ({
const mockRequest = {
cookies: {
'test-provider-nonce': 'nonce',
},
query: {
state: encodeState(state),
},
} as unknown) as express.Request;
} as unknown as express.Request;
const mockResponse = ({
const mockResponse = {
cookie: jest.fn().mockReturnThis(),
setHeader: jest.fn().mockReturnThis(),
end: jest.fn().mockReturnThis(),
} as unknown) as express.Response;
} as unknown as express.Response;
await oauthProvider.frameHandler(mockRequest, mockResponse);
expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
@@ -141,20 +141,20 @@ describe('OAuthAdapter', () => {
disableRefresh: true,
});
const mockRequest = ({
const mockRequest = {
cookies: {
'test-provider-nonce': 'nonce',
},
query: {
state: 'nonce',
},
} as unknown) as express.Request;
} as unknown as express.Request;
const mockResponse = ({
const mockResponse = {
cookie: jest.fn().mockReturnThis(),
setHeader: jest.fn().mockReturnThis(),
end: jest.fn().mockReturnThis(),
} as unknown) as express.Response;
} as unknown as express.Response;
await oauthProvider.frameHandler(mockRequest, mockResponse);
expect(mockResponse.cookie).toHaveBeenCalledTimes(0);
@@ -166,15 +166,15 @@ describe('OAuthAdapter', () => {
disableRefresh: false,
});
const mockRequest = ({
const mockRequest = {
header: () => 'XMLHttpRequest',
} as unknown) as express.Request;
} as unknown as express.Request;
const mockResponse = ({
const mockResponse = {
cookie: jest.fn().mockReturnThis(),
send: jest.fn().mockReturnThis(),
status: jest.fn().mockReturnThis(),
} as unknown) as express.Response;
} as unknown as express.Response;
await oauthProvider.logout(mockRequest, mockResponse);
expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
@@ -192,18 +192,18 @@ describe('OAuthAdapter', () => {
disableRefresh: false,
});
const mockRequest = ({
const mockRequest = {
header: () => 'XMLHttpRequest',
cookies: {
'test-provider-refresh-token': 'token',
},
query: {},
} as unknown) as express.Request;
} as unknown as express.Request;
const mockResponse = ({
const mockResponse = {
json: jest.fn().mockReturnThis(),
status: jest.fn().mockReturnThis(),
} as unknown) as express.Response;
} as unknown as express.Response;
await oauthProvider.refresh(mockRequest, mockResponse);
expect(mockResponse.json).toHaveBeenCalledTimes(1);
@@ -222,18 +222,18 @@ describe('OAuthAdapter', () => {
disableRefresh: true,
});
const mockRequest = ({
const mockRequest = {
header: () => 'XMLHttpRequest',
cookies: {
'test-provider-refresh-token': 'token',
},
query: {},
} as unknown) as express.Request;
} as unknown as express.Request;
const mockResponse = ({
const mockResponse = {
send: jest.fn().mockReturnThis(),
status: jest.fn().mockReturnThis(),
} as unknown) as express.Response;
} as unknown as express.Response;
await oauthProvider.refresh(mockRequest, mockResponse);
expect(mockResponse.send).toHaveBeenCalledTimes(1);
@@ -21,24 +21,24 @@ describe('OAuthProvider Utils', () => {
describe('verifyNonce', () => {
it('should throw error if cookie nonce missing', () => {
const state = { nonce: 'NONCE', env: 'development' };
const mockRequest = ({
const mockRequest = {
cookies: {},
query: {
state: encodeState(state),
},
} as unknown) as express.Request;
} as unknown as express.Request;
expect(() => {
verifyNonce(mockRequest, 'providera');
}).toThrowError('Auth response is missing cookie nonce');
});
it('should throw error if state nonce missing', () => {
const mockRequest = ({
const mockRequest = {
cookies: {
'providera-nonce': 'NONCE',
},
query: {},
} as unknown) as express.Request;
} as unknown as express.Request;
expect(() => {
verifyNonce(mockRequest, 'providera');
}).toThrowError('Invalid state passed via request');
@@ -46,14 +46,14 @@ describe('OAuthProvider Utils', () => {
it('should throw error if nonce mismatch', () => {
const state = { nonce: 'NONCEB', env: 'development' };
const mockRequest = ({
const mockRequest = {
cookies: {
'providera-nonce': 'NONCEA',
},
query: {
state: encodeState(state),
},
} as unknown) as express.Request;
} as unknown as express.Request;
expect(() => {
verifyNonce(mockRequest, 'providera');
}).toThrowError('Invalid nonce');
@@ -61,14 +61,14 @@ describe('OAuthProvider Utils', () => {
it('should not throw any error if nonce matches', () => {
const state = { nonce: 'NONCE', env: 'development' };
const mockRequest = ({
const mockRequest = {
cookies: {
'providera-nonce': 'NONCE',
},
query: {
state: encodeState(state),
},
} as unknown) as express.Request;
} as unknown as express.Request;
expect(() => {
verifyNonce(mockRequest, 'providera');
}).not.toThrow();
@@ -29,18 +29,14 @@ export const readState = (stateString: string): OAuthState => {
) {
throw Error(`Invalid state passed via request`);
}
return {
nonce: state.nonce,
env: state.env,
};
return state as OAuthState;
};
export const encodeState = (state: OAuthState): string => {
const searchParams = new URLSearchParams();
searchParams.append('nonce', state.nonce);
searchParams.append('env', state.env);
const stateString = new URLSearchParams(state).toString();
return Buffer.from(searchParams.toString(), 'utf-8').toString('hex');
return Buffer.from(stateString, 'utf-8').toString('hex');
};
export const verifyNonce = (req: express.Request, providerId: string) => {
+1 -3
View File
@@ -106,9 +106,7 @@ export interface OAuthHandlers {
* Handles the redirect from the auth provider when the user has signed in.
* @param {express.Request} req
*/
handler(
req: express.Request,
): Promise<{
handler(req: express.Request): Promise<{
response: AuthResponse<OAuthProviderInfo>;
refreshToken?: string;
}>;
@@ -23,7 +23,7 @@ import {
executeRefreshTokenStrategy,
} from './PassportStrategyHelper';
const mockRequest = ({} as unknown) as express.Request;
const mockRequest = {} as unknown as express.Request;
describe('PassportStrategyHelper', () => {
class MyCustomRedirectStrategy extends passport.Strategy {
@@ -195,7 +195,7 @@ export const executeFetchUserProfileStrategy = async (
accessToken: string,
): Promise<passport.Profile> => {
return new Promise((resolve, reject) => {
const anyStrategy = (providerStrategy as unknown) as ProviderStrategy;
const anyStrategy = providerStrategy as unknown as ProviderStrategy;
anyStrategy.userProfile(
accessToken,
(error: Error, rawProfile: passport.Profile) => {
@@ -79,22 +79,22 @@ describe('AwsALBAuthProvider', () => {
getEntityByName: jest.fn(),
};
const mockRequest = ({
const mockRequest = {
header: jest.fn(() => {
return 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImZvbyIsImlzcyI6ImZvbyJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.T2BNS4G-6RoiFnXc8Q8TiwdWzTpNitY8jcsGM3N3-Yo';
}),
} as unknown) as express.Request;
const mockRequestWithoutJwt = ({
} as unknown as express.Request;
const mockRequestWithoutJwt = {
header: jest.fn(() => {
return undefined;
}),
} as unknown) as express.Request;
const mockResponse = ({
} as unknown as express.Request;
const mockResponse = {
end: jest.fn(),
header: () => jest.fn(),
json: jest.fn().mockReturnThis(),
status: jest.fn(),
} as unknown) as express.Response;
} as unknown as express.Response;
describe('should transform to type OAuthResponse', () => {
it('when JWT is valid and identity is resolved successfully', async () => {
@@ -19,10 +19,10 @@ import { GithubAuthProvider } from './provider';
import * as helpers from '../../lib/passport/PassportStrategyHelper';
import { OAuthResult } from '../../lib/oauth';
const mockFrameHandler = (jest.spyOn(
const mockFrameHandler = jest.spyOn(
helpers,
'executeFrameHandlerStrategy',
) as unknown) as jest.MockedFunction<
) as unknown as jest.MockedFunction<
() => Promise<{
result: Omit<OAuthResult, 'params'> & { params: { scope: string } };
}>
@@ -87,7 +87,7 @@ describe('GithubAuthProvider', () => {
it('when "email" is missing, it should be able to create the profile without it', async () => {
const accessToken = '19xasczxcm9n7gacn9jdgm19me';
const fullProfile = ({
const fullProfile = {
id: 'uid-123',
username: 'jimmymarkum',
provider: 'github',
@@ -99,7 +99,7 @@ describe('GithubAuthProvider', () => {
'https://a1cf74336522e87f135f-2f21ace9a6cf0052456644b80fa06d4f.ssl.cf2.rackcdn.com/images/characters_opt/p-mystic-river-sean-penn.jpg',
},
],
} as unknown) as PassportProfile;
} as unknown as PassportProfile;
const params = {
scope: 'read:scope',
@@ -131,7 +131,7 @@ describe('GithubAuthProvider', () => {
it('when "displayName" is missing, it should be able to create the profile and map "displayName" with "username"', async () => {
const accessToken = '19xasczxcm9n7gacn9jdgm19me';
const fullProfile = ({
const fullProfile = {
id: 'uid-123',
username: 'jimmymarkum',
provider: 'github',
@@ -143,7 +143,7 @@ describe('GithubAuthProvider', () => {
'https://a1cf74336522e87f135f-2f21ace9a6cf0052456644b80fa06d4f.ssl.cf2.rackcdn.com/images/characters_opt/p-mystic-river-sean-penn.jpg',
},
],
} as unknown) as PassportProfile;
} as unknown as PassportProfile;
const params = {
scope: 'read:scope',
@@ -18,10 +18,10 @@ import { GitlabAuthProvider } from './provider';
import * as helpers from '../../lib/passport/PassportStrategyHelper';
import { OAuthResult } from '../../lib/oauth';
const mockFrameHandler = (jest.spyOn(
const mockFrameHandler = jest.spyOn(
helpers,
'executeFrameHandlerStrategy',
) as unknown) as jest.MockedFunction<() => Promise<{ result: OAuthResult }>>;
) as unknown as jest.MockedFunction<() => Promise<{ result: OAuthResult }>>;
describe('GitlabAuthProvider', () => {
it('should transform to type OAuthResponse', async () => {
@@ -21,10 +21,10 @@ import { getVoidLogger } from '@backstage/backend-common';
import { TokenIssuer } from '../../identity/types';
import { CatalogIdentityClient } from '../../lib/catalog';
const mockFrameHandler = (jest.spyOn(
const mockFrameHandler = jest.spyOn(
helpers,
'executeFrameHandlerStrategy',
) as unknown) as jest.MockedFunction<
) as unknown as jest.MockedFunction<
() => Promise<{ result: OAuthResult; privateInfo: any }>
>;
@@ -40,8 +40,9 @@ describe('createGoogleProvider', () => {
const provider = new GoogleAuthProvider({
logger: getVoidLogger(),
catalogIdentityClient: (catalogIdentityClient as unknown) as CatalogIdentityClient,
tokenIssuer: (tokenIssuer as unknown) as TokenIssuer,
catalogIdentityClient:
catalogIdentityClient as unknown as CatalogIdentityClient,
tokenIssuer: tokenIssuer as unknown as TokenIssuer,
authHandler: async ({ fullProfile }) => ({
profile: {
email: fullProfile.emails![0]!.value,
@@ -21,10 +21,10 @@ import { getVoidLogger } from '@backstage/backend-common';
import { TokenIssuer } from '../../identity/types';
import { CatalogIdentityClient } from '../../lib/catalog';
const mockFrameHandler = (jest.spyOn(
const mockFrameHandler = jest.spyOn(
helpers,
'executeFrameHandlerStrategy',
) as unknown) as jest.MockedFunction<
) as unknown as jest.MockedFunction<
() => Promise<{ result: OAuthResult; privateInfo: any }>
>;
@@ -40,8 +40,9 @@ describe('createMicrosoftProvider', () => {
const provider = new MicrosoftAuthProvider({
logger: getVoidLogger(),
catalogIdentityClient: (catalogIdentityClient as unknown) as CatalogIdentityClient,
tokenIssuer: (tokenIssuer as unknown) as TokenIssuer,
catalogIdentityClient:
catalogIdentityClient as unknown as CatalogIdentityClient,
tokenIssuer: tokenIssuer as unknown as TokenIssuer,
authHandler: async ({ fullProfile }) => ({
profile: {
email: fullProfile.emails![0]!.value,
@@ -220,24 +220,22 @@ export const microsoftEmailSignInResolver: SignInResolver<OAuthResult> = async (
return { id: entity.metadata.name, entity, token };
};
export const microsoftDefaultSignInResolver: SignInResolver<OAuthResult> = async (
info,
ctx,
) => {
const { profile } = info;
export const microsoftDefaultSignInResolver: SignInResolver<OAuthResult> =
async (info, ctx) => {
const { profile } = info;
if (!profile.email) {
throw new Error('Profile contained no email');
}
if (!profile.email) {
throw new Error('Profile contained no email');
}
const userId = profile.email.split('@')[0];
const userId = profile.email.split('@')[0];
const token = await ctx.tokenIssuer.issueToken({
claims: { sub: userId, ent: [`user:default/${userId}`] },
});
const token = await ctx.tokenIssuer.issueToken({
claims: { sub: userId, ent: [`user:default/${userId}`] },
});
return { id: userId, token };
};
return { id: userId, token };
};
export type MicrosoftProviderOptions = {
/**
@@ -70,7 +70,7 @@ describe('OidcAuthProvider', () => {
rest.get('https://oidc.test/.well-known/openid-configuration', handler),
);
const provider = new OidcAuthProvider(clientMetadata);
const { strategy } = ((await (provider as any).implementation) as any) as {
const { strategy } = (await (provider as any).implementation) as any as {
strategy: {
_client: ClientMetadata;
_issuer: IssuerMetadata;
@@ -138,7 +138,7 @@ describe('OidcAuthProvider', () => {
const req = {
method: 'GET',
url: 'https://oidc.test/?code=test2',
session: ({ 'oidc:oidc.test': 'test' } as any) as Session,
session: { 'oidc:oidc.test': 'test' } as any as Session,
} as express.Request;
await provider.handler(req);
expect(requestSequence).toEqual([0, 1, 2].map(i => requests[i].url));
@@ -42,7 +42,7 @@ describe('DefaultBadgeBuilder', () => {
createBadge: () => badge,
},
failbadge: {
createBadge: () => (undefined as unknown) as Badge, // force a bad return value..
createBadge: () => undefined as unknown as Badge, // force a bad return value..
},
invalidbadge: {
createBadge: () => ({ style: 'wrong' as BadgeStyle, ...badge }),
+9
View File
@@ -1,5 +1,14 @@
# @backstage/plugin-badges
## 0.2.7
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.3.0
- @backstage/core-plugin-api@0.1.5
- @backstage/plugin-catalog-react@0.4.1
## 0.2.6
### Patch Changes
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-badges",
"version": "0.2.6",
"version": "0.2.7",
"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.2.0",
"@backstage/core-plugin-api": "^0.1.4",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/errors": "^0.1.1",
"@backstage/plugin-catalog-react": "^0.4.0",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -35,9 +35,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.6",
"@backstage/core-app-api": "^0.1.6",
"@backstage/dev-utils": "^0.2.4",
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
@@ -48,7 +48,11 @@ export const EntityBadgesDialog = ({ open, onClose }: Props) => {
const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
const badgesApi = useApi(badgesApiRef);
const { value: badges, loading, error } = useAsync(async () => {
const {
value: badges,
loading,
error,
} = useAsync(async () => {
if (open && entity) {
return await badgesApi.getEntityBadgeSpecs(entity);
}
+9
View File
@@ -1,5 +1,14 @@
# @backstage/plugin-bitrise
## 0.1.9
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.3.0
- @backstage/core-plugin-api@0.1.5
- @backstage/plugin-catalog-react@0.4.1
## 0.1.8
### Patch Changes
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-bitrise",
"version": "0.1.8",
"version": "0.1.9",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,9 +21,9 @@
},
"dependencies": {
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-components": "^0.2.0",
"@backstage/core-plugin-api": "^0.1.4",
"@backstage/plugin-catalog-react": "^0.4.0",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/plugin-catalog-react": "^0.4.1",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -38,9 +38,9 @@
"recharts": "^1.8.5"
},
"devDependencies": {
"@backstage/cli": "^0.7.6",
"@backstage/core-app-api": "^0.1.6",
"@backstage/dev-utils": "^0.2.4",
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
@@ -140,21 +140,7 @@ describe('readLdapUsers', () => {
distinguishedName: ['dn-value'],
objectGUID: [
Buffer.from([
68,
2,
125,
190,
209,
0,
94,
73,
133,
33,
230,
174,
234,
195,
160,
68, 2, 125, 190, 209, 0, 94, 73, 133, 33, 230, 174, 234, 195, 160,
152,
]),
],
@@ -286,21 +272,7 @@ describe('readLdapGroups', () => {
distinguishedName: ['dn-value'],
objectGUID: [
Buffer.from([
68,
2,
125,
190,
209,
0,
94,
73,
133,
33,
230,
174,
234,
195,
160,
68, 2, 125, 190, 209, 0, 94, 73, 133, 33, 230, 174, 234, 195, 160,
152,
]),
],
@@ -89,9 +89,11 @@ export async function myGroupTransformer(
groupPhoto?: string,
): Promise<GroupEntity | undefined> {
if (
((group as unknown) as {
creationOptions: string[];
}).creationOptions.includes('ProvisionGroupHomepage')
(
group as unknown as {
creationOptions: string[];
}
).creationOptions.includes('ProvisionGroupHomepage')
) {
return undefined;
}
@@ -21,9 +21,10 @@ import { setupServer } from 'msw/node';
import { MicrosoftGraphClient } from './client';
describe('MicrosoftGraphClient', () => {
const confidentialClientApplication: jest.Mocked<msal.ConfidentialClientApplication> = {
acquireTokenByClientCredential: jest.fn(),
} as any;
const confidentialClientApplication: jest.Mocked<msal.ConfidentialClientApplication> =
{
acquireTokenByClientCredential: jest.fn(),
} as any;
let client: MicrosoftGraphClient;
const worker = setupServer();
@@ -202,14 +202,10 @@ describe('read microsoft graph', () => {
'data:image/jpeg;base64,...',
);
const {
groups,
groupMember,
groupMemberOf,
rootGroup,
} = await readMicrosoftGraphGroups(client, 'tenantid', {
groupFilter: 'securityEnabled eq false',
});
const { groups, groupMember, groupMemberOf, rootGroup } =
await readMicrosoftGraphGroups(client, 'tenantid', {
groupFilter: 'securityEnabled eq false',
});
const expectedRootGroup = group({
metadata: {
@@ -318,14 +318,12 @@ export function resolveRelations(
// Make sure every group (except root) has at least one parent. If the parent is missing, add the root.
if (rootGroup) {
const tenantId = rootGroup.metadata.annotations![
MICROSOFT_GRAPH_TENANT_ID_ANNOTATION
];
const tenantId =
rootGroup.metadata.annotations![MICROSOFT_GRAPH_TENANT_ID_ANNOTATION];
groups.forEach(group => {
const groupId = group.metadata.annotations![
MICROSOFT_GRAPH_GROUP_ID_ANNOTATION
];
const groupId =
group.metadata.annotations![MICROSOFT_GRAPH_GROUP_ID_ANNOTATION];
if (!groupId) {
return;
@@ -392,15 +390,11 @@ export async function readMicrosoftGraphOrg(
userFilter: options.userFilter,
logger: options.logger,
});
const {
groups,
rootGroup,
groupMember,
groupMemberOf,
} = await readMicrosoftGraphGroups(client, tenantId, {
groupFilter: options?.groupFilter,
transformer: options?.groupTransformer,
});
const { groups, rootGroup, groupMember, groupMemberOf } =
await readMicrosoftGraphGroups(client, tenantId, {
groupFilter: options?.groupFilter,
transformer: options?.groupTransformer,
});
resolveRelations(rootGroup, groups, users, groupMember, groupMemberOf);
users.sort((a, b) => a.metadata.name.localeCompare(b.metadata.name));
+5 -8
View File
@@ -442,13 +442,11 @@
and lets the other processors take care of further processing.
```typescript
const customRepositoryParser: BitbucketRepositoryParser = async function* customRepositoryParser({
client,
repository,
}) {
// Custom logic for interpret the matching repository.
// See defaultRepositoryParser for an example
};
const customRepositoryParser: BitbucketRepositoryParser =
async function* customRepositoryParser({ client, repository }) {
// Custom logic for interpret the matching repository.
// See defaultRepositoryParser for an example
};
const processor = BitbucketDiscoveryProcessor.fromConfig(env.config, {
parser: customRepositoryParser,
@@ -920,7 +918,6 @@
spec:
type: website
---
```
This behaves now the same way as Kubernetes handles multiple documents in a single YAML file.
+3 -4
View File
@@ -88,9 +88,7 @@ export class AwsOrganizationCloudAccountProcessor implements CatalogProcessor {
logger: Logger_2;
});
// (undocumented)
extractInformationFromArn(
arn: string,
): {
extractInformationFromArn(arn: string): {
accountId: string;
organizationId: string;
};
@@ -596,7 +594,8 @@ export class DefaultCatalogCollator implements DocumentCollator {
//
// @public (undocumented)
export class DefaultCatalogProcessingOrchestrator
implements CatalogProcessingOrchestrator {
implements CatalogProcessingOrchestrator
{
constructor(options: {
processors: CatalogProcessor[];
integrations: ScmIntegrationRegistry;
@@ -52,12 +52,8 @@ export class DatabaseLocationsCatalog implements LocationsCatalog {
}
async location(id: string): Promise<LocationResponse> {
const {
message,
status,
timestamp,
...data
} = await this.database.location(id);
const { message, status, timestamp, ...data } =
await this.database.location(id);
return {
currentStatus: {
message,
@@ -556,9 +556,10 @@ export class CommonDatabase implements Database {
}
}
function parsePagination(
input?: EntityPagination,
): { limit?: number; offset?: number } {
function parsePagination(input?: EntityPagination): {
limit?: number;
offset?: number;
} {
if (!input) {
return {};
}
@@ -71,9 +71,8 @@ export class AnnotateLocationEntityProcessor implements CatalogProcessor {
annotations: pickBy(
{
[LOCATION_ANNOTATION]: stringifyLocationReference(location),
[ORIGIN_LOCATION_ANNOTATION]: stringifyLocationReference(
originLocation,
),
[ORIGIN_LOCATION_ANNOTATION]:
stringifyLocationReference(originLocation),
[VIEW_URL_ANNOTATION]: viewUrl,
[EDIT_URL_ANNOTATION]: editUrl,
[SOURCE_LOCATION_ANNOTATION]: sourceLocation,
@@ -37,8 +37,7 @@ describe('AwsOrganizationCloudAccountProcessor', () => {
return {
Accounts: [
{
Arn:
'arn:aws:organizations::192594491037:account/o-1vl18kc5a3/957140518395',
Arn: 'arn:aws:organizations::192594491037:account/o-1vl18kc5a3/957140518395',
Name: 'testaccount',
},
],
@@ -83,13 +82,11 @@ describe('AwsOrganizationCloudAccountProcessor', () => {
return {
Accounts: [
{
Arn:
'arn:aws:organizations::192594491037:account/o-1vl18kc5a3/957140518395',
Arn: 'arn:aws:organizations::192594491037:account/o-1vl18kc5a3/957140518395',
Name: 'testaccount',
},
{
Arn:
'arn:aws:organizations::192594491037:account/o-zzzzzzzzz/957140518395',
Arn: 'arn:aws:organizations::192594491037:account/o-zzzzzzzzz/957140518395',
Name: 'testaccount2',
},
],
@@ -89,9 +89,10 @@ export class AwsOrganizationCloudAccountProcessor implements CatalogProcessor {
.replace(/[^a-zA-Z0-9\-]/g, '-');
}
extractInformationFromArn(
arn: string,
): { accountId: string; organizationId: string } {
extractInformationFromArn(arn: string): {
accountId: string;
organizationId: string;
} {
const parts = arn.split('/');
return {
@@ -256,15 +256,16 @@ describe('BitbucketDiscoveryProcessor', () => {
});
describe('Custom repository parser', () => {
const customRepositoryParser: BitbucketRepositoryParser = async function* customRepositoryParser({}) {
yield results.location(
{
type: 'custom-location-type',
target: 'custom-target',
},
true,
);
};
const customRepositoryParser: BitbucketRepositoryParser =
async function* customRepositoryParser({}) {
yield results.location(
{
type: 'custom-location-type',
target: 'custom-target',
},
true,
);
};
const processor = BitbucketDiscoveryProcessor.fromConfig(
new ConfigReader({
@@ -136,9 +136,11 @@ export async function readBitbucketOrg(
return result;
}
function parseUrl(
urlString: string,
): { projectSearchPath: RegExp; repoSearchPath: RegExp; catalogPath: string } {
function parseUrl(urlString: string): {
projectSearchPath: RegExp;
repoSearchPath: RegExp;
catalogPath: string;
} {
const url = new URL(urlString);
const path = url.pathname.substr(1).split('/');
@@ -21,9 +21,10 @@ import { getOrganizationRepositories } from './github';
import { ConfigReader } from '@backstage/config';
jest.mock('./github');
const mockGetOrganizationRepositories = getOrganizationRepositories as jest.MockedFunction<
typeof getOrganizationRepositories
>;
const mockGetOrganizationRepositories =
getOrganizationRepositories as jest.MockedFunction<
typeof getOrganizationRepositories
>;
describe('GithubDiscoveryProcessor', () => {
describe('parseUrl', () => {
@@ -32,6 +33,7 @@ describe('GithubDiscoveryProcessor', () => {
parseUrl('https://github.com/foo/proj/blob/master/catalog.yaml'),
).toEqual({
org: 'foo',
host: 'github.com',
repoSearchPath: /^proj$/,
catalogPath: '/blob/master/catalog.yaml',
});
@@ -39,6 +41,7 @@ describe('GithubDiscoveryProcessor', () => {
parseUrl('https://github.com/foo/proj*/blob/master/catalog.yaml'),
).toEqual({
org: 'foo',
host: 'github.com',
repoSearchPath: /^proj.*$/,
catalogPath: '/blob/master/catalog.yaml',
});
@@ -56,17 +56,26 @@ export class GithubDiscoveryProcessor implements CatalogProcessor {
return false;
}
const gitHubConfig = this.integrations.github.byUrl(location.target)
?.config;
const gitHubConfig = this.integrations.github.byUrl(
location.target,
)?.config;
if (!gitHubConfig) {
throw new Error(
`There is no GitHub integration that matches ${location.target}. Please add a configuration entry for it under integrations.github`,
);
}
const { org, repoSearchPath, catalogPath, host } = parseUrl(
location.target,
);
// Building the org url here so that the github creds provider doesn't need to know
// about how to handle the wild card which is special for this processor.
const orgUrl = `https://${host}/${org}`;
const { headers } = await GithubCredentialsProvider.create(
gitHubConfig,
).getCredentials({ url: location.target });
const { org, repoSearchPath, catalogPath } = parseUrl(location.target);
).getCredentials({ url: orgUrl });
const client = graphql.defaults({
baseUrl: gitHubConfig.apiBaseUrl,
@@ -110,9 +119,12 @@ export class GithubDiscoveryProcessor implements CatalogProcessor {
* Helpers
*/
export function parseUrl(
urlString: string,
): { org: string; repoSearchPath: RegExp; catalogPath: string } {
export function parseUrl(urlString: string): {
org: string;
repoSearchPath: RegExp;
catalogPath: string;
host: string;
} {
const url = new URL(urlString);
const path = url.pathname.substr(1).split('/');
@@ -122,6 +134,7 @@ export function parseUrl(
org: decodeURIComponent(path[0]),
repoSearchPath: escapeRegExp(decodeURIComponent(path[1])),
catalogPath: `/${decodeURIComponent(path.slice(2).join('/'))}`,
host: url.host,
};
}
@@ -75,8 +75,9 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor {
return false;
}
const gitHubConfig = this.integrations.github.byUrl(location.target)
?.config;
const gitHubConfig = this.integrations.github.byUrl(
location.target,
)?.config;
if (!gitHubConfig) {
throw new Error(
`There is no GitHub integration that matches ${location.target}. Please add a configuration entry for it under integrations.github`,
@@ -93,12 +94,10 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor {
for (const orgConfig of orgsToProcess) {
try {
const {
headers,
type: tokenType,
} = await credentialsProvider.getCredentials({
url: `${baseUrl}/${orgConfig.name}`,
});
const { headers, type: tokenType } =
await credentialsProvider.getCredentials({
url: `${baseUrl}/${orgConfig.name}`,
});
const client = graphql.defaults({
baseUrl: gitHubConfig.apiBaseUrl,
headers,
@@ -113,12 +113,10 @@ export class GithubOrgReaderProcessor implements CatalogProcessor {
}
const credentialsProvider = GithubCredentialsProvider.create(gitHubConfig);
const {
headers,
type: tokenType,
} = await credentialsProvider.getCredentials({
url: orgUrl,
});
const { headers, type: tokenType } =
await credentialsProvider.getCredentials({
url: orgUrl,
});
const client = graphql.defaults({
baseUrl: gitHubConfig.apiBaseUrl,
@@ -26,7 +26,7 @@ import { toAbsoluteUrl } from './LocationEntityProcessor';
describe('LocationEntityProcessor', () => {
describe('toAbsoluteUrl', () => {
it('handles files', () => {
const integrations = ({} as unknown) as ScmIntegrationRegistry;
const integrations = {} as unknown as ScmIntegrationRegistry;
const base: LocationSpec = {
type: 'file',
target: `some${path.sep}path${path.sep}catalog-info.yaml`,
@@ -24,17 +24,16 @@ export type BitbucketRepositoryParser = (options: {
logger: Logger;
}) => AsyncIterable<CatalogProcessorResult>;
export const defaultRepositoryParser: BitbucketRepositoryParser = async function* defaultRepositoryParser({
target,
}) {
yield results.location(
{
type: 'url',
target: target,
},
// Not all locations may actually exist, since the user defined them as a wildcard pattern.
// Thus, we emit them as optional and let the downstream processor find them while not outputting
// an error if it couldn't.
true,
);
};
export const defaultRepositoryParser: BitbucketRepositoryParser =
async function* defaultRepositoryParser({ target }) {
yield results.location(
{
type: 'url',
target: target,
},
// Not all locations may actually exist, since the user defined them as a wildcard pattern.
// Thus, we emit them as optional and let the downstream processor find them while not outputting
// an error if it couldn't.
true,
);
};
@@ -330,7 +330,7 @@ export async function queryWithPaging<
GraphqlType,
OutputType,
Variables extends {},
Response = QueryResponse
Response = QueryResponse,
>(
client: typeof graphql,
query: string,
@@ -58,11 +58,9 @@ export function* parseEntityYaml(
}
}
export const defaultEntityDataParser: CatalogProcessorParser = async function* defaultEntityDataParser({
data,
location,
}) {
for (const e of parseEntityYaml(data, location)) {
yield e;
}
};
export const defaultEntityDataParser: CatalogProcessorParser =
async function* defaultEntityDataParser({ data, location }) {
for (const e of parseEntityYaml(data, location)) {
yield e;
}
};
@@ -31,9 +31,9 @@ describe('ConfigLocationEntityProvider', () => {
},
});
const mockConnection = ({
const mockConnection = {
applyMutation: jest.fn(),
} as unknown) as EntityProviderConnection;
} as unknown as EntityProviderConnection;
const locationProvider = new ConfigLocationEntityProvider(mockConfig);
await locationProvider.connect(mockConnection);
@@ -22,17 +22,17 @@ import { CatalogProcessingOrchestrator } from './processing/types';
import { Stitcher } from './stitching/Stitcher';
describe('DefaultCatalogProcessingEngine', () => {
const db = ({
const db = {
transaction: jest.fn(),
getProcessableEntities: jest.fn(),
updateProcessedEntity: jest.fn(),
} as unknown) as jest.Mocked<DefaultProcessingDatabase>;
} as unknown as jest.Mocked<DefaultProcessingDatabase>;
const orchestrator: jest.Mocked<CatalogProcessingOrchestrator> = {
process: jest.fn(),
};
const stitcher = ({
const stitcher = {
stitch: jest.fn(),
} as unknown) as jest.Mocked<Stitcher>;
} as unknown as jest.Mocked<Stitcher>;
beforeEach(() => {
jest.resetAllMocks();
@@ -28,9 +28,10 @@ import {
DbSearchRow,
} from './database/tables';
function parsePagination(
input?: EntityPagination,
): { limit?: number; offset?: number } {
function parsePagination(input?: EntityPagination): {
limit?: number;
offset?: number;
} {
if (!input) {
return {};
}
@@ -47,13 +47,8 @@ export interface RouterOptions {
export async function createNextRouter(
options: RouterOptions,
): Promise<express.Router> {
const {
entitiesCatalog,
locationAnalyzer,
locationService,
config,
logger,
} = options;
const { entitiesCatalog, locationAnalyzer, locationService, config, logger } =
options;
const router = Router();
router.use(express.json());
@@ -79,10 +79,10 @@ describe('Default Processing Database', () => {
'updates refresh state with varying location keys, %p',
async databaseId => {
const mockWarn = jest.fn();
const { db } = await createDatabase(databaseId, ({
const { db } = await createDatabase(databaseId, {
debug: jest.fn(),
warn: mockWarn,
} as unknown) as Logger);
} as unknown as Logger);
await db.transaction(async tx => {
const knexTx = tx as Knex.Transaction;
@@ -54,7 +54,8 @@ type Context = {
};
export class DefaultCatalogProcessingOrchestrator
implements CatalogProcessingOrchestrator {
implements CatalogProcessingOrchestrator
{
constructor(
private readonly options: {
processors: CatalogProcessor[];
@@ -59,23 +59,21 @@ export class DefaultCatalogCollator implements DocumentCollator {
const baseUrl = await this.discovery.getBaseUrl('catalog');
const res = await fetch(`${baseUrl}/entities`);
const entities: Entity[] = await res.json();
return entities.map(
(entity: Entity): CatalogEntityDocument => {
return {
title: entity.metadata.name,
location: this.applyArgsToFormat(this.locationTemplate, {
namespace: entity.metadata.namespace || 'default',
kind: entity.kind,
name: entity.metadata.name,
}),
text: entity.metadata.description || '',
componentType: entity.spec?.type?.toString() || 'other',
return entities.map((entity: Entity): CatalogEntityDocument => {
return {
title: entity.metadata.name,
location: this.applyArgsToFormat(this.locationTemplate, {
namespace: entity.metadata.namespace || 'default',
kind: entity.kind,
lifecycle: (entity.spec?.lifecycle as string) || '',
owner: (entity.spec?.owner as string) || '',
};
},
);
name: entity.metadata.name,
}),
text: entity.metadata.description || '',
componentType: entity.spec?.type?.toString() || 'other',
namespace: entity.metadata.namespace || 'default',
kind: entity.kind,
lifecycle: (entity.spec?.lifecycle as string) || '',
owner: (entity.spec?.owner as string) || '',
};
});
}
}
@@ -60,8 +60,8 @@ describe('parseEntityTransformParams', () => {
expect(
parseEntityTransformParams({ fields: 'kind,metadata.name' })!(entity),
).toEqual({ kind: 'k', metadata: { name: 'n' } });
expect(
parseEntityTransformParams({ fields: 'metadata' })!(entity),
).toEqual({ metadata: { name: 'n', tags: ['t1', 't2'] } });
expect(parseEntityTransformParams({ fields: 'metadata' })!(entity)).toEqual(
{ metadata: { name: 'n', tags: ['t1', 't2'] } },
);
});
});
@@ -291,10 +291,10 @@ describe('createRouter readonly disabled', () => {
describe('POST /locations', () => {
it('rejects malformed locations', async () => {
const spec = ({
const spec = {
typez: 'b',
target: 'c',
} as unknown) as LocationSpec;
} as unknown as LocationSpec;
const response = await request(app).post('/locations').send(spec);
@@ -49,11 +49,8 @@ export async function startStandaloneServer(
config,
reader,
});
const {
entitiesCatalog,
locationsCatalog,
higherOrderOperation,
} = await builder.build();
const { entitiesCatalog, locationsCatalog, higherOrderOperation } =
await builder.build();
logger.debug('Starting application server...');
const router = await createRouter({
+1 -1
View File
@@ -52,7 +52,7 @@ export async function validateRequestBody<T>(
throw new InputError(`Malformed request: ${e}`);
}
return (body as unknown) as T;
return body as unknown as T;
}
export function disallowReadonlyMode(readonly: boolean) {
+16 -16
View File
@@ -219,7 +219,7 @@ export interface SubscriptionSubscriberObject<
TKey extends string,
TParent,
TContext,
TArgs
TArgs,
> {
subscribe: SubscriptionSubscribeFn<
{ [key in TKey]: TResult },
@@ -245,7 +245,7 @@ export type SubscriptionObject<
TKey extends string,
TParent,
TContext,
TArgs
TArgs,
> =
| SubscriptionSubscriberObject<TResult, TKey, TParent, TContext, TArgs>
| SubscriptionResolverObject<TResult, TParent, TContext, TArgs>;
@@ -255,7 +255,7 @@ export type SubscriptionResolver<
TKey extends string,
TParent = {},
TContext = {},
TArgs = {}
TArgs = {},
> =
| ((
...args: any[]
@@ -279,7 +279,7 @@ export type DirectiveResolverFn<
TResult = {},
TParent = {},
TContext = {},
TArgs = {}
TArgs = {},
> = (
next: NextResolverFn<TResult>,
parent: TParent,
@@ -364,7 +364,7 @@ export interface JsonObjectScalarConfig
export type EntityMetadataResolvers<
ContextType = ModuleContext,
ParentType = ResolversParentTypes['EntityMetadata']
ParentType = ResolversParentTypes['EntityMetadata'],
> = ResolversObject<{
__resolveType: TypeResolveFn<
'DefaultEntityMetadata' | 'ComponentMetadata' | 'TemplateMetadata',
@@ -393,7 +393,7 @@ export type EntityMetadataResolvers<
export type DefaultEntityMetadataResolvers<
ContextType = ModuleContext,
ParentType = ResolversParentTypes['DefaultEntityMetadata']
ParentType = ResolversParentTypes['DefaultEntityMetadata'],
> = ResolversObject<{
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
annotations?: Resolver<ResolversTypes['JSONObject'], ParentType, ContextType>;
@@ -418,7 +418,7 @@ export type DefaultEntityMetadataResolvers<
export type ComponentMetadataResolvers<
ContextType = ModuleContext,
ParentType = ResolversParentTypes['ComponentMetadata']
ParentType = ResolversParentTypes['ComponentMetadata'],
> = ResolversObject<{
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
annotations?: Resolver<ResolversTypes['JSONObject'], ParentType, ContextType>;
@@ -448,7 +448,7 @@ export type ComponentMetadataResolvers<
export type TemplateMetadataResolvers<
ContextType = ModuleContext,
ParentType = ResolversParentTypes['TemplateMetadata']
ParentType = ResolversParentTypes['TemplateMetadata'],
> = ResolversObject<{
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
annotations?: Resolver<ResolversTypes['JSONObject'], ParentType, ContextType>;
@@ -478,7 +478,7 @@ export type TemplateMetadataResolvers<
export type TemplateEntitySpecResolvers<
ContextType = ModuleContext,
ParentType = ResolversParentTypes['TemplateEntitySpec']
ParentType = ResolversParentTypes['TemplateEntitySpec'],
> = ResolversObject<{
type?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
path?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
@@ -489,7 +489,7 @@ export type TemplateEntitySpecResolvers<
export type ComponentEntitySpecResolvers<
ContextType = ModuleContext,
ParentType = ResolversParentTypes['ComponentEntitySpec']
ParentType = ResolversParentTypes['ComponentEntitySpec'],
> = ResolversObject<{
title?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
lifecycle?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
@@ -499,7 +499,7 @@ export type ComponentEntitySpecResolvers<
export type LocationEntitySpecResolvers<
ContextType = ModuleContext,
ParentType = ResolversParentTypes['LocationEntitySpec']
ParentType = ResolversParentTypes['LocationEntitySpec'],
> = ResolversObject<{
type?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
target?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
@@ -509,7 +509,7 @@ export type LocationEntitySpecResolvers<
export type DefaultEntitySpecResolvers<
ContextType = ModuleContext,
ParentType = ResolversParentTypes['DefaultEntitySpec']
ParentType = ResolversParentTypes['DefaultEntitySpec'],
> = ResolversObject<{
raw?: Resolver<Maybe<ResolversTypes['JSONObject']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType>;
@@ -517,7 +517,7 @@ export type DefaultEntitySpecResolvers<
export type EntitySpecResolvers<
ContextType = ModuleContext,
ParentType = ResolversParentTypes['EntitySpec']
ParentType = ResolversParentTypes['EntitySpec'],
> = ResolversObject<{
__resolveType: TypeResolveFn<
| 'DefaultEntitySpec'
@@ -531,7 +531,7 @@ export type EntitySpecResolvers<
export type CatalogEntityResolvers<
ContextType = ModuleContext,
ParentType = ResolversParentTypes['CatalogEntity']
ParentType = ResolversParentTypes['CatalogEntity'],
> = ResolversObject<{
apiVersion?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
kind?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
@@ -546,7 +546,7 @@ export type CatalogEntityResolvers<
export type CatalogQueryResolvers<
ContextType = ModuleContext,
ParentType = ResolversParentTypes['CatalogQuery']
ParentType = ResolversParentTypes['CatalogQuery'],
> = ResolversObject<{
list?: Resolver<
Array<ResolversTypes['CatalogEntity']>,
@@ -558,7 +558,7 @@ export type CatalogQueryResolvers<
export type QueryResolvers<
ContextType = ModuleContext,
ParentType = ResolversParentTypes['Query']
ParentType = ResolversParentTypes['Query'],
> = ResolversObject<{
catalog?: Resolver<ResolversTypes['CatalogQuery'], ParentType, ContextType>;
}>;
+11
View File
@@ -1,5 +1,16 @@
# @backstage/plugin-catalog-import
## 0.5.16
### Patch Changes
- Updated dependencies
- @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
## 0.5.15
### Patch Changes
+1 -1
View File
@@ -178,7 +178,7 @@ export const ImportStepper: ({
//
// @public
export const PreparePullRequestForm: <
TFieldValues extends Record<string, any>
TFieldValues extends Record<string, any>,
>({
defaultValues,
onSubmit,
+10 -10
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-import",
"version": "0.5.15",
"version": "0.5.16",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -32,17 +32,17 @@
"dependencies": {
"@backstage/catalog-client": "^0.3.18",
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-components": "^0.2.0",
"@backstage/core-plugin-api": "^0.1.4",
"@backstage/integration": "^0.5.8",
"@backstage/integration-react": "^0.1.5",
"@backstage/plugin-catalog-react": "^0.4.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/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": "^16.9",
"@types/react": "*",
"git-url-parse": "~11.4.4",
"js-base64": "^3.6.0",
"react": "^16.13.1",
@@ -54,9 +54,9 @@
"yaml": "^1.10.0"
},
"devDependencies": {
"@backstage/cli": "^0.7.6",
"@backstage/core-app-api": "^0.1.6",
"@backstage/dev-utils": "^0.2.4",
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
@@ -192,7 +192,7 @@ describe('CatalogImportClient', () => {
});
it('should find locations from github', async () => {
((new Octokit().search.code as any) as jest.Mock).mockResolvedValueOnce({
(new Octokit().search.code as any as jest.Mock).mockResolvedValueOnce({
data: {
total_count: 2,
items: [
@@ -248,7 +248,7 @@ describe('CatalogImportClient', () => {
});
it('should find repository from github', async () => {
((new Octokit().search.code as any) as jest.Mock).mockResolvedValueOnce({
(new Octokit().search.code as any as jest.Mock).mockResolvedValueOnce({
data: { total_count: 0, items: [] },
});
@@ -312,7 +312,7 @@ describe('CatalogImportClient', () => {
});
expect(
((new Octokit().git.createRef as any) as jest.Mock).mock.calls[0][0],
(new Octokit().git.createRef as any as jest.Mock).mock.calls[0][0],
).toEqual({
owner: 'backstage',
repo: 'backstage',
@@ -320,7 +320,7 @@ describe('CatalogImportClient', () => {
sha: 'any',
});
expect(
((new Octokit().repos.createOrUpdateFileContents as any) as jest.Mock)
(new Octokit().repos.createOrUpdateFileContents as any as jest.Mock)
.mock.calls[0][0],
).toEqual({
owner: 'backstage',
@@ -331,7 +331,7 @@ describe('CatalogImportClient', () => {
branch: 'backstage-integration',
});
expect(
((new Octokit().pulls.create as any) as jest.Mock).mock.calls[0][0],
(new Octokit().pulls.create as any as jest.Mock).mock.calls[0][0],
).toEqual({
owner: 'backstage',
repo: 'backstage',
@@ -53,8 +53,7 @@ export const ImportComponentPage = (opts: StepperProviderOpts) => {
title="Register an existing component"
deepLink={{
title: 'Learn more about the Software Catalog',
link:
'https://backstage.io/docs/features/software-catalog/software-catalog-overview',
link: 'https://backstage.io/docs/features/software-catalog/software-catalog-overview',
}}
>
<Typography variant="body2" paragraph>
@@ -39,9 +39,10 @@ import { ConfigApi } from '@backstage/core-plugin-api';
export type StepperProviderOpts = {
pullRequest?: {
disable?: boolean;
preparePullRequest?: (
apis: StepperApis,
) => { title?: string; body?: string };
preparePullRequest?: (apis: StepperApis) => {
title?: string;
body?: string;
};
};
};
@@ -382,7 +382,7 @@ describe('<StepInitAnalyzeUrl />', () => {
);
catalogImportApi.analyzeUrl.mockReturnValueOnce(
Promise.resolve(({ type: 'unknown' } as any) as AnalyzeResult),
Promise.resolve({ type: 'unknown' } as any as AnalyzeResult),
);
await act(async () => {
@@ -49,19 +49,14 @@ type Props<TFieldValues extends Record<string, any>> = Pick<
* @param render render the form elements
*/
export const PreparePullRequestForm = <
TFieldValues extends Record<string, any>
TFieldValues extends Record<string, any>,
>({
defaultValues,
onSubmit,
render,
}: Props<TFieldValues>) => {
const {
handleSubmit,
watch,
control,
register,
errors,
} = useForm<TFieldValues>({ mode: 'onTouched', defaultValues });
const { handleSubmit, watch, control, register, errors } =
useForm<TFieldValues>({ mode: 'onTouched', defaultValues });
return (
<form onSubmit={handleSubmit(onSubmit)}>
+10
View File
@@ -1,5 +1,15 @@
# @backstage/plugin-catalog-react
## 0.4.1
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.3.0
- @backstage/core-plugin-api@0.1.5
- @backstage/integration@0.5.9
- @backstage/core-app-api@0.1.7
## 0.4.0
### Minor Changes
+28 -6
View File
@@ -10,9 +10,11 @@ import { AsyncState } from 'react-use/lib/useAsync';
import { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client';
import { CatalogApi } from '@backstage/catalog-client';
import { ComponentEntity } from '@backstage/catalog-model';
import { ComponentProps } from 'react';
import { Context } from 'react';
import { Entity } from '@backstage/catalog-model';
import { EntityName } from '@backstage/catalog-model';
import { IconButton } from '@material-ui/core';
import { LinkProps } from '@backstage/core-components';
import { PropsWithChildren } from 'react';
import { default as React_2 } from 'react';
@@ -116,7 +118,10 @@ export const EntityContext: Context<EntityLoadingStatus>;
//
// @public (undocumented)
export type EntityFilter = {
getCatalogFilters?: () => Record<string, string | string[]>;
getCatalogFilters?: () => Record<
string,
string | symbol | (string | symbol)[]
>;
filterEntity?: (entity: Entity) => boolean;
toQueryValue?: () => string | string[];
};
@@ -516,9 +521,7 @@ export const entityRoute: RouteRef<{
// Warning: (ae-missing-release-tag) "entityRouteParams" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export function entityRouteParams(
entity: Entity,
): {
export function entityRouteParams(entity: Entity): {
readonly kind: string;
readonly namespace: string;
readonly name: string;
@@ -620,6 +623,25 @@ export class EntityTypeFilter implements EntityFilter {
// @public (undocumented)
export const EntityTypePicker: () => JSX.Element | null;
// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "FavoriteEntity" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
export const FavoriteEntity: (props: Props_2) => JSX.Element;
// Warning: (ae-missing-release-tag) "favoriteEntityIcon" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const favoriteEntityIcon: (isStarred: boolean) => JSX.Element;
// Warning: (ae-missing-release-tag) "favoriteEntityTooltip" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const favoriteEntityTooltip: (
isStarred: boolean,
) => 'Remove from favorites' | 'Add to favorites';
// Warning: (ae-missing-release-tag) "formatEntityRefTitle" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -679,7 +701,7 @@ export const MockEntityListContextProvider: ({
// @public (undocumented)
export function reduceCatalogFilters(
filters: EntityFilter[],
): Record<string, string | string[]>;
): Record<string, string | symbol | (string | symbol)[]>;
// Warning: (ae-missing-release-tag) "reduceEntityFilters" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -721,7 +743,7 @@ export const useEntityFromUrl: () => EntityLoadingStatus;
//
// @public (undocumented)
export function useEntityListProvider<
EntityFilters extends DefaultEntityFilters = DefaultEntityFilters
EntityFilters extends DefaultEntityFilters = DefaultEntityFilters,
>(): EntityListContextProps<EntityFilters>;
// Warning: (ae-missing-release-tag) "useEntityOwnership" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-react",
"version": "0.4.0",
"version": "0.4.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -30,14 +30,14 @@
"dependencies": {
"@backstage/catalog-client": "^0.3.18",
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-app-api": "^0.1.6",
"@backstage/core-components": "^0.2.0",
"@backstage/core-plugin-api": "^0.1.4",
"@backstage/integration": "^0.5.8",
"@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",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@types/react": "^16.9",
"@types/react": "*",
"jwt-decode": "^3.1.0",
"lodash": "^4.17.15",
"qs": "^6.9.4",
@@ -47,8 +47,8 @@
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.6",
"@backstage/dev-utils": "^0.2.4",
"@backstage/cli": "^0.7.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
@@ -34,12 +34,8 @@ const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
const checkedIcon = <CheckBoxIcon fontSize="small" />;
export const EntityLifecyclePicker = () => {
const {
updateFilters,
backendEntities,
filters,
queryParameters,
} = useEntityListProvider();
const { updateFilters, backendEntities, filters, queryParameters } =
useEntityListProvider();
const queryParamLifecycles = [queryParameters.lifecycles]
.flat()
@@ -36,12 +36,8 @@ const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
const checkedIcon = <CheckBoxIcon fontSize="small" />;
export const EntityOwnerPicker = () => {
const {
updateFilters,
backendEntities,
filters,
queryParameters,
} = useEntityListProvider();
const { updateFilters, backendEntities, filters, queryParameters } =
useEntityListProvider();
const queryParamOwners = [queryParameters.owners]
.flat()
@@ -134,7 +134,7 @@ export function createSystemColumn<T extends Entity>(): TableColumn<T> {
}
export function createMetadataDescriptionColumn<
T extends Entity
T extends Entity,
>(): TableColumn<T> {
return {
title: 'Description',
@@ -34,12 +34,8 @@ const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
const checkedIcon = <CheckBoxIcon fontSize="small" />;
export const EntityTagPicker = () => {
const {
updateFilters,
backendEntities,
filters,
queryParameters,
} = useEntityListProvider();
const { updateFilters, backendEntities, filters, queryParameters } =
useEntityListProvider();
const queryParamTags = [queryParameters.tags]
.flat()
@@ -63,17 +63,17 @@ const entities: Entity[] = [
const apis = ApiRegistry.from([
[
catalogApiRef,
({
{
getEntities: jest
.fn()
.mockImplementation(() => Promise.resolve({ items: entities })),
} as unknown) as CatalogApi,
} as unknown as CatalogApi,
],
[
alertApiRef,
({
{
post: jest.fn(),
} as unknown) as AlertApi,
} as unknown as AlertApi,
],
]);
@@ -24,12 +24,8 @@ import { Select } from '@backstage/core-components';
export const EntityTypePicker = () => {
const alertApi = useApi(alertApiRef);
const {
error,
availableTypes,
selectedTypes,
setSelectedTypes,
} = useEntityTypeFilter();
const { error, availableTypes, selectedTypes, setSelectedTypes } =
useEntityTypeFilter();
useEffect(() => {
if (error) {
@@ -15,7 +15,7 @@
*/
import React, { ComponentProps } from 'react';
import { useStarredEntities } from '@backstage/plugin-catalog-react';
import { useStarredEntities } from '../../hooks/useStarredEntities';
import { IconButton, Tooltip, withStyles } from '@material-ui/core';
import StarBorder from '@material-ui/icons/StarBorder';
import Star from '@material-ui/icons/Star';
@@ -29,17 +29,17 @@ const YellowStar = withStyles({
},
})(Star);
export const favouriteEntityTooltip = (isStarred: boolean) =>
export const favoriteEntityTooltip = (isStarred: boolean) =>
isStarred ? 'Remove from favorites' : 'Add to favorites';
export const favouriteEntityIcon = (isStarred: boolean) =>
export const favoriteEntityIcon = (isStarred: boolean) =>
isStarred ? <YellowStar /> : <StarBorder />;
/**
* IconButton for showing if a current entity is starred and adding/removing it from the favourite entities
* IconButton for showing if a current entity is starred and adding/removing it from the favorite entities
* @param props MaterialUI IconButton props extended by required `entity` prop
*/
export const FavouriteEntity = (props: Props) => {
export const FavoriteEntity = (props: Props) => {
const { toggleStarredEntity, isStarredEntity } = useStarredEntities();
const isStarred = isStarredEntity(props.entity);
return (
@@ -48,8 +48,8 @@ export const FavouriteEntity = (props: Props) => {
{...props}
onClick={() => toggleStarredEntity(props.entity)}
>
<Tooltip title={favouriteEntityTooltip(isStarred)}>
{favouriteEntityIcon(isStarred)}
<Tooltip title={favoriteEntityTooltip(isStarred)}>
{favoriteEntityIcon(isStarred)}
</Tooltip>
</IconButton>
);
@@ -1,5 +1,5 @@
/*
* Copyright 2020 The Backstage Authors
* 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.
@@ -15,8 +15,7 @@
*/
export {
registerComponentPlugin,
registerComponentPlugin as plugin,
RegisterComponentPage,
} from './plugin';
export { Router } from './components/Router';
favoriteEntityTooltip,
favoriteEntityIcon,
FavoriteEntity,
} from './FavoriteEntity';
@@ -129,12 +129,8 @@ export const UserListPicker = ({
}))
.filter(({ items }) => !!items.length);
const {
filters,
updateFilters,
backendEntities,
queryParameters,
} = useEntityListProvider();
const { filters, updateFilters, backendEntities, queryParameters } =
useEntityListProvider();
const { isStarredEntity } = useStarredEntities();
const { isOwnedEntity } = useEntityOwnership();
@@ -166,9 +162,8 @@ export const UserListPicker = ({
// To show proper counts for each section, apply all other frontend filters _except_ the user
// filter that's controlled by this picker.
const [entitiesWithoutUserFilter, setEntitiesWithoutUserFilter] = useState(
backendEntities,
);
const [entitiesWithoutUserFilter, setEntitiesWithoutUserFilter] =
useState(backendEntities);
useEffect(() => {
const filterFn = reduceEntityFilters(
compact(Object.values({ ...filters, user: undefined })),
@@ -22,4 +22,5 @@ export * from './EntitySearchBar';
export * from './EntityTable';
export * from './EntityTagPicker';
export * from './EntityTypePicker';
export * from './FavoriteEntity';
export * from './UserListPicker';
+6 -1
View File
@@ -41,7 +41,12 @@ export const useEntityFromUrl = (): EntityLoadingStatus => {
const errorApi = useApi(errorApiRef);
const catalogApi = useApi(catalogApiRef);
const { value: entity, error, loading, retry: refresh } = useAsyncRetry(
const {
value: entity,
error,
loading,
retry: refresh,
} = useAsyncRetry(
() => catalogApi.getEntityByName({ kind, namespace, name }),
[catalogApi, kind, namespace, name],
);
@@ -51,7 +51,7 @@ export type DefaultEntityFilters = {
};
export type EntityListContextProps<
EntityFilters extends DefaultEntityFilters = DefaultEntityFilters
EntityFilters extends DefaultEntityFilters = DefaultEntityFilters,
> = {
/**
* The currently registered filters, adhering to the shape of DefaultEntityFilters or an extension
@@ -215,7 +215,7 @@ export const EntityListProvider = <EntityFilters extends DefaultEntityFilters>({
};
export function useEntityListProvider<
EntityFilters extends DefaultEntityFilters = DefaultEntityFilters
EntityFilters extends DefaultEntityFilters = DefaultEntityFilters,
>(): EntityListContextProps<EntityFilters> {
const context = useContext(EntityListContext);
if (!context)
@@ -46,8 +46,8 @@ describe('useEntityOwnership', () => {
getEntityByName: jest.fn(),
};
const identityApi = (mockIdentityApi as unknown) as IdentityApi;
const catalogApi = (mockCatalogApi as unknown) as CatalogApi;
const identityApi = mockIdentityApi as unknown as IdentityApi;
const catalogApi = mockCatalogApi as unknown as CatalogApi;
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
<ApiProvider
@@ -131,9 +131,10 @@ export function useEntityOwnership(): {
const isOwnedEntity = useMemo(() => {
const myOwnerRefs = new Set(refs ?? []);
return (entity: Entity | EntityName) => {
const entityOwnerRefs = ('metadata' in entity
? getEntityRelations(entity, RELATION_OWNED_BY)
: [entity]
const entityOwnerRefs = (
'metadata' in entity
? getEntityRelations(entity, RELATION_OWNED_BY)
: [entity]
).map(stringifyEntityRef);
for (const ref of entityOwnerRefs) {
if (myOwnerRefs.has(ref)) {
@@ -53,7 +53,11 @@ export function useEntityTypeFilter(): EntityTypeReturn {
// Load all valid spec.type values straight from the catalogApi, paying attention to only the
// kind filter for a complete list.
const { error, loading, value: entities } = useAsync(async () => {
const {
error,
loading,
value: entities,
} = useAsync(async () => {
if (kind) {
const items = await catalogApi
.getEntities({
@@ -30,7 +30,11 @@ export function useRelatedEntities(
error: Error | undefined;
} {
const catalogApi = useApi(catalogApiRef);
const { loading, value: entities, error } = useAsync(async () => {
const {
loading,
value: entities,
error,
} = useAsync(async () => {
const relations =
entity.relations &&
entity.relations.filter(
+4 -1
View File
@@ -23,7 +23,10 @@ export type EntityFilter = {
* { field: 'kind', values: ['component'] }
* { field: 'metadata.name', values: ['component-1', 'component-2'] }
*/
getCatalogFilters?: () => Record<string, string | string[]>;
getCatalogFilters?: () => Record<
string,
string | symbol | (string | symbol)[]
>;
/**
* Filter entities on the frontend after a catalog-backend request. This function will be called
+2 -2
View File
@@ -19,13 +19,13 @@ import { EntityFilter } from '../types';
export function reduceCatalogFilters(
filters: EntityFilter[],
): Record<string, string | string[]> {
): Record<string, string | symbol | (string | symbol)[]> {
return filters.reduce((compoundFilter, filter) => {
return {
...compoundFilter,
...(filter.getCatalogFilters ? filter.getCatalogFilters() : {}),
};
}, {} as Record<string, string | string[]>);
}, {} as Record<string, string | symbol | (string | symbol)[]>);
}
export function reduceEntityFilters(
+13
View File
@@ -1,5 +1,18 @@
# @backstage/plugin-catalog
## 0.6.10
### Patch Changes
- cf4635f93: Fix bug with re-rendering the EntityRoutes when the entity changes but the route does not
- 7b8aa8d0d: Move the `CreateComponentButton` from the catalog plugin to the `core-components` & rename it to `CreateButton` to be reused inside the api-docs plugin & scaffolder plugin, but also future plugins. Additionally, improve responsiveness of `CreateButton` & `SupportButton` by shrinking them to `IconButtons` on smaller screens.
- Updated dependencies
- @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
## 0.6.9
### Patch Changes
+1 -1
View File
@@ -80,7 +80,7 @@ sidebar:
export const Root = ({ children }: PropsWithChildren<{}>) => (
<SidebarPage>
<Sidebar>
+ <SidebarItem icon={HomeIcon} to="/catalog" text="Home" />
+ <SidebarItem icon={HomeIcon} to="catalog" text="Home" />
...
</Sidebar>
```
+10 -10
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog",
"version": "0.6.9",
"version": "0.6.10",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -32,17 +32,17 @@
"dependencies": {
"@backstage/catalog-client": "^0.3.18",
"@backstage/catalog-model": "^0.9.0",
"@backstage/core-components": "^0.2.0",
"@backstage/core-plugin-api": "^0.1.4",
"@backstage/core-components": "^0.3.0",
"@backstage/core-plugin-api": "^0.1.5",
"@backstage/errors": "^0.1.1",
"@backstage/integration": "^0.5.8",
"@backstage/integration-react": "^0.1.5",
"@backstage/plugin-catalog-react": "^0.4.0",
"@backstage/integration": "^0.5.9",
"@backstage/integration-react": "^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",
"@types/react": "^16.9",
"@types/react": "*",
"classnames": "^2.2.6",
"git-url-parse": "~11.4.4",
"lodash": "^4.17.21",
@@ -54,9 +54,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.6",
"@backstage/core-app-api": "^0.1.6",
"@backstage/dev-utils": "^0.2.4",
"@backstage/cli": "^0.7.7",
"@backstage/core-app-api": "^0.1.7",
"@backstage/dev-utils": "^0.2.5",
"@backstage/test-utils": "^0.1.16",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
@@ -15,6 +15,8 @@
*/
import { RELATION_OWNED_BY, RELATION_PART_OF } from '@backstage/catalog-model';
import {
favoriteEntityIcon,
favoriteEntityTooltip,
formatEntityRefTitle,
getEntityMetadataEditUrl,
getEntityMetadataViewUrl,
@@ -26,10 +28,6 @@ import Edit from '@material-ui/icons/Edit';
import OpenInNew from '@material-ui/icons/OpenInNew';
import { capitalize } from 'lodash';
import React from 'react';
import {
favouriteEntityIcon,
favouriteEntityTooltip,
} from '../FavouriteEntity/FavouriteEntity';
import * as columnFactories from './columns';
import { EntityRow } from './types';
import {
@@ -105,8 +103,8 @@ export const CatalogTable = ({ columns, actions }: CatalogTableProps) => {
const isStarred = isStarredEntity(entity);
return {
cellStyle: { paddingLeft: '1em' },
icon: () => favouriteEntityIcon(isStarred),
tooltip: favouriteEntityTooltip(isStarred),
icon: () => favoriteEntityIcon(isStarred),
tooltip: favoriteEntityTooltip(isStarred),
onClick: () => toggleStarredEntity(entity),
};
},
@@ -35,6 +35,7 @@ import {
import {
EntityContext,
EntityRefLinks,
FavoriteEntity,
getEntityRelations,
useEntityCompoundName,
} from '@backstage/plugin-catalog-react';
@@ -43,7 +44,6 @@ import { Alert } from '@material-ui/lab';
import React, { useContext, useState } from 'react';
import { useNavigate } from 'react-router';
import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu';
import { FavouriteEntity } from '../FavouriteEntity/FavouriteEntity';
import { UnregisterEntityDialog } from '../UnregisterEntityDialog/UnregisterEntityDialog';
type SubRoute = {
@@ -79,7 +79,7 @@ const EntityLayoutTitle = ({
>
{title}
</Box>
{entity && <FavouriteEntity entity={entity} />}
{entity && <FavoriteEntity entity={entity} />}
</Box>
);
};
@@ -21,6 +21,7 @@ import {
import {
EntityContext,
EntityRefLinks,
FavoriteEntity,
getEntityRelations,
useEntityCompoundName,
} from '@backstage/plugin-catalog-react';
@@ -28,7 +29,6 @@ import { Box } from '@material-ui/core';
import React, { useContext, useState } from 'react';
import { useNavigate } from 'react-router';
import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu';
import { FavouriteEntity } from '../FavouriteEntity/FavouriteEntity';
import { UnregisterEntityDialog } from '../UnregisterEntityDialog/UnregisterEntityDialog';
import { Tabbed } from './Tabbed';
@@ -54,7 +54,7 @@ const EntityPageTitle = ({
}) => (
<Box display="inline-flex" alignItems="center" height="1em">
{title}
{entity && <FavouriteEntity entity={entity} />}
{entity && <FavoriteEntity entity={entity} />}
</Box>
);
@@ -158,7 +158,11 @@ export function SystemDiagramCard() {
const systemEdges = new Array<{ from: string; to: string; label: string }>();
const catalogApi = useApi(catalogApiRef);
const { loading, error, value: catalogResponse } = useAsync(() => {
const {
loading,
error,
value: catalogResponse,
} = useAsync(() => {
return catalogApi.getEntities({
filter: {
kind: ['Component', 'API', 'Resource', 'System', 'Domain'],
@@ -47,7 +47,7 @@ describe('useUnregisterEntityDialogState', () => {
removeLocationById: jest.fn(),
removeEntityByUid: jest.fn(),
};
const catalogApi = (catalogApiMock as Partial<CatalogApi>) as CatalogApi;
const catalogApi = catalogApiMock as Partial<CatalogApi> as CatalogApi;
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
<ApiProvider apis={ApiRegistry.with(catalogApiRef, catalogApi)}>

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