From 1e828e1f3e16b688ddf30b018cd8e36eb9dae88f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Jan 2023 18:22:24 +0000 Subject: [PATCH 001/255] chore(deps): update dependency @testing-library/cypress to v9 Signed-off-by: Renovate Bot --- packages/app/package.json | 2 +- yarn.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index bdf019f107..ac42281a78 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -83,7 +83,7 @@ }, "devDependencies": { "@backstage/test-utils": "workspace:^", - "@testing-library/cypress": "^8.0.2", + "@testing-library/cypress": "^9.0.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^14.0.0", diff --git a/yarn.lock b/yarn.lock index f0435a6f9e..67f2dac999 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13624,15 +13624,15 @@ __metadata: languageName: unknown linkType: soft -"@testing-library/cypress@npm:^8.0.2": - version: 8.0.7 - resolution: "@testing-library/cypress@npm:8.0.7" +"@testing-library/cypress@npm:^9.0.0": + version: 9.0.0 + resolution: "@testing-library/cypress@npm:9.0.0" dependencies: "@babel/runtime": ^7.14.6 "@testing-library/dom": ^8.1.0 peerDependencies: - cypress: ^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 - checksum: e005bc1a7ec808706c57e95ed312069fb5be39ea7362900dc2a32c09f124d478ade69ebcd7df88c076e3867ab328ae6e6ce13791bdf042621ff66b56552bf74b + cypress: ^12.0.0 + checksum: fbd24e8f0b8a60279b336de5f6bc0e7ad6fb31316eacab5128dacc7fccde1eb40935b90f2c3bddc7d814115fe3965c6dbf011785448cd15b5a5b0bc40ef5bb4c languageName: node linkType: hard @@ -22304,7 +22304,7 @@ __metadata: "@roadiehq/backstage-plugin-github-insights": ^2.0.5 "@roadiehq/backstage-plugin-github-pull-requests": ^2.2.7 "@roadiehq/backstage-plugin-travis-ci": ^2.0.5 - "@testing-library/cypress": ^8.0.2 + "@testing-library/cypress": ^9.0.0 "@testing-library/jest-dom": ^5.10.1 "@testing-library/react": ^12.1.3 "@testing-library/user-event": ^14.0.0 From 016f6714c615c87f86fbb99709f1ff82d2e5f3e3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 5 Jan 2023 11:28:33 +0000 Subject: [PATCH 002/255] chore(deps): update docker/build-push-action action to v3 Signed-off-by: Renovate Bot --- .github/workflows/uffizzi-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 1a86f8de71..f58e31c688 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -58,7 +58,7 @@ jobs: tags: type=raw,value=60d - name: Build Image - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 with: context: . file: packages/backend/Dockerfile From c8e09cc383da3b2126b69aa8b8f64449b1cf78fb Mon Sep 17 00:00:00 2001 From: Gabriel Testault Date: Thu, 5 Jan 2023 16:16:48 +0100 Subject: [PATCH 003/255] fix(techdocs): Fixed bug in Techdocs reader where a techdocs page with a hash in the URL did not always jump to the document anchor. the scrollIntoAnchor transformer now jumps to the anchor only after css waas loaded instead of jumping after 200ms. Signed-off-by: Gabriel Testault --- .changeset/gold-masks-sleep.md | 5 + .../transformers/scrollIntoAnchor.test.ts | 92 +++++++++++++++---- .../reader/transformers/scrollIntoAnchor.ts | 23 +++-- 3 files changed, 95 insertions(+), 25 deletions(-) create mode 100644 .changeset/gold-masks-sleep.md diff --git a/.changeset/gold-masks-sleep.md b/.changeset/gold-masks-sleep.md new file mode 100644 index 0000000000..425a1abdc0 --- /dev/null +++ b/.changeset/gold-masks-sleep.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Fixed bug in Techdocs reader where a techdocs page with a hash in the URL did not always jump to the document anchor. diff --git a/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.test.ts b/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.test.ts index 896f909be4..e222c24a4c 100644 --- a/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.test.ts +++ b/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.test.ts @@ -15,46 +15,104 @@ */ import { scrollIntoAnchor } from '../transformers'; - -jest.useFakeTimers(); +import { createTestShadowDom } from '../../test-utils'; +import { Transformer } from './transformer'; +import mkdocsIndex from '../../test-utils/fixtures/mkdocs-index'; +import { SHADOW_DOM_STYLE_LOAD_EVENT } from '@backstage/plugin-techdocs-react'; describe('scrollIntoAnchor', () => { - const transformer = scrollIntoAnchor(); - const dom = { querySelector: jest.fn() }; + const scrollIntoView = jest.fn(); + let querySelectorSpy: jest.SpyInstance; + let addEventListenerSpy: jest.SpyInstance; + let removeEventListenerSpy: jest.SpyInstance; + const applySpies: Transformer = dom => { + querySelectorSpy = jest.spyOn(dom, 'querySelector'); + querySelectorSpy.mockReturnValue({ scrollIntoView }); + addEventListenerSpy = jest.spyOn(dom, 'addEventListener'); + removeEventListenerSpy = jest.spyOn(dom, 'removeEventListener'); + return dom; + }; afterEach(() => { jest.clearAllMocks(); }); it('does nothing if there is no anchor element', async () => { - transformer(dom as unknown as Element); - jest.advanceTimersByTime(200); - expect(dom.querySelector).not.toHaveBeenCalled(); + await createTestShadowDom(mkdocsIndex, { + preTransformers: [], + postTransformers: [applySpies, scrollIntoAnchor()], + }); + expect(querySelectorSpy).not.toHaveBeenCalled(); + expect(addEventListenerSpy).toHaveBeenCalled(); + expect(removeEventListenerSpy).toHaveBeenCalled(); + expect(addEventListenerSpy).toHaveBeenCalledWith( + SHADOW_DOM_STYLE_LOAD_EVENT, + expect.any(Function), + ); + expect(removeEventListenerSpy).toHaveBeenCalledTimes(1); + expect(removeEventListenerSpy).toHaveBeenCalledWith( + SHADOW_DOM_STYLE_LOAD_EVENT, + expect.any(Function), + ); + // check that the same function is passed to both addEventListener and removeEventListener + expect(addEventListenerSpy.mock.calls[0][1]).toBe( + removeEventListenerSpy.mock.calls[0][1], + ); }); it('scroll to the hash anchor element', async () => { - const scrollIntoView = jest.fn(); - dom.querySelector.mockReturnValue({ scrollIntoView }); window.location.hash = '#hash'; - transformer(dom as unknown as Element); - jest.advanceTimersByTime(200); - expect(dom.querySelector).toHaveBeenCalledWith( + await createTestShadowDom(mkdocsIndex, { + preTransformers: [], + postTransformers: [applySpies, scrollIntoAnchor()], + }); + expect(querySelectorSpy).toHaveBeenCalledWith( expect.stringMatching('[id="hash"]'), ); expect(scrollIntoView).toHaveBeenCalledWith(); + expect(addEventListenerSpy).toHaveBeenCalledTimes(1); + expect(removeEventListenerSpy).toHaveBeenCalledTimes(1); + expect(addEventListenerSpy).toHaveBeenCalledWith( + SHADOW_DOM_STYLE_LOAD_EVENT, + expect.any(Function), + ); + expect(removeEventListenerSpy).toHaveBeenCalledTimes(1); + expect(removeEventListenerSpy).toHaveBeenCalledWith( + SHADOW_DOM_STYLE_LOAD_EVENT, + expect.any(Function), + ); + // check that the same function is passed to both addEventListener and removeEventListener + expect(addEventListenerSpy.mock.calls[0][1]).toBe( + removeEventListenerSpy.mock.calls[0][1], + ); window.location.hash = ''; }); it('works for anchor starting with number', async () => { - const scrollIntoView = jest.fn(); - dom.querySelector.mockReturnValue({ scrollIntoView }); + querySelectorSpy.mockReturnValue({ scrollIntoView }); window.location.hash = '#1-hash'; - transformer(dom as unknown as Element); - jest.advanceTimersByTime(200); - expect(dom.querySelector).toHaveBeenCalledWith( + await createTestShadowDom(mkdocsIndex, { + preTransformers: [], + postTransformers: [applySpies, scrollIntoAnchor()], + }); + expect(querySelectorSpy).toHaveBeenCalledWith( expect.stringMatching('[id="1-hash"]'), ); expect(scrollIntoView).toHaveBeenCalledWith(); + expect(addEventListenerSpy).toHaveBeenCalledTimes(1); + expect(addEventListenerSpy).toHaveBeenCalledWith( + SHADOW_DOM_STYLE_LOAD_EVENT, + expect.any(Function), + ); + expect(removeEventListenerSpy).toHaveBeenCalledTimes(1); + expect(removeEventListenerSpy).toHaveBeenCalledWith( + SHADOW_DOM_STYLE_LOAD_EVENT, + expect.any(Function), + ); + // check that the same function is passed to both addEventListener and removeEventListener + expect(addEventListenerSpy.mock.calls[0][1]).toBe( + removeEventListenerSpy.mock.calls[0][1], + ); window.location.hash = ''; }); }); diff --git a/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.ts b/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.ts index 7ba02bcdfc..0be6fefc0b 100644 --- a/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.ts +++ b/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.ts @@ -15,17 +15,24 @@ */ import type { Transformer } from './transformer'; +import { SHADOW_DOM_STYLE_LOAD_EVENT } from '@backstage/plugin-techdocs-react'; export const scrollIntoAnchor = (): Transformer => { return dom => { - setTimeout(() => { - // Scroll to the desired anchor on initial navigation - if (window.location.hash) { - const hash = window.location.hash.slice(1); - // fix invalid selector error for anchor starting with number - dom?.querySelector(`[id="${hash}"]`)?.scrollIntoView(); - } - }, 200); + dom.addEventListener( + SHADOW_DOM_STYLE_LOAD_EVENT, + function handleShadowDomStyleLoad() { + if (window.location.hash) { + const hash = window.location.hash.slice(1); + // fix invalid selector error for anchor starting with number + dom?.querySelector(`[id="${hash}"]`)?.scrollIntoView(); + } + dom.removeEventListener( + SHADOW_DOM_STYLE_LOAD_EVENT, + handleShadowDomStyleLoad, + ); + }, + ); return dom; }; }; From 0a8fefc3f8007f3c65df6569e4400bf3818e9ec0 Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Fri, 6 Jan 2023 13:46:22 -0700 Subject: [PATCH 004/255] Add note about protected headers in JWT Signed-off-by: Tim Hansen --- docs/auth/service-to-service-auth.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md index 7c501a8e9e..8d10fd7cc6 100644 --- a/docs/auth/service-to-service-auth.md +++ b/docs/auth/service-to-service-auth.md @@ -110,6 +110,9 @@ value of the configured key as the secret. It must also have the following paylo - `sub`: "backstage-server" (only this value supported currently) - `exp`: one hour from the time it was generated, in epoch seconds +> NOTE: The JWT must encode the `alg` header as a protected header, such as with +> [setProtectedHeader](https://github.com/panva/jose/blob/main/docs/classes/jwt_sign.SignJWT.md#setprotectedheader). + ## Granular Access Control We plan to build out the service-to-service auth to be much more powerful in the From 87ab76e55c6f1ff4936e51b23202af9fdd945749 Mon Sep 17 00:00:00 2001 From: Dang Huynh Date: Fri, 6 Jan 2023 15:23:38 -0600 Subject: [PATCH 005/255] Fixed AWS SDK V3 bug with s3ForcePathStyle Signed-off-by: Dang Huynh --- .changeset/cyan-deers-walk.md | 5 +++++ plugins/techdocs-node/src/stages/publish/awsS3.ts | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .changeset/cyan-deers-walk.md diff --git a/.changeset/cyan-deers-walk.md b/.changeset/cyan-deers-walk.md new file mode 100644 index 0000000000..2a9265090b --- /dev/null +++ b/.changeset/cyan-deers-walk.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-node': minor +--- + +Fixed bug caused by recent migration to AWS SDK V3 for techdocs-node. Instead of s3ForcePathStyle, forcePathStyle should be passed. diff --git a/plugins/techdocs-node/src/stages/publish/awsS3.ts b/plugins/techdocs-node/src/stages/publish/awsS3.ts index a13baef926..6b528cfd30 100644 --- a/plugins/techdocs-node/src/stages/publish/awsS3.ts +++ b/plugins/techdocs-node/src/stages/publish/awsS3.ts @@ -152,7 +152,7 @@ export class AwsS3Publish implements PublisherBase { // AWS forcePathStyle is an optional config. If missing, it defaults to false. Needs to be enabled for cases // where endpoint url points to locally hosted S3 compatible storage like Localstack - const s3ForcePathStyle = config.getOptionalBoolean( + const forcePathStyle = config.getOptionalBoolean( 'techdocs.publisher.awsS3.s3ForcePathStyle', ); @@ -161,7 +161,7 @@ export class AwsS3Publish implements PublisherBase { credentialDefaultProvider: () => sdkCredentialProvider, ...(region && { region }), ...(endpoint && { endpoint }), - ...(s3ForcePathStyle && { s3ForcePathStyle }), + ...(forcePathStyle && { forcePathStyle }), }); const legacyPathCasing = From 0f0f8bd6af3153c4203ceb515abbf553b2aca7db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 5 Jan 2023 16:09:07 +0100 Subject: [PATCH 006/255] minor updates to the entity peek-ahead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../CardActionComponents/EmailCardAction.tsx | 8 +- .../EntityCardActions.tsx | 11 +- .../CardActionComponents/GroupCardActions.tsx | 12 +- .../CardActionComponents/UserCardActions.tsx | 12 +- .../CardActionComponents/index.ts | 1 + .../EntityPeekAheadPopover.stories.tsx | 20 +-- .../EntityPeekAheadPopover.test.tsx | 21 ++-- .../EntityPeekAheadPopover.tsx | 116 ++++++++---------- 8 files changed, 81 insertions(+), 120 deletions(-) diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/EmailCardAction.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/EmailCardAction.tsx index 2fcb89eed1..16804426fb 100644 --- a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/EmailCardAction.tsx +++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/EmailCardAction.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { IconButton } from '@material-ui/core'; import EmailIcon from '@material-ui/icons/Email'; import React from 'react'; @@ -23,14 +24,13 @@ import { Link } from '@backstage/core-components'; * * @private */ -export const EmailCardAction = ({ email }: { email: string }) => { +export const EmailCardAction = (props: { email: string }) => { return ( diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/EntityCardActions.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/EntityCardActions.tsx index fc8b5f4007..3f8f67c668 100644 --- a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/EntityCardActions.tsx +++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/EntityCardActions.tsx @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { entityRouteRef } from '../../../routes'; import { IconButton } from '@material-ui/core'; import InfoIcon from '@material-ui/icons/Info'; import React from 'react'; import { useRouteRef } from '@backstage/core-plugin-api'; -import { Entity } from '@backstage/catalog-model'; +import { Entity, getCompoundEntityRef } from '@backstage/catalog-model'; import { Link } from '@backstage/core-components'; /** @@ -26,7 +27,7 @@ import { Link } from '@backstage/core-components'; * * @private */ -export const EntityCardActions = ({ entity }: { entity: Entity }) => { +export const EntityCardActions = (props: { entity: Entity }) => { const entityRoute = useRouteRef(entityRouteRef); return ( @@ -34,11 +35,7 @@ export const EntityCardActions = ({ entity }: { entity: Entity }) => { component={Link} aria-label="Show" title="Show details" - to={entityRoute({ - name: entity.metadata.name, - namespace: entity.metadata.namespace || 'default', - kind: entity.kind.toLocaleLowerCase('en-US'), - })} + to={entityRoute(getCompoundEntityRef(props.entity))} > diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/GroupCardActions.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/GroupCardActions.tsx index fca0763703..9bc840c112 100644 --- a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/GroupCardActions.tsx +++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/GroupCardActions.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { EmailCardAction } from './EmailCardAction'; import React from 'react'; import { GroupEntity } from '@backstage/catalog-model'; @@ -22,12 +23,7 @@ import { GroupEntity } from '@backstage/catalog-model'; * * @private */ -export const GroupCardActions = ({ entity }: { entity: GroupEntity }) => { - return ( - <> - {entity.spec.profile?.email && ( - - )} - - ); +export const GroupCardActions = (props: { entity: GroupEntity }) => { + const email = props.entity.spec.profile?.email; + return email ? : null; }; diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/UserCardActions.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/UserCardActions.tsx index 57801497f6..cdf0eea385 100644 --- a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/UserCardActions.tsx +++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/UserCardActions.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { EmailCardAction } from './EmailCardAction'; import React from 'react'; import { UserEntity } from '@backstage/catalog-model'; @@ -22,12 +23,7 @@ import { UserEntity } from '@backstage/catalog-model'; * * @private */ -export const UserCardActions = ({ entity }: { entity: UserEntity }) => { - return ( - <> - {entity.spec.profile?.email && ( - - )} - - ); +export const UserCardActions = (props: { entity: UserEntity }) => { + const email = props.entity.spec.profile?.email; + return email ? : null; }; diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/index.ts b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/index.ts index 75959026f7..ba2114d73d 100644 --- a/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/index.ts +++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/CardActionComponents/index.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export { EntityCardActions } from './EntityCardActions'; export { GroupCardActions } from './GroupCardActions'; export { UserCardActions } from './UserCardActions'; diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx index 132042452b..bee5587ea1 100644 --- a/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx +++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx @@ -33,12 +33,8 @@ import { Table, TableColumn } from '@backstage/core-components'; import { EntityRefLink } from '../EntityRefLink'; const mockCatalogApi = { - getEntityByRef: async (entityRef: CompoundEntityRef) => { - if ( - entityRef.namespace === 'default' && - entityRef.name === 'playback' && - entityRef.kind === 'component' - ) { + getEntityByRef: async (entityRef: string) => { + if (entityRef === 'component:default/playback') { return { kind: 'Component', metadata: { @@ -48,11 +44,7 @@ const mockCatalogApi = { }, }; } - if ( - entityRef.namespace === 'default' && - entityRef.name === 'fname.lname' && - entityRef.kind === 'user' - ) { + if (entityRef === 'user:default/fname.lname') { return { kind: 'User', metadata: { @@ -66,11 +58,7 @@ const mockCatalogApi = { }, }; } - if ( - entityRef.namespace === 'default' && - entityRef.name === 'slow.catalog.item' && - entityRef.kind === 'component' - ) { + if (entityRef === 'component:default/slow.catalog.item') { await new Promise(resolve => setTimeout(resolve, 3000)); return { kind: 'Component', diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.test.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.test.tsx index 2fc630313f..f665ca6c00 100644 --- a/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.test.tsx +++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.test.tsx @@ -14,26 +14,21 @@ * limitations under the License. */ -import { fireEvent, screen } from '@testing-library/react'; +import { screen } from '@testing-library/react'; +import user from '@testing-library/user-event'; import React from 'react'; import { EntityPeekAheadPopover } from './EntityPeekAheadPopover'; import { ApiProvider } from '@backstage/core-app-api'; import { TestApiRegistry, renderInTestApp } from '@backstage/test-utils'; import { catalogApiRef } from '../../api'; -import { CompoundEntityRef, Entity } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { CatalogApi } from '@backstage/catalog-client'; import { Button } from '@material-ui/core'; import { entityRouteRef } from '../../routes'; const catalogApi: Partial = { - getEntityByRef: async ( - entityRef: CompoundEntityRef, - ): Promise => { - if ( - entityRef.name === 'service1' && - entityRef.namespace === 'default' && - entityRef.kind === 'component' - ) { + getEntityByRef: async (entityRef: string): Promise => { + if (entityRef === 'component:default/service1') { return { apiVersion: '', kind: 'Component', @@ -71,14 +66,14 @@ describe('', () => { ); expect(screen.getByText('s1')).toBeInTheDocument(); expect(screen.queryByText('service1')).toBeNull(); - fireEvent.mouseOver(screen.getByTestId('popover1')); + user.hover(screen.getByTestId('popover1')); expect(await screen.findByText('service1')).toBeInTheDocument(); expect(screen.getByText('s2')).toBeInTheDocument(); expect(screen.queryByText('service2')).toBeNull(); - fireEvent.mouseOver(screen.getByTestId('popover2')); + user.hover(screen.getByTestId('popover2')); expect( - await screen.findByText('Error: service2 was not found'), + await screen.findByText('Error: component:default/service2 not found'), ).toBeInTheDocument(); }); }); diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.tsx index 05a818fa1f..dbdbe01a50 100644 --- a/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.tsx +++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.tsx @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import useAsyncFn from 'react-use/lib/useAsyncFn'; import { catalogApiRef } from '../../api'; -import React, { PropsWithChildren, useEffect, useState } from 'react'; +import React, { PropsWithChildren, useEffect, useMemo, useState } from 'react'; import HoverPopover from 'material-ui-popup-state/HoverPopover'; import { bindHover, @@ -33,11 +34,7 @@ import { Typography, } from '@material-ui/core'; import { useApiHolder } from '@backstage/core-plugin-api'; -import { - isGroupEntity, - isUserEntity, - parseEntityRef, -} from '@backstage/catalog-model'; +import { isGroupEntity, isUserEntity } from '@backstage/catalog-model'; import { Progress, ResponseErrorPanel } from '@backstage/core-components'; import { EntityCardActions, @@ -58,9 +55,6 @@ export type EntityPeekAheadPopoverProps = PropsWithChildren<{ const useStyles = makeStyles(() => { return { - trigger: { - display: 'inline-block', - }, popoverPaper: { width: '30em', }, @@ -90,27 +84,24 @@ export const EntityPeekAheadPopover = (props: EntityPeekAheadPopoverProps) => { variant: 'popover', popupId: 'entity-peek-ahead', }); - const compoundEntityRef = parseEntityRef(entityRef); const [isHovered, setIsHovered] = useState(false); - const debouncedHandleMouseEnter = debounce( - () => setIsHovered(true), - delayTime, + const debouncedHandleMouseEnter = useMemo( + () => debounce(() => setIsHovered(true), delayTime), + [delayTime], ); const [{ loading, error, value: entity }, load] = useAsyncFn(async () => { const catalogApi = apiHolder.get(catalogApiRef); if (catalogApi) { - const retrievedEntity = await catalogApi.getEntityByRef( - compoundEntityRef, - ); + const retrievedEntity = await catalogApi.getEntityByRef(entityRef); if (!retrievedEntity) { - throw new Error(`${compoundEntityRef.name} was not found`); + throw new Error(`${entityRef} not found`); } return retrievedEntity; } return undefined; - }, [apiHolder, compoundEntityRef]); + }, [apiHolder, entityRef]); const handleOnMouseLeave = () => { setIsHovered(false); @@ -146,61 +137,58 @@ export const EntityPeekAheadPopover = (props: EntityPeekAheadPopoverProps) => { }} onMouseLeave={handleOnMouseLeave} > - <> - {error && } - + + + {error && } {loading && } - - - {entity && ( - <> - - {compoundEntityRef.namespace} - - - {compoundEntityRef.name} - - {entity.kind} + {entity && ( + <> + + {entity.metadata.namespace} + + + {entity.metadata.name} + + + {entity.kind} + + {entity.metadata.description && ( {entity.metadata.description} - {entity.spec?.type} - - {(entity.metadata.tags || []) - .slice(0, maxTagChips) - .map(tag => { - return ; - })} - {entity.metadata.tags?.length && - entity.metadata.tags?.length > maxTagChips && ( - - - - )} - - - )} - - {!error && ( - - {entity && ( - <> - {isUserEntity(entity) && ( - - )} - {isGroupEntity(entity) && ( - - )} - - )} - + {entity.spec?.type} + + {(entity.metadata.tags || []) + .slice(0, maxTagChips) + .map(tag => { + return ; + })} + {entity.metadata.tags?.length && + entity.metadata.tags?.length > maxTagChips && ( + + + + )} + + )} - - + + {!error && entity && ( + + <> + {isUserEntity(entity) && } + {isGroupEntity(entity) && ( + + )} + + + + )} + )} From cb5a4d48498c3f4fbdb3f01ff7b406c50b8889dd Mon Sep 17 00:00:00 2001 From: Gabriel Testault Date: Tue, 10 Jan 2023 12:59:52 +0100 Subject: [PATCH 007/255] fix(techdocs): remove comment about implementation details Signed-off-by: Gabriel Testault --- plugins/techdocs/src/reader/transformers/scrollIntoAnchor.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.ts b/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.ts index 0be6fefc0b..97a18b7241 100644 --- a/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.ts +++ b/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.ts @@ -24,7 +24,6 @@ export const scrollIntoAnchor = (): Transformer => { function handleShadowDomStyleLoad() { if (window.location.hash) { const hash = window.location.hash.slice(1); - // fix invalid selector error for anchor starting with number dom?.querySelector(`[id="${hash}"]`)?.scrollIntoView(); } dom.removeEventListener( From 44c18b4d3f0cfb110c06f0a0903c047fbd7a1cc0 Mon Sep 17 00:00:00 2001 From: Jussi Hallila Date: Tue, 10 Jan 2023 13:40:38 +0100 Subject: [PATCH 008/255] Expose optional persistenceContext on TechInsights construction This enables integrators to provide their own database implementations for fact handling if something more suitable than Postgres/SQlite is needed. Signed-off-by: Jussi Hallila --- .changeset/rude-pumas-draw.md | 5 +++ plugins/tech-insights-backend/api-report.md | 41 +++++++++++++++++++ plugins/tech-insights-backend/src/index.ts | 1 + .../persistence/TechInsightsDatabase.ts | 5 +++ .../src/service/techInsightsContextBuilder.ts | 14 +++++-- 5 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 .changeset/rude-pumas-draw.md diff --git a/.changeset/rude-pumas-draw.md b/.changeset/rude-pumas-draw.md new file mode 100644 index 0000000000..df3592ece8 --- /dev/null +++ b/.changeset/rude-pumas-draw.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-tech-insights-backend': patch +--- + +Expose optional persistenceContext on TechInsights construction to enable integrators to provide their own database implementations for fact handling. diff --git a/plugins/tech-insights-backend/api-report.md b/plugins/tech-insights-backend/api-report.md index 8dc8efbb24..faca4a615c 100644 --- a/plugins/tech-insights-backend/api-report.md +++ b/plugins/tech-insights-backend/api-report.md @@ -5,6 +5,7 @@ ```ts import { CheckResult } from '@backstage/plugin-tech-insights-common'; import { Config } from '@backstage/config'; +import { DateTime } from 'luxon'; import { Duration } from 'luxon'; import express from 'express'; import { FactChecker } from '@backstage/plugin-tech-insights-node'; @@ -13,12 +14,16 @@ import { FactLifecycle } from '@backstage/plugin-tech-insights-node'; import { FactRetriever } from '@backstage/plugin-tech-insights-node'; import { FactRetrieverRegistration } from '@backstage/plugin-tech-insights-node'; import { FactSchema } from '@backstage/plugin-tech-insights-node'; +import { FactSchemaDefinition } from '@backstage/plugin-tech-insights-node'; +import { FlatTechInsightFact } from '@backstage/plugin-tech-insights-node'; import { HumanDuration } from '@backstage/types'; +import { Knex } from 'knex'; import { Logger } from 'winston'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { TechInsightCheck } from '@backstage/plugin-tech-insights-node'; +import { TechInsightFact } from '@backstage/plugin-tech-insights-node'; import { TechInsightsStore } from '@backstage/plugin-tech-insights-node'; import { TokenManager } from '@backstage/backend-common'; @@ -106,6 +111,41 @@ export type TechInsightsContext< factRetrieverEngine: FactRetrieverEngine; }; +// @public +export class TechInsightsDatabase implements TechInsightsStore { + constructor(db: Knex, logger: Logger); + // (undocumented) + getFactsBetweenTimestampsByIds( + ids: string[], + entityTriplet: string, + startDateTime: DateTime, + endDateTime: DateTime, + ): Promise<{ + [factId: string]: FlatTechInsightFact[]; + }>; + // (undocumented) + getLatestFactsByIds( + ids: string[], + entityTriplet: string, + ): Promise<{ + [factId: string]: FlatTechInsightFact; + }>; + // (undocumented) + getLatestSchemas(ids?: string[]): Promise; + // (undocumented) + insertFacts({ + id, + facts, + lifecycle, + }: { + id: string; + facts: TechInsightFact[]; + lifecycle?: FactLifecycle; + }): Promise; + // (undocumented) + insertFactSchema(schemaDefinition: FactSchemaDefinition): Promise; +} + // @public (undocumented) export interface TechInsightsOptions< CheckType extends TechInsightCheck, @@ -122,6 +162,7 @@ export interface TechInsightsOptions< factRetrievers?: FactRetrieverRegistration[]; // (undocumented) logger: Logger; + persistenceContext?: PersistenceContext; // (undocumented) scheduler: PluginTaskScheduler; // (undocumented) diff --git a/plugins/tech-insights-backend/src/index.ts b/plugins/tech-insights-backend/src/index.ts index 28a922cd67..8106517a3f 100644 --- a/plugins/tech-insights-backend/src/index.ts +++ b/plugins/tech-insights-backend/src/index.ts @@ -23,6 +23,7 @@ export type { TechInsightsContext, } from './service/techInsightsContextBuilder'; export type { FactRetrieverEngine } from './service/fact/FactRetrieverEngine'; +export type { TechInsightsDatabase } from './service/persistence/TechInsightsDatabase'; export type { PersistenceContext } from './service/persistence/persistenceContext'; export { createFactRetrieverRegistration } from './service/fact/createFactRetriever'; export type { FactRetrieverRegistry } from './service/fact/FactRetrieverRegistry'; diff --git a/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts b/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts index c6751e864a..65db088431 100644 --- a/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts +++ b/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts @@ -46,6 +46,11 @@ type RawDbFactSchemaRow = { entityFilter?: string; }; +/** + * Default TechInsightsDatabase implementation. + * + * @public + */ export class TechInsightsDatabase implements TechInsightsStore { private readonly CHUNK_SIZE = 50; diff --git a/plugins/tech-insights-backend/src/service/techInsightsContextBuilder.ts b/plugins/tech-insights-backend/src/service/techInsightsContextBuilder.ts index f85d99b2a8..20693d0a7d 100644 --- a/plugins/tech-insights-backend/src/service/techInsightsContextBuilder.ts +++ b/plugins/tech-insights-backend/src/service/techInsightsContextBuilder.ts @@ -74,6 +74,12 @@ export interface TechInsightsOptions< */ factRetrieverRegistry?: FactRetrieverRegistry; + /** + * Optional persistenceContext implementation that replaces the default one. + * This can be used to replace underlying database with a more suitable implementation if needed + */ + persistenceContext?: PersistenceContext; + logger: Logger; config: Config; discovery: PluginEndpointDiscovery; @@ -139,9 +145,11 @@ export const buildTechInsightsContext = async < const factRetrieverRegistry = buildFactRetrieverRegistry(); - const persistenceContext = await initializePersistenceContext(database, { - logger, - }); + const persistenceContext = + options.persistenceContext ?? + (await initializePersistenceContext(database, { + logger, + })); const factRetrieverEngine = await DefaultFactRetrieverEngine.create({ scheduler, From 4e7bd0a1b1ce05d470fbf9d5b2c433e6419d5264 Mon Sep 17 00:00:00 2001 From: Jussi Hallila Date: Tue, 10 Jan 2023 13:46:27 +0100 Subject: [PATCH 009/255] Fix english language Signed-off-by: Jussi Hallila --- .changeset/rude-pumas-draw.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/rude-pumas-draw.md b/.changeset/rude-pumas-draw.md index df3592ece8..e36d1dd997 100644 --- a/.changeset/rude-pumas-draw.md +++ b/.changeset/rude-pumas-draw.md @@ -2,4 +2,4 @@ '@backstage/plugin-tech-insights-backend': patch --- -Expose optional persistenceContext on TechInsights construction to enable integrators to provide their own database implementations for fact handling. +Expose optional `persistenceContext` on `TechInsights` construction to enable integrators to provide their own database implementations for fact handling. From 1322d305f5b84f86b42283f67c5827156e9f3880 Mon Sep 17 00:00:00 2001 From: Drew Boswell <6523434+drewboswell@users.noreply.github.com> Date: Tue, 10 Jan 2023 15:14:26 +0100 Subject: [PATCH 010/255] Update ADOPTERS.md Signed-off-by: Drew Boswell <6523434+drewboswell@users.noreply.github.com> --- ADOPTERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index fd2c656820..fa8e9de00a 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -226,3 +226,4 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [Tractable AI](https://tractable.ai/) | [Stephan Schielke](https://github.com/stephanschielke) | We are hitting a critical point in our scale (100+ engineers) and need to get a handle on discoverability and ownership. The Service Catalog, TechDocs and Search are essential to us to achieve that. | | [Garanti BBVA Teknoloji](https://www.linkedin.com/company/garanti-teknoloji/) | [Caglar Cataloglu](https://github.com/crozwise) | We are using Backstage focusing on improving experience of developers, minimizing friction from idea to production. We call our portal as "Hyperspace" and very excited for our community (2000+ engineers) that finally we have a platform to boost our productivity! | [Booking.com](https://www.linkedin.com/company/booking.com/) | [Mesut Yilmazyildirim](https://www.linkedin.com/in/myilmazyildirim) | We are adopting Backstage as the new reliability platform inside the company. We are migrating UIs of our internal developer tools to Backstage for a better user experience. +| [Swissquote Bank](https://swissquote.com/company/jobs/open-positions) | [Bruno Rocha](https://www.linkedin.com/in/bruno-rocha1/) | Integrating Backstage as the visualization layer & tactical overview of our services and teams. From c2df6f01927fabcb51fe6a8bed4dd3a9e0db301e Mon Sep 17 00:00:00 2001 From: Dang Huynh Date: Tue, 10 Jan 2023 08:53:28 -0600 Subject: [PATCH 011/255] Minor change to patch Signed-off-by: Dang Huynh --- .changeset/cyan-deers-walk.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/cyan-deers-walk.md b/.changeset/cyan-deers-walk.md index 2a9265090b..236eb7935b 100644 --- a/.changeset/cyan-deers-walk.md +++ b/.changeset/cyan-deers-walk.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-techdocs-node': minor +'@backstage/plugin-techdocs-node': patch --- Fixed bug caused by recent migration to AWS SDK V3 for techdocs-node. Instead of s3ForcePathStyle, forcePathStyle should be passed. From e75f39e603e639ebffaccb535ed0c5565763447f Mon Sep 17 00:00:00 2001 From: Carlos Esteban Lopez Date: Thu, 15 Dec 2022 00:02:48 -0500 Subject: [PATCH 012/255] feat: Lint paragraphs except in test files & fix Signed-off-by: Carlos Esteban Lopez --- .eslintrc.js | 1 + .../Breadcrumbs/Breadcrumbs.stories.tsx | 20 +++++++++---------- .../EntityBazaarInfoContent.tsx | 7 ++++++- .../HomePageBazaarInfoCard.tsx | 5 +++-- .../src/components/FossaCard/FossaCard.tsx | 13 +++++++----- .../WorkflowRunsTable/WorkflowRunsTable.tsx | 4 ++-- .../homePageComponents/RandomJoke/Content.tsx | 7 ++++--- .../BuildsPage/lib/CITable/CITable.tsx | 10 +++++----- .../ArgoRollouts/StepsProgress.tsx | 7 ++++--- .../ListTasksPage/columns/CreatedAtColumn.tsx | 5 ++++- .../columns/OwnerEntityColumn.tsx | 3 ++- .../home/StackOverflowQuestions/Content.tsx | 5 +++-- .../RadarLegend/RadarLegendRing.tsx | 3 ++- .../tech-radar/src/components/RadarPage.tsx | 5 +++-- .../BuildTimeline/BuildTimeline.tsx | 3 ++- yarn.lock | 2 +- 16 files changed, 60 insertions(+), 40 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index cf01ffe3a6..df54fa865d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -247,6 +247,7 @@ module.exports = { { forbid: [ { element: 'button', message: 'use MUI