Merge branch 'master' into techdocs-common/case-sensitive-migration
Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
+14
-11
@@ -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,
|
||||
},
|
||||
|
||||
+23
-18
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
>;
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
+3
-6
@@ -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',
|
||||
},
|
||||
],
|
||||
|
||||
+4
-3
@@ -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 {
|
||||
|
||||
+10
-9
@@ -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`,
|
||||
|
||||
+13
-14
@@ -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({
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>;
|
||||
}>;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -178,7 +178,7 @@ export const ImportStepper: ({
|
||||
//
|
||||
// @public
|
||||
export const PreparePullRequestForm: <
|
||||
TFieldValues extends Record<string, any>
|
||||
TFieldValues extends Record<string, any>,
|
||||
>({
|
||||
defaultValues,
|
||||
onSubmit,
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -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 () => {
|
||||
|
||||
+3
-8
@@ -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)}>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
+2
-6
@@ -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) {
|
||||
|
||||
+7
-7
@@ -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>
|
||||
);
|
||||
+5
-6
@@ -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';
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
```
|
||||
|
||||
@@ -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'],
|
||||
|
||||
+1
-1
@@ -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
Reference in New Issue
Block a user