From a889314692a285f2569fec0d16b57632b42a0e9e Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 13 Oct 2022 14:27:50 +0200 Subject: [PATCH 01/36] Introduce an entityRef context key Signed-off-by: Eric Peterson --- .changeset/analyze-software-exploration.md | 5 ++ docs/plugins/analytics.md | 10 +-- .../src/hooks/useEntity.test.tsx | 64 ++++++++++++++++++- plugins/catalog-react/src/hooks/useEntity.tsx | 11 +++- 4 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 .changeset/analyze-software-exploration.md diff --git a/.changeset/analyze-software-exploration.md b/.changeset/analyze-software-exploration.md new file mode 100644 index 0000000000..f535f60127 --- /dev/null +++ b/.changeset/analyze-software-exploration.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Both `EntityProvider` and `AsyncEntityProvider` contexts now wrap all children with an `AnalyticsContext` containing the corresponding `entityRef`; this opens up the possibility for all events underneath these contexts to be associated with and aggregated by the corresponding entity. diff --git a/docs/plugins/analytics.md b/docs/plugins/analytics.md index ccb4e5c5a7..0c2173de5c 100644 --- a/docs/plugins/analytics.md +++ b/docs/plugins/analytics.md @@ -301,11 +301,11 @@ it's important to keep each of these levels of detail disaggregated. automatically as part of the `extension` in which the `filter` event was captured). -- On the flip side, when adding `attributes` to an event, look at existing - events and see if the data you are capturing matches the intention, type, or - even the content of _their_ `attributes`. For instance, it may be common for - events that involve the Catalog to add details like entity `name`, `kind`, - and/or `namespace` as `attributes`. Using the same keys in your event will +- On the flip side, when adding `attributes` to or `context` around an event, + look at existing events and see if the data you are capturing matches the + intention, type, or even the content of _their_ `attributes` or `context`. + For instance, it's common for events that involve the Catalog to include an + `entityRef` contextual key. Using the same keys and values in your event will ensure that events instrumented across plugins can easily be aggregated. ### Unit Testing Event Capture diff --git a/plugins/catalog-react/src/hooks/useEntity.test.tsx b/plugins/catalog-react/src/hooks/useEntity.test.tsx index 5e86a94180..3a1e5010d3 100644 --- a/plugins/catalog-react/src/hooks/useEntity.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntity.test.tsx @@ -23,6 +23,11 @@ import { AsyncEntityProvider, } from './useEntity'; import { Entity } from '@backstage/catalog-model'; +import { analyticsApiRef, useAnalytics } from '@backstage/core-plugin-api'; +import { MockAnalyticsApi, TestApiRegistry } from '@backstage/test-utils'; +import { ApiProvider } from '@backstage/core-app-api'; + +const entity = { metadata: { name: 'my-entity' }, kind: 'MyKind' } as Entity; describe('useEntity', () => { it('should throw if no entity is provided', async () => { @@ -34,7 +39,6 @@ describe('useEntity', () => { }); it('should provide an entity', async () => { - const entity = { kind: 'MyEntity' } as Entity; const { result } = renderHook(() => useEntity(), { wrapper: ({ children }) => ( @@ -43,6 +47,24 @@ describe('useEntity', () => { expect(result.current.entity).toBe(entity); }); + + it('should provide entityRef analytics context', () => { + const analyticsSpy = new MockAnalyticsApi(); + const apis = TestApiRegistry.from([analyticsApiRef, analyticsSpy]); + const { result } = renderHook(() => useAnalytics(), { + wrapper: ({ children }) => ( + + + + ), + }); + + result.current.captureEvent('test', 'value'); + + expect(analyticsSpy.getEvents()[0]).toMatchObject({ + context: { entityRef: 'mykind:default/my-entity' }, + }); + }); }); describe('useAsyncEntity', () => { @@ -60,7 +82,6 @@ describe('useAsyncEntity', () => { }); it('should provide an entity', async () => { - const entity = { kind: 'MyEntity' } as Entity; const refresh = () => {}; const { result } = renderHook(() => useAsyncEntity(), { wrapper: ({ children }) => ( @@ -96,4 +117,43 @@ describe('useAsyncEntity', () => { expect(result.current.error).toBe(error); expect(result.current.refresh).toBe(undefined); }); + + it('should provide entityRef analytics context', () => { + const analyticsSpy = new MockAnalyticsApi(); + const apis = TestApiRegistry.from([analyticsApiRef, analyticsSpy]); + const { result } = renderHook(() => useAnalytics(), { + wrapper: ({ children }) => ( + + {}} + children={children} + /> + + ), + }); + + result.current.captureEvent('test', 'value'); + + expect(analyticsSpy.getEvents()[0]).toMatchObject({ + context: { entityRef: 'mykind:default/my-entity' }, + }); + }); + + it('should omit entityRef analytics context', () => { + const analyticsSpy = new MockAnalyticsApi(); + const apis = TestApiRegistry.from([analyticsApiRef, analyticsSpy]); + const { result } = renderHook(() => useAnalytics(), { + wrapper: ({ children }) => ( + + + + ), + }); + + result.current.captureEvent('test', 'value'); + + expect(analyticsSpy.getEvents()[0].context).not.toHaveProperty('entityRef'); + }); }); diff --git a/plugins/catalog-react/src/hooks/useEntity.tsx b/plugins/catalog-react/src/hooks/useEntity.tsx index f654cff777..84b7883c8a 100644 --- a/plugins/catalog-react/src/hooks/useEntity.tsx +++ b/plugins/catalog-react/src/hooks/useEntity.tsx @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; +import { AnalyticsContext } from '@backstage/core-plugin-api'; import { createVersionedContext, createVersionedValueMap, @@ -66,7 +67,13 @@ export const AsyncEntityProvider = ({ // consumers might be doing things like `useContext(EntityContext)` return ( - {children} + + {children} + ); }; From 4830a3569f792a726c6853a17a75a2a773c094e1 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 13 Oct 2022 14:30:34 +0200 Subject: [PATCH 02/36] Introduce basic scaffolder instrumentation Signed-off-by: Eric Peterson --- .changeset/analyze-software-creation.md | 8 ++ docs/plugins/analytics.md | 13 ++-- .../MultistepJsonForm/MultistepJsonForm.tsx | 10 ++- .../TemplatePage/TemplatePage.test.tsx | 70 ++++++++++++++++- .../components/TemplatePage/TemplatePage.tsx | 75 ++++++++++--------- 5 files changed, 132 insertions(+), 44 deletions(-) create mode 100644 .changeset/analyze-software-creation.md diff --git a/.changeset/analyze-software-creation.md b/.changeset/analyze-software-creation.md new file mode 100644 index 0000000000..312987d3d1 --- /dev/null +++ b/.changeset/analyze-software-creation.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Basic analytics instrumentation is now in place: + +- As users make their way through template steps, a `click` event is fired, including the step number. +- After a user clicks "Create" a `create` event is fired, including the name of the software that was just created. The template used at creation is set on the `entityRef` context key. diff --git a/docs/plugins/analytics.md b/docs/plugins/analytics.md index 0c2173de5c..46e1d354df 100644 --- a/docs/plugins/analytics.md +++ b/docs/plugins/analytics.md @@ -52,12 +52,13 @@ learn how to contribute the integration yourself! The following table summarizes events that, depending on the plugins you have installed, may be captured. -| Action | Subject | Other Notes | -| ---------- | --------------------------------------------------- | ----------------------------------------------------------------- | -| `navigate` | The URL of the page that was navigated to | | -| `click` | The text of the link that was clicked on | The `to` attribute represents the URL clicked to | -| `search` | The search term entered in any search bar component | The `searchTypes` attribute holds `types` constraining the search | -| `discover` | The title of the search result that was clicked on | The `value` is the result rank. A `to` attribute is also provided | +| Action | Subject | Other Notes | +| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- | +| `navigate` | The URL of the page that was navigated to | | +| `click` | The text of the link that was clicked on | The `to` attribute represents the URL clicked to | +| `create` | The `name` of the software being created; if no `name` property is requested by the given Software Template, then the string `new {templateName}` is used instead. | The context holds an `entityRef`, set to the template's ref (e.g. `template:default/template-name`) | +| `search` | The search term entered in any search bar component | The context holds `searchTypes`, representing `types` constraining the search | +| `discover` | The title of the search result that was clicked on | The `value` is the result rank. A `to` attribute is also provided | If there is an event you'd like to see captured, please [open an issue][add-event] describing the event you want to see and the questions it diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 8c71df67b6..7afbeb5a97 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -28,6 +28,8 @@ import { errorApiRef, useApi, featureFlagsApiRef, + useAnalytics, + useRouteRefParams, } from '@backstage/core-plugin-api'; import { FormProps, IChangeEvent, UiSchema, withTheme } from '@rjsf/core'; import { Theme as MuiTheme } from '@rjsf/material-ui'; @@ -37,6 +39,7 @@ import { Content, StructuredMetadataTable } from '@backstage/core-components'; import cloneDeep from 'lodash/cloneDeep'; import * as fieldOverrides from './FieldOverrides'; import { LayoutOptions } from '../../layouts'; +import { selectedTemplateRouteRef } from '../../routes'; const Form = withTheme(MuiTheme); type Step = { @@ -123,6 +126,8 @@ export const MultistepJsonForm = (props: Props) => { finishButtonLabel, layouts, } = props; + const { templateName } = useRouteRefParams(selectedTemplateRouteRef); + const analytics = useAnalytics(); const [activeStep, setActiveStep] = useState(0); const [disableButtons, setDisableButtons] = useState(false); const errorApi = useApi(errorApiRef); @@ -171,7 +176,9 @@ export const MultistepJsonForm = (props: Props) => { onReset(); }; const handleNext = () => { - setActiveStep(Math.min(activeStep + 1, steps.length)); + const stepNum = Math.min(activeStep + 1, steps.length); + setActiveStep(stepNum); + analytics.captureEvent('click', `Next Step (${stepNum})`); }; const handleBack = () => setActiveStep(Math.max(activeStep - 1, 0)); const handleCreate = async () => { @@ -182,6 +189,7 @@ export const MultistepJsonForm = (props: Props) => { setDisableButtons(true); try { await onFinish(); + analytics.captureEvent('create', formData.name || `new ${templateName}`); } catch (err) { errorApi.post(err); } finally { diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx index d35b561f2b..a48644e72d 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx @@ -13,7 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; +import { + MockAnalyticsApi, + renderInTestApp, + TestApiRegistry, +} from '@backstage/test-utils'; import { act, fireEvent, within } from '@testing-library/react'; import React from 'react'; import { Route, Routes } from 'react-router'; @@ -24,6 +28,7 @@ import { TemplatePage } from './TemplatePage'; import { featureFlagsApiRef, FeatureFlagsApi, + analyticsApiRef, } from '@backstage/core-plugin-api'; import { ApiProvider } from '@backstage/core-app-api'; @@ -57,6 +62,8 @@ const featureFlagsApiMock: jest.Mocked = { const errorApiMock = { post: jest.fn(), error$: jest.fn() }; +const analyticsMock = new MockAnalyticsApi(); + const schemaMockValue = { title: 'my-schema', steps: [ @@ -105,6 +112,7 @@ const apis = TestApiRegistry.from( [scaffolderApiRef, scaffolderApiMock], [errorApiRef, errorApiMock], [featureFlagsApiRef, featureFlagsApiMock], + [analyticsApiRef, analyticsMock], ); describe('TemplatePage', () => { @@ -158,6 +166,66 @@ describe('TemplatePage', () => { }); }); + it('captures expected analytics events', async () => { + scaffolderApiMock.scaffold.mockResolvedValue({ taskId: 'xyz' }); + scaffolderApiMock.getTemplateParameterSchema.mockResolvedValue({ + title: 'schema-4-analytics', + steps: [ + { + title: 'Fill in some steps', + schema: { + properties: { + name: { + title: 'Name', + type: 'string', + }, + }, + required: ['name'], + }, + }, + ], + }); + const { findByLabelText, findByText } = await renderInTestApp( + + + , + { + mountedRoutes: { + '/create': rootRouteRef, + }, + }, + ); + + // Fill out the name field + expect(await findByText('Fill in some steps')).toBeInTheDocument(); + fireEvent.change(await findByLabelText('Name', { exact: false }), { + target: { value: 'expected-name' }, + }); + + // Go to the final page + fireEvent.click(await findByText('Next step')); + expect(await findByText('Reset')).toBeInTheDocument(); + + // Create the software + await act(async () => { + fireEvent.click(await findByText('Create')); + }); + + // The "Next Step" button should have fired an event + expect(analyticsMock.getEvents()[0]).toMatchObject({ + action: 'click', + subject: 'Next Step (1)', + context: { entityRef: 'template:default/test' }, + }); + + // And the "Create" button should have fired an event + expect(analyticsMock.getEvents()[1]).toMatchObject({ + action: 'create', + subject: 'expected-name', + context: { entityRef: 'template:default/test' }, + }); + }); + it('navigates away if no template was loaded', async () => { scaffolderApiMock.getTemplateParameterSchema.mockResolvedValue( undefined as any, diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index 9eba61b53b..87678d2a83 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -32,6 +32,7 @@ import { createValidator } from './createValidator'; import { Content, Header, InfoCard, Page } from '@backstage/core-components'; import { + AnalyticsContext, errorApiRef, useApi, useApiHolder, @@ -129,41 +130,43 @@ export const TemplatePage = ({ ); return ( - -
- - {loading && } - {schema && ( - - { - return { - ...step, - validate: createValidator( - step.schema, - customFieldValidators, - { apiHolder }, - ), - }; - })} - /> - - )} - - + + +
+ + {loading && } + {schema && ( + + { + return { + ...step, + validate: createValidator( + step.schema, + customFieldValidators, + { apiHolder }, + ), + }; + })} + /> + + )} + + + ); }; From d79f104234830d13f843c38342fee738d83bc806 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 13 Oct 2022 15:17:27 +0200 Subject: [PATCH 03/36] Fix entity mocks in tests Signed-off-by: Eric Peterson --- .../components/EntityBadgesDialog.test.tsx | 5 ++++- .../EntityLinksCard/EntityLinksCard.test.tsx | 2 ++ .../EntitySwitch/EntitySwitch.test.tsx | 20 ++++++++++++------- .../GoCdBuildsComponent.test.tsx | 4 +++- .../src/components/TodoList/TodoList.test.tsx | 5 ++++- 5 files changed, 26 insertions(+), 10 deletions(-) diff --git a/plugins/badges/src/components/EntityBadgesDialog.test.tsx b/plugins/badges/src/components/EntityBadgesDialog.test.tsx index ea44eb20fc..5d0e7fe849 100644 --- a/plugins/badges/src/components/EntityBadgesDialog.test.tsx +++ b/plugins/badges/src/components/EntityBadgesDialog.test.tsx @@ -38,7 +38,10 @@ describe('EntityBadgesDialog', () => { }, ]), }; - const mockEntity = { metadata: { name: 'mock' } } as Entity; + const mockEntity = { + metadata: { name: 'mock' }, + kind: 'MockKind', + } as Entity; const rendered = await renderWithEffects( { const createEntity = (links: EntityLink[] = []): Entity => ({ metadata: { + name: 'mock', links, }, + kind: 'MockKind', } as Entity); const createLink = ({ diff --git a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx index 9588327c3b..4ee9d9c820 100644 --- a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx +++ b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx @@ -46,7 +46,9 @@ describe('EntitySwitch', () => { const rendered = render( - + {content} , @@ -58,7 +60,9 @@ describe('EntitySwitch', () => { rendered.rerender( - + {content} , @@ -70,7 +74,9 @@ describe('EntitySwitch', () => { rendered.rerender( - + {content} , @@ -94,7 +100,7 @@ describe('EntitySwitch', () => { }); it('should switch child when filters switch', () => { - const entity = { kind: 'component' } as Entity; + const entity = { metadata: { name: 'mock' }, kind: 'component' } as Entity; const rendered = render( @@ -126,7 +132,7 @@ describe('EntitySwitch', () => { }); it('should switch with async condition that is true', async () => { - const entity = { kind: 'component' } as Entity; + const entity = { metadata: { name: 'mock' }, kind: 'component' } as Entity; const shouldRender = () => Promise.resolve(true); const rendered = render( @@ -145,7 +151,7 @@ describe('EntitySwitch', () => { }); it('should switch with sync condition that is false', async () => { - const entity = { kind: 'component' } as Entity; + const entity = { metadata: { name: 'mock' }, kind: 'component' } as Entity; const shouldRender = () => Promise.resolve(false); const rendered = render( @@ -164,7 +170,7 @@ describe('EntitySwitch', () => { }); it('should switch with sync condition that throws', async () => { - const entity = { kind: 'component' } as Entity; + const entity = { metadata: { name: 'mock' }, kind: 'component' } as Entity; const shouldRender = () => Promise.reject(); const rendered = render( diff --git a/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx b/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx index 7f16f40a6a..ebc3502901 100644 --- a/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx +++ b/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx @@ -36,7 +36,9 @@ describe('GoCdArtifactsComponent', () => { baseUrl: 'gocd.baseurl.com', }, }); - const entityValue = { entity: { metadata: {} } as Entity }; + const entityValue = { + entity: { metadata: { name: 'mock' }, kind: 'MockKind' } as Entity, + }; const renderComponent = () => renderWithEffects( diff --git a/plugins/todo/src/components/TodoList/TodoList.test.tsx b/plugins/todo/src/components/TodoList/TodoList.test.tsx index 2759fe40f8..15d4334610 100644 --- a/plugins/todo/src/components/TodoList/TodoList.test.tsx +++ b/plugins/todo/src/components/TodoList/TodoList.test.tsx @@ -38,7 +38,10 @@ describe('TodoList', () => { offset: 0, }), }; - const mockEntity = { metadata: { name: 'mock' } } as Entity; + const mockEntity = { + metadata: { name: 'mock' }, + kind: 'MockKind', + } as Entity; const rendered = await renderWithEffects( From 3ec10f407ea01b7f47f274dba3208c7d3f0e7bcb Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Wed, 28 Sep 2022 11:47:22 +0200 Subject: [PATCH 04/36] added header options to scaffolder page props Signed-off-by: Alex Rybchenko --- plugins/scaffolder/src/components/Router.tsx | 6 ++++++ .../src/components/ScaffolderPage/ScaffolderPage.tsx | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/plugins/scaffolder/src/components/Router.tsx b/plugins/scaffolder/src/components/Router.tsx index 021d73f5e9..097eddf6cd 100644 --- a/plugins/scaffolder/src/components/Router.tsx +++ b/plugins/scaffolder/src/components/Router.tsx @@ -63,6 +63,11 @@ export type RouterProps = { filter: (entity: Entity) => boolean; }>; defaultPreviewTemplate?: string; + headerOptions?: { + pageTitleOverride?: string; + title?: string; + subtitle?: string; + }; /** * Options for the context menu on the scaffolder page. */ @@ -143,6 +148,7 @@ export const Router = (props: RouterProps) => { groups={groups} TemplateCardComponent={TemplateCardComponent} contextMenu={props.contextMenu} + headerOptions={props.headerOptions} /> } /> diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index 60a761d171..9c13db72b1 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -54,12 +54,18 @@ export type ScaffolderPageProps = { actions?: boolean; tasks?: boolean; }; + headerOptions?: { + pageTitleOverride?: string; + title?: string; + subtitle?: string; + }; }; export const ScaffolderPageContents = ({ TemplateCardComponent, groups, contextMenu, + headerOptions, }: ScaffolderPageProps) => { const registerComponentLink = useRouteRef(registerComponentRouteRef); const otherTemplatesGroup = { @@ -80,6 +86,7 @@ export const ScaffolderPageContents = ({ pageTitleOverride="Create a New Component" title="Create a New Component" subtitle="Create new software components using standard templates" + {...headerOptions} >
@@ -134,12 +141,14 @@ export const ScaffolderPage = ({ TemplateCardComponent, groups, contextMenu, + headerOptions, }: ScaffolderPageProps) => ( ); From a3f1340f3ccf43ffdc2010f971f1684db6163770 Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Wed, 28 Sep 2022 11:47:52 +0200 Subject: [PATCH 05/36] exported scaffolder routes Signed-off-by: Alex Rybchenko --- plugins/scaffolder/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index e68eece8e5..440c87c363 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -61,6 +61,7 @@ export { scaffolderPlugin, } from './plugin'; export * from './components'; +export * from './routes'; export type { TaskPageProps } from './components/TaskPage'; /** next exports */ From edae17309e7e0e0007dc2176bbb226ed7717db5c Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Wed, 28 Sep 2022 11:52:36 +0200 Subject: [PATCH 06/36] added changeset Signed-off-by: Alex Rybchenko --- .changeset/sixty-islands-develop.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/sixty-islands-develop.md diff --git a/.changeset/sixty-islands-develop.md b/.changeset/sixty-islands-develop.md new file mode 100644 index 0000000000..75af6bcc70 --- /dev/null +++ b/.changeset/sixty-islands-develop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Added props to override default Scaffolder page title, subtitle and pageTitleOverride From 7d8734c35cb2bfcae40388bc7dbffb38da234cf4 Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Wed, 28 Sep 2022 15:30:51 +0200 Subject: [PATCH 07/36] updated api report Signed-off-by: Alex Rybchenko --- plugins/scaffolder/api-report.md | 75 ++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index cf69736bd4..193497a849 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -24,10 +24,12 @@ import { JsonObject } from '@backstage/types'; import { JSONSchema7 } from 'json-schema'; import { JsonValue } from '@backstage/types'; import { Observable } from '@backstage/types'; +import { PathParams } from '@backstage/core-plugin-api'; import { PropsWithChildren } from 'react'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { ScmIntegrationRegistry } from '@backstage/integration'; +import { SubRouteRef } from '@backstage/core-plugin-api'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { TaskStep } from '@backstage/plugin-scaffolder-common'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; @@ -42,6 +44,11 @@ export function createNextScaffolderFieldExtension< options: NextFieldExtensionOptions, ): Extension>; +// Warning: (ae-missing-release-tag) "actionsRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const actionsRouteRef: SubRouteRef; + // @public export function createScaffolderFieldExtension< TReturnValue = unknown, @@ -64,6 +71,11 @@ export type CustomFieldValidator = ( }, ) => void | Promise; +// Warning: (ae-missing-release-tag) "editRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const editRouteRef: SubRouteRef; + // @public export const EntityNamePickerFieldExtension: FieldExtensionComponent< string, @@ -144,6 +156,13 @@ export interface LayoutOptions

{ // @public export type LayoutTemplate = FormProps['ObjectFieldTemplate']; +// Warning: (ae-missing-release-tag) "legacySelectedTemplateRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public @deprecated (undocumented) +export const legacySelectedTemplateRouteRef: SubRouteRef< + PathParams<'/templates/:templateName'> +>; + // @public export type ListActionsResponse = Array<{ id: string; @@ -167,6 +186,11 @@ export type LogEvent = { taskId: string; }; +// Warning: (ae-missing-release-tag) "nextRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const nextRouteRef: RouteRef; + // @alpha export type NextCustomFieldValidator = ( data: TFieldReturnValue, @@ -215,6 +239,13 @@ export const NextScaffolderPage: ( props: PropsWithChildren, ) => JSX.Element; +// Warning: (ae-missing-release-tag) "nextSelectedTemplateRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const nextSelectedTemplateRouteRef: SubRouteRef< + PathParams<'/templates/:namespace/:templateName'> +>; + // @public export const OwnedEntityPickerFieldExtension: FieldExtensionComponent< string, @@ -249,6 +280,11 @@ export interface OwnerPickerUiOptions { defaultNamespace?: string | false; } +// Warning: (ae-missing-release-tag) "registerComponentRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const registerComponentRouteRef: ExternalRouteRef; + // @public export const repoPickerValidation: ( value: string, @@ -287,6 +323,11 @@ export interface RepoUrlPickerUiOptions { }; } +// Warning: (ae-missing-release-tag) "rootRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const rootRouteRef: RouteRef; + // @public export type RouterProps = { components?: { @@ -302,6 +343,11 @@ export type RouterProps = { filter: (entity: Entity) => boolean; }>; defaultPreviewTemplate?: string; + headerOptions?: { + pageTitleOverride?: string; + title?: string; + subtitle?: string; + }; contextMenu?: { editor?: boolean; actions?: boolean; @@ -424,6 +470,11 @@ export interface ScaffolderGetIntegrationsListResponse { // @public export const ScaffolderLayouts: React.ComponentType; +// Warning: (ae-missing-release-tag) "scaffolderListTaskRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const scaffolderListTaskRouteRef: SubRouteRef; + // @public (undocumented) export type ScaffolderOutputLink = { title?: string; @@ -494,6 +545,11 @@ export type ScaffolderTaskOutput = { [key: string]: unknown; }; +// Warning: (ae-missing-release-tag) "scaffolderTaskRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const scaffolderTaskRouteRef: SubRouteRef>; + // @public export type ScaffolderTaskStatus = | 'open' @@ -508,6 +564,13 @@ export interface ScaffolderUseTemplateSecrets { setSecrets: (input: Record) => void; } +// Warning: (ae-missing-release-tag) "selectedTemplateRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const selectedTemplateRouteRef: SubRouteRef< + PathParams<'/templates/:namespace/:templateName'> +>; + // @public export const TaskPage: ({ loadingText }: TaskPageProps) => JSX.Element; @@ -538,4 +601,16 @@ export const TemplateTypePicker: () => JSX.Element | null; // @public export const useTemplateSecrets: () => ScaffolderUseTemplateSecrets; + +// Warning: (ae-missing-release-tag) "viewTechDocRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const viewTechDocRouteRef: ExternalRouteRef< + { + name: string; + kind: string; + namespace: string; + }, + true +>; ``` From 36985448d9e87d51d33fa30258a69a5db7171ff3 Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Thu, 29 Sep 2022 16:49:56 +0200 Subject: [PATCH 08/36] updated api docs Signed-off-by: Alex Rybchenko --- plugins/scaffolder/api-report.md | 24 +----------------------- plugins/scaffolder/src/routes.ts | 11 +++++++++++ 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 193497a849..5931e127f5 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -44,8 +44,6 @@ export function createNextScaffolderFieldExtension< options: NextFieldExtensionOptions, ): Extension>; -// Warning: (ae-missing-release-tag) "actionsRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const actionsRouteRef: SubRouteRef; @@ -71,8 +69,6 @@ export type CustomFieldValidator = ( }, ) => void | Promise; -// Warning: (ae-missing-release-tag) "editRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const editRouteRef: SubRouteRef; @@ -156,9 +152,7 @@ export interface LayoutOptions

{ // @public export type LayoutTemplate = FormProps['ObjectFieldTemplate']; -// Warning: (ae-missing-release-tag) "legacySelectedTemplateRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public @deprecated (undocumented) +// @public (undocumented) export const legacySelectedTemplateRouteRef: SubRouteRef< PathParams<'/templates/:templateName'> >; @@ -186,8 +180,6 @@ export type LogEvent = { taskId: string; }; -// Warning: (ae-missing-release-tag) "nextRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const nextRouteRef: RouteRef; @@ -239,8 +231,6 @@ export const NextScaffolderPage: ( props: PropsWithChildren, ) => JSX.Element; -// Warning: (ae-missing-release-tag) "nextSelectedTemplateRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const nextSelectedTemplateRouteRef: SubRouteRef< PathParams<'/templates/:namespace/:templateName'> @@ -280,8 +270,6 @@ export interface OwnerPickerUiOptions { defaultNamespace?: string | false; } -// Warning: (ae-missing-release-tag) "registerComponentRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const registerComponentRouteRef: ExternalRouteRef; @@ -323,8 +311,6 @@ export interface RepoUrlPickerUiOptions { }; } -// Warning: (ae-missing-release-tag) "rootRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const rootRouteRef: RouteRef; @@ -470,8 +456,6 @@ export interface ScaffolderGetIntegrationsListResponse { // @public export const ScaffolderLayouts: React.ComponentType; -// Warning: (ae-missing-release-tag) "scaffolderListTaskRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const scaffolderListTaskRouteRef: SubRouteRef; @@ -545,8 +529,6 @@ export type ScaffolderTaskOutput = { [key: string]: unknown; }; -// Warning: (ae-missing-release-tag) "scaffolderTaskRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const scaffolderTaskRouteRef: SubRouteRef>; @@ -564,8 +546,6 @@ export interface ScaffolderUseTemplateSecrets { setSecrets: (input: Record) => void; } -// Warning: (ae-missing-release-tag) "selectedTemplateRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const selectedTemplateRouteRef: SubRouteRef< PathParams<'/templates/:namespace/:templateName'> @@ -602,8 +582,6 @@ export const TemplateTypePicker: () => JSX.Element | null; // @public export const useTemplateSecrets: () => ScaffolderUseTemplateSecrets; -// Warning: (ae-missing-release-tag) "viewTechDocRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const viewTechDocRouteRef: ExternalRouteRef< { diff --git a/plugins/scaffolder/src/routes.ts b/plugins/scaffolder/src/routes.ts index 5e2d191443..26e25b54a2 100644 --- a/plugins/scaffolder/src/routes.ts +++ b/plugins/scaffolder/src/routes.ts @@ -19,17 +19,20 @@ import { createSubRouteRef, } from '@backstage/core-plugin-api'; +/** @public */ export const registerComponentRouteRef = createExternalRouteRef({ id: 'register-component', optional: true, }); +/** @public */ export const viewTechDocRouteRef = createExternalRouteRef({ id: 'view-techdoc', optional: true, params: ['namespace', 'kind', 'name'], }); +/** @public */ export const rootRouteRef = createRouteRef({ id: 'scaffolder', }); @@ -37,28 +40,33 @@ export const rootRouteRef = createRouteRef({ /** * @deprecated This is the old template route, can be deleted before next major release */ +/** @public */ export const legacySelectedTemplateRouteRef = createSubRouteRef({ id: 'scaffolder/legacy/selected-template', parent: rootRouteRef, path: '/templates/:templateName', }); +/** @public */ export const nextRouteRef = createRouteRef({ id: 'scaffolder/next', }); +/** @public */ export const selectedTemplateRouteRef = createSubRouteRef({ id: 'scaffolder/selected-template', parent: rootRouteRef, path: '/templates/:namespace/:templateName', }); +/** @public */ export const nextSelectedTemplateRouteRef = createSubRouteRef({ id: 'scaffolder/next/selected-template', parent: nextRouteRef, path: '/templates/:namespace/:templateName', }); +/** @public */ export const scaffolderTaskRouteRef = createSubRouteRef({ id: 'scaffolder/task', parent: rootRouteRef, @@ -71,18 +79,21 @@ export const nextScaffolderTaskRouteRef = createSubRouteRef({ path: '/tasks/:taskId', }); +/** @public */ export const scaffolderListTaskRouteRef = createSubRouteRef({ id: 'scaffolder/list-tasks', parent: rootRouteRef, path: '/tasks', }); +/** @public */ export const actionsRouteRef = createSubRouteRef({ id: 'scaffolder/actions', parent: rootRouteRef, path: '/actions', }); +/** @public */ export const editRouteRef = createSubRouteRef({ id: 'scaffolder/edit', parent: rootRouteRef, From b6640cd762aec813d537b51b23046e0a2342bb8a Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Fri, 30 Sep 2022 15:12:19 +0200 Subject: [PATCH 09/36] export selected routes only Signed-off-by: Alex Rybchenko --- .changeset/sixty-islands-develop.md | 2 +- plugins/scaffolder/api-report.md | 34 ++--------------------------- plugins/scaffolder/src/index.ts | 7 +++++- plugins/scaffolder/src/routes.ts | 11 ++-------- 4 files changed, 11 insertions(+), 43 deletions(-) diff --git a/.changeset/sixty-islands-develop.md b/.changeset/sixty-islands-develop.md index 75af6bcc70..1afcc5730a 100644 --- a/.changeset/sixty-islands-develop.md +++ b/.changeset/sixty-islands-develop.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder': patch --- -Added props to override default Scaffolder page title, subtitle and pageTitleOverride +Added props to override default Scaffolder page title, subtitle and pageTitleOverride. diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 5931e127f5..e759049233 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -44,9 +44,6 @@ export function createNextScaffolderFieldExtension< options: NextFieldExtensionOptions, ): Extension>; -// @public (undocumented) -export const actionsRouteRef: SubRouteRef; - // @public export function createScaffolderFieldExtension< TReturnValue = unknown, @@ -69,9 +66,6 @@ export type CustomFieldValidator = ( }, ) => void | Promise; -// @public (undocumented) -export const editRouteRef: SubRouteRef; - // @public export const EntityNamePickerFieldExtension: FieldExtensionComponent< string, @@ -152,11 +146,6 @@ export interface LayoutOptions

{ // @public export type LayoutTemplate = FormProps['ObjectFieldTemplate']; -// @public (undocumented) -export const legacySelectedTemplateRouteRef: SubRouteRef< - PathParams<'/templates/:templateName'> ->; - // @public export type ListActionsResponse = Array<{ id: string; @@ -180,7 +169,7 @@ export type LogEvent = { taskId: string; }; -// @public (undocumented) +// @alpha (undocumented) export const nextRouteRef: RouteRef; // @alpha @@ -231,7 +220,7 @@ export const NextScaffolderPage: ( props: PropsWithChildren, ) => JSX.Element; -// @public (undocumented) +// @alpha (undocumented) export const nextSelectedTemplateRouteRef: SubRouteRef< PathParams<'/templates/:namespace/:templateName'> >; @@ -270,9 +259,6 @@ export interface OwnerPickerUiOptions { defaultNamespace?: string | false; } -// @public (undocumented) -export const registerComponentRouteRef: ExternalRouteRef; - // @public export const repoPickerValidation: ( value: string, @@ -456,9 +442,6 @@ export interface ScaffolderGetIntegrationsListResponse { // @public export const ScaffolderLayouts: React.ComponentType; -// @public (undocumented) -export const scaffolderListTaskRouteRef: SubRouteRef; - // @public (undocumented) export type ScaffolderOutputLink = { title?: string; @@ -529,9 +512,6 @@ export type ScaffolderTaskOutput = { [key: string]: unknown; }; -// @public (undocumented) -export const scaffolderTaskRouteRef: SubRouteRef>; - // @public export type ScaffolderTaskStatus = | 'open' @@ -581,14 +561,4 @@ export const TemplateTypePicker: () => JSX.Element | null; // @public export const useTemplateSecrets: () => ScaffolderUseTemplateSecrets; - -// @public (undocumented) -export const viewTechDocRouteRef: ExternalRouteRef< - { - name: string; - kind: string; - namespace: string; - }, - true ->; ``` diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index 440c87c363..7853667101 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -61,7 +61,12 @@ export { scaffolderPlugin, } from './plugin'; export * from './components'; -export * from './routes'; +export { + rootRouteRef, + nextRouteRef, + selectedTemplateRouteRef, + nextSelectedTemplateRouteRef, +} from './routes'; export type { TaskPageProps } from './components/TaskPage'; /** next exports */ diff --git a/plugins/scaffolder/src/routes.ts b/plugins/scaffolder/src/routes.ts index 26e25b54a2..7451a3a943 100644 --- a/plugins/scaffolder/src/routes.ts +++ b/plugins/scaffolder/src/routes.ts @@ -19,13 +19,11 @@ import { createSubRouteRef, } from '@backstage/core-plugin-api'; -/** @public */ export const registerComponentRouteRef = createExternalRouteRef({ id: 'register-component', optional: true, }); -/** @public */ export const viewTechDocRouteRef = createExternalRouteRef({ id: 'view-techdoc', optional: true, @@ -40,14 +38,13 @@ export const rootRouteRef = createRouteRef({ /** * @deprecated This is the old template route, can be deleted before next major release */ -/** @public */ export const legacySelectedTemplateRouteRef = createSubRouteRef({ id: 'scaffolder/legacy/selected-template', parent: rootRouteRef, path: '/templates/:templateName', }); -/** @public */ +/** @alpha */ export const nextRouteRef = createRouteRef({ id: 'scaffolder/next', }); @@ -59,14 +56,13 @@ export const selectedTemplateRouteRef = createSubRouteRef({ path: '/templates/:namespace/:templateName', }); -/** @public */ +/** @alpha */ export const nextSelectedTemplateRouteRef = createSubRouteRef({ id: 'scaffolder/next/selected-template', parent: nextRouteRef, path: '/templates/:namespace/:templateName', }); -/** @public */ export const scaffolderTaskRouteRef = createSubRouteRef({ id: 'scaffolder/task', parent: rootRouteRef, @@ -79,21 +75,18 @@ export const nextScaffolderTaskRouteRef = createSubRouteRef({ path: '/tasks/:taskId', }); -/** @public */ export const scaffolderListTaskRouteRef = createSubRouteRef({ id: 'scaffolder/list-tasks', parent: rootRouteRef, path: '/tasks', }); -/** @public */ export const actionsRouteRef = createSubRouteRef({ id: 'scaffolder/actions', parent: rootRouteRef, path: '/actions', }); -/** @public */ export const editRouteRef = createSubRouteRef({ id: 'scaffolder/edit', parent: rootRouteRef, From dec322b87158e31cdb86f90d262663637e77cae0 Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Tue, 4 Oct 2022 18:45:51 +0200 Subject: [PATCH 10/36] Update .changeset/sixty-islands-develop.md Co-authored-by: Ben Lambert Signed-off-by: Alex Rybchenko --- .changeset/sixty-islands-develop.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/sixty-islands-develop.md b/.changeset/sixty-islands-develop.md index 1afcc5730a..3bdcd344b1 100644 --- a/.changeset/sixty-islands-develop.md +++ b/.changeset/sixty-islands-develop.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder': patch --- -Added props to override default Scaffolder page title, subtitle and pageTitleOverride. +Added props to override default Scaffolder page `title`, `subtitle` and `pageTitleOverride`. From ca0cbd5a022259e11a67b48ddda7f554dd6ed4f2 Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Wed, 12 Oct 2022 15:16:33 +0200 Subject: [PATCH 11/36] updated changeset Signed-off-by: Alex Rybchenko --- .changeset/sixty-islands-develop.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.changeset/sixty-islands-develop.md b/.changeset/sixty-islands-develop.md index 3bdcd344b1..9fbe2b2edd 100644 --- a/.changeset/sixty-islands-develop.md +++ b/.changeset/sixty-islands-develop.md @@ -1,5 +1,11 @@ --- -'@backstage/plugin-scaffolder': patch +'@backstage/plugin-scaffolder': minor --- +<<<<<<< Updated upstream Added props to override default Scaffolder page `title`, `subtitle` and `pageTitleOverride`. +======= +Added props to override default Scaffolder page title, subtitle and pageTitleOverride. +Routes like `rootRouteRef`, `selectedTemplateRouteRef`, `nextRouteRef`, `nextSelectedTemplateRouteRef` were made public and can be used in your app (e.g. in custom TemplateCard component) + +> > > > > > > Stashed changes From afb13576c9060d35a660923b8d0082314f187106 Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Thu, 13 Oct 2022 14:49:37 +0200 Subject: [PATCH 12/36] fixed changeset Signed-off-by: Alex Rybchenko --- .changeset/sixty-islands-develop.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.changeset/sixty-islands-develop.md b/.changeset/sixty-islands-develop.md index 9fbe2b2edd..9eb36cc1a9 100644 --- a/.changeset/sixty-islands-develop.md +++ b/.changeset/sixty-islands-develop.md @@ -2,10 +2,5 @@ '@backstage/plugin-scaffolder': minor --- -<<<<<<< Updated upstream -Added props to override default Scaffolder page `title`, `subtitle` and `pageTitleOverride`. -======= Added props to override default Scaffolder page title, subtitle and pageTitleOverride. -Routes like `rootRouteRef`, `selectedTemplateRouteRef`, `nextRouteRef`, `nextSelectedTemplateRouteRef` were made public and can be used in your app (e.g. in custom TemplateCard component) - -> > > > > > > Stashed changes +Routes like `rootRouteRef`, `selectedTemplateRouteRef`, `nextRouteRef`, `nextSelectedTemplateRouteRef` were made public and can be used in your app (e.g. in custom TemplateCard component). From 5e2347ef682dabaecf1c7fd92fdf915c975a7e8d Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Thu, 13 Oct 2022 15:22:58 +0200 Subject: [PATCH 13/36] updated api report Signed-off-by: Alex Rybchenko --- plugins/scaffolder/api-report.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index e759049233..158c75e5bd 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -169,9 +169,6 @@ export type LogEvent = { taskId: string; }; -// @alpha (undocumented) -export const nextRouteRef: RouteRef; - // @alpha export type NextCustomFieldValidator = ( data: TFieldReturnValue, @@ -204,6 +201,9 @@ export type NextFieldExtensionOptions< validation?: NextCustomFieldValidator; }; +// @alpha (undocumented) +export const nextRouteRef: RouteRef; + // @alpha export type NextRouterProps = { components?: { From 6aec3eb1b04a11c793c6d0ef620fc99b279ea7af Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 14 Oct 2022 15:22:26 +0100 Subject: [PATCH 14/36] initial REVIEWING.md Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 REVIEWING.md diff --git a/REVIEWING.md b/REVIEWING.md new file mode 100644 index 0000000000..9b21b49d51 --- /dev/null +++ b/REVIEWING.md @@ -0,0 +1,107 @@ +# Introduction + +This file provides pointers for reviewing pull requests. While the main audience are reviewers, this can also be useful if you are contributing to the repository as well. + +## Code Style + +See [STYLE.md](./STYLE.md). + +## Secure Coding Practices + +Be sure to familiarize yourself with our [secure coding practices](./SECURITY.md#coding-practices). + +## Changesets + +We use changesets to track the changes in all published packages. Changesets both define what should go into the changelog of each package, but also what kind of version bump should be done for the next release. +An introduction to changesets can be found in our [contribution guidelines](./CONTRIBUTING.md#creating-changesets). + +When reviewing a changeset, the most important things to look for are the bump level, i.e. `major` / `minor` / `patch`, and whether the content is accurate and if it's written in a way that makes sense when reading it in the changelog for each package. + +### Reviewing Changeset Bump Levels + +### Reviewing Changeset Content + +Each changeset should be written in a way that describes the impact of the change for the end user of each package. The changesets end up in the changelog of each package, for example [@backstage/core-plugin-api](./packages/core-plugin-api/CHANGELOG.md). The changelogs are intended to provide both a summary of the new features as well as guidance in the case of breaking changes or deprecations. + +Some things that changeset should NOT contain are: + +- Internal architecture details - these are generally not interesting to end users, focus on the impact towards end users instead. +- Information related to a different package. +- A large amount of content, consider for example a separate migration guide instead, either in the package README or [./docs/](./docs/), and then link to that instead. +- Documentation - changesets can describe new features, but it should not be relied on for documenting them. Documentation should either be placed in [TSDoc](https://tsdoc.org) comments, package README, or [./docs/](./docs/). + +### When is a changeset needed? + +In general our changeset feedback bot will take care of informing whether a changeset is needed or not, but there are some edge cases. Whether a changeset is needed depends mostly on what files have been changed, but sometimes also + +Changes that do NOT need a new changeset: + +- Changes to any test, storybook, or other local development files, for example, `MyComponent.test.tsx`, `MyComponent.stories.tsx, `**mocks**/MyMock.ts`, `.eslintrc.js`, `setupTests.ts`, or `api-report.md`. Explained differently, it is only files that affect the published package that need changesets, such as source files and additional resources like `package.json`, `README.md`, `config.d.ts`, etc. +- When tweaking a change that has not yet been released, you can rely on and potentially modify the existing changeset. +- Changes that do not belong to a published packages, either because it's not a package at all, such as `docs/`, or because the package is private, such as `packages/app`. +- Changes that do not end up having an effect on the published package can be skipped, such as whitespace fixes or code formatting changes. It is also alright to include a short changeset for these kind of changes too. + +### Changeset Examples + +**Example 1** + +A new `EntityList` component has been added to `plugins/catalog-react`. + +#### GOOD + +```md +--- +'@backstage/plugin-catalog-react': minor +--- + +Added a new `EntityList` component that can be used to display detailed information about a list of entities. +``` + +The Catalog React library has reached version `1.x`, which means that feature additions that aren't breaking should be a `minor` change. We don't bother with too much documentation, keeping it short and sweet. The main purpose is to inform users that this new component exists and to give them an idea of how they can use it. + +#### BAD + +```md +--- +'@backstage/plugin-catalog-react': minor +'@backstage/plugin-catalog': minor +--- + +Added `EntityList` component. +Fixed a bug in the catalog index page. +``` + +This changeset is too short, it's best to give users an idea of how they can benefit from the new addition. + +It also includes changes affecting both the Catalog and Catalog React library. It should be split into two separate changeset for each of the two packages, otherwise we'll end up with redundant and unrelated information in both changelogs. + +```md +--- +'@backstage/plugin-catalog-react': major +--- + +Added a new `EntityList` component that can be used to display detailed information about a list of entities. The component looks like this: + +![EntityList screenshot](./screenshot.png) + +It accepts the following properties: + +- entities - The entities that should be listed. +- title - An optional formatting function for the list titles. +- dialog - An optional component that overrides the default details dialog. +``` + +This changeset is getting too detailed. It's not always bad to get this much into the weeds, but keep in mind that changesets are not easy to browse when search for information about specific APIs. It's better to document things like this separately and keep the changeset more lean. Also avoid linking to assets in changesets, keep them text-only. + +The change is also marked as a breaking `major` change. This should be changed to `minor` since adding new APIs is never a breaking change. + +## Review Checklist + +- [ ] API Reports + - [ ] Naming + - [ ] Breaking changes +- [ ] Changesets + - [ ] Content + - [ ] Bump level +- [ ] Have tests been added for new features bug fixes? +- [ ] Has documentation been added? From 3934e83101e6ef5df2afc6e76a3526e53e4a831e Mon Sep 17 00:00:00 2001 From: Chris Langhout Date: Mon, 17 Oct 2022 17:25:13 +0200 Subject: [PATCH 15/36] chore: update bol.com contacts Signed-off-by: Chris Langhout --- ADOPTERS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index 574623cd14..b6efa67836 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -6,7 +6,7 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | Organization | Contact | Description of Use | | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. | -| [bol.com](https://www.bol.com) | [@sagacity](https://github.com/sagacity) | Initial work being done to unify platform tooling. | +| [bol.com](https://www.bol.com) | [@acierto](https://github.com/acierto), [@clanghout](https://github.com/clanghout) | Initial work being done to unify platform tooling. | | [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | | [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up | | [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | From d383afb3722011879550febccb2595ffaad82149 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 18 Oct 2022 19:50:43 +0200 Subject: [PATCH 16/36] REVIEWING: add typescript section Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/REVIEWING.md b/REVIEWING.md index 9b21b49d51..f0fdc3fbcd 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -105,3 +105,150 @@ The change is also marked as a breaking `major` change. This should be changed t - [ ] Bump level - [ ] Have tests been added for new features bug fixes? - [ ] Has documentation been added? + +## Breaking Changes + +Identifying breaking changes can be quite tricky. You need to look at the changes from both the point of view of consumers and producers of APIs, as well as behavioral changes. In this section we explore a couple of methods for identifying whether a change is breaking or not. + +### TypeScript + +Typescript is a huge help when it comes to identifying breaking changes, as well as the API Reports that we generate for all packages. Most of the time it is enough to only look at the API Reports to determine whether a change is breaking or not. + +In this section we will be talking about changed "types", but that refers to any kind of exported symbol from a packages, such as TypeScript types and interfaces, functions, classes, constants, etc. + +An important distinction to make when looking at changes to an API Report is the direction of a changed type, that is whether it's used as input or output from the user's point of view. In the next two sections we'll dive into the different directions of a type, and how it affects whether a change is breaking or not. + +#### Input Types + +A input type is one that users need to provide to the package by consumers. The most common form of input type are function, constructor and method parameters. + +The following is an example where `MyComponentProps` is an input type: + +```ts +type MyComponentProps = { + title: string; + size?: 'small' | 'medium' | 'large'; +}; + +function MyComponent(props: MyComponentProps): JSX.Element; +``` + +And from the consumer's point of view it would look something like this: + +```tsx + +``` + +When modifying an input type, any change that increases constraints are breaking. For example, if we made the `size` prop required, that would be a breaking change. Likewise, if we changed the type of `size` to `'small' | 'large'`, that would also be breaking. + +On the other hand, it's fine to relax constraints without it being a breaking change. For example, if we made the `title` prop optional, that would not be breaking. Likewise, if we changed the type of `size` to `'small' | 'medium' | 'large' | 'huge'`, that would not be breaking either. It is also possible to add new properties without it being a breaking change, as long as they are optional. + +There's an edge-case where completely removing a property is also considered a breaking change. That's because of TypeScript being strict and refusing unknown properties, rather than a runtime breaking change. It is typically and easy thing for consumers to fix though. + +Another way to think about the rules for evolving input types is that the old type must be assignable to the new type. In this case for example `_props: NewComponentProps = {} as OldComponentProps`. It's not a silver bullet though, because of edge-cases like the one mentioned above. + +#### Output Types + +An output type is one that the user receives from the packages. One of the most obvious examples here are the top-level exports from the package itself, but it also includes function return types. + +The following is an example where both `useSize` and `Size` are output types: + +```ts +type Box = { + title: string; + shape?: 'square' | 'rounded'; +}; + +function useBox(): Box; +``` + +And from the consumer's point of view it would look something like this: + +```ts +const { title, shape } = useBox(); +``` + +When modifying an output type, any change that reduces constraints are breaking. For example, if we made the `title` property optional, that would be a breaking change, or if we changed the type of `shape` to `'square' | 'rounded' | 'octagon'`. + +Adding new properties is not a breaking change, regardless of whether they are optional or not. Removing properties is on the other hand always breaking. + +It is generally fine to increase constraints without it being a breaking change. For example, if we made the `title` property required, that would not be breaking. + +There are some edge-cases though, for example if `shape` was changed to just `'square'`, that would be a breaking change because consumers might be checking for `box.shape === 'rounded'`, which would then be breaking. It's typically a quite easy thing for consumers to fix though. More generally, type unions and discriminated unions are quite troublesome in output types, as both adding and removing types from them are considered breaking changes. + +Another way to think about the rules for evolving output types is that the new type must be assignable to the old type. In this case for example `_box: OldBox = {} as NewBox`. It's not a silver bullet though, because of edge-cases like the one mentioned above. + +#### I/O Types + +Some types are considered both input and output types. For example, consider the following example: + +```ts +type Point = { + x: number; + y: number; +}; + +function trimCoords(point: Point): Point; +``` + +In this case `Point` is both an input and output type. This means that the only changes we can make to the type that aren't breaking are the intersection of allowed changes between input and output types. In practice this only allows for the addition of new optional properties. Because of this constraint it is generally best to avoid using I/O types, and keep the input separated from the output. + +There are some cases where I/O types favor either input or output when it comes to API stability. For example, all types used by Utility APIs are I/O types, but the stability of the output is a lot more important than the stability of the input. That is because it's a lot easier for the single producer of the input interface to adapt to changes compared to all consumers of the API that use it as an output type. + +#### Identifying the Direction + +The only way to identify the direction of a type is to look at the context in which it's being used. In particular this can be tricky when looking at individual type aliases and interfaces, as you need to look at the rest of the package exports to see how the type is being used. + +One important rule is that the context considered for any type is limited to only the package in which the type is declared. Just because a type is imported in a different package and used as an input type does not make it an input type. + +The following rules can be used to identify the direction of a type alias or interface: + +- If the type is used in an input context, for example function parameter, then it's an input type. +- If the type is used in an output context, for example function return type, then it's an output type. +- If the type is referenced by another type, then it inherits the direction of that type, except if referenced through a function callback, in which case the direction is reversed. +- If the type is used or inherits both input and output context, then it's an I/O type. +- If the type is not referenced anywhere else, then it's an I/O type. + +Below is an example of the public API of a package, with type directions assigned to each export: + +```ts +// I/O, used by getPoint as return type and referenced by BoxProps, an input type +interface Point { + x: number; + y: number; +} + +function getPoint(): Point; + +// Input, used by Box as parameter type +interface BoxProps { + point?: Point +} + +function Box(props: BoxProps): JSX.Element; + +// Output, used by createWidget as return type +interface Widget { + ... +} + +// Output, as it's referenced by WidgetOptions, which is an input +// type, but the render callback causes a direction reversal +interface WidgetProps { + ... +} + +// Input, just like WidgetProps this is due to the direction reversal +// caused by the render callback +type RenderedWidget = JSX.Element | null; + +// Input, used by createWidget parameter type +interface WidgetOptions { + render(props: WidgetProps): RenderedWidget; +} + +function createWidget(options: WidgetOptions): Widget; + +// I/O, since it's not referenced anywhere else +type LabelStyle = 'normal' | 'thin'; +``` From b3d522b0a8db57f205d7b0d6a7250f8f5b16454c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 19 Oct 2022 10:42:40 +0200 Subject: [PATCH 17/36] REVIEWING: example tweaks talk about behavioral changes Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 49 ++++++++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/REVIEWING.md b/REVIEWING.md index f0fdc3fbcd..074f568638 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -4,7 +4,7 @@ This file provides pointers for reviewing pull requests. While the main audience ## Code Style -See [STYLE.md](./STYLE.md). +See [STYLE.md](./STYLE.md). In particular, make sure that naming follows established conventions within the project and/or package. ## Secure Coding Practices @@ -41,13 +41,15 @@ Changes that do NOT need a new changeset: - Changes that do not belong to a published packages, either because it's not a package at all, such as `docs/`, or because the package is private, such as `packages/app`. - Changes that do not end up having an effect on the published package can be skipped, such as whitespace fixes or code formatting changes. It is also alright to include a short changeset for these kind of changes too. -### Changeset Examples +### Changeset Example -**Example 1** +Consider the following scenario for a changeset: A new `EntityList` component has been added to `plugins/catalog-react`. -#### GOOD +Below are examples of a good and two bad changesets for that change. + +**GOOD** ```md --- @@ -57,14 +59,14 @@ A new `EntityList` component has been added to `plugins/catalog-react`. Added a new `EntityList` component that can be used to display detailed information about a list of entities. ``` -The Catalog React library has reached version `1.x`, which means that feature additions that aren't breaking should be a `minor` change. We don't bother with too much documentation, keeping it short and sweet. The main purpose is to inform users that this new component exists and to give them an idea of how they can use it. +The `@backstage/plugin-catalog-react` package has reached version `1.x`, which means that feature additions that aren't breaking should be a `minor` change. We don't bother with too much documentation, keeping it short and sweet. The main purpose is to inform users that this new component exists and to give them an idea of how they can use it. -#### BAD +**BAD** ```md --- '@backstage/plugin-catalog-react': minor -'@backstage/plugin-catalog': minor +'@backstage/plugin-catalog': patch --- Added `EntityList` component. @@ -75,6 +77,10 @@ This changeset is too short, it's best to give users an idea of how they can ben It also includes changes affecting both the Catalog and Catalog React library. It should be split into two separate changeset for each of the two packages, otherwise we'll end up with redundant and unrelated information in both changelogs. +Lastly, the `@backstage/plugin-catalog-react` package has reached `1.x`, which means that new features should be introduced through a `minor` bump. We'd only use `patch` bumps for minor changes or fixes that do not affect the public API. + +**BAD** + ```md --- '@backstage/plugin-catalog-react': major @@ -95,22 +101,21 @@ This changeset is getting too detailed. It's not always bad to get this much int The change is also marked as a breaking `major` change. This should be changed to `minor` since adding new APIs is never a breaking change. -## Review Checklist - -- [ ] API Reports - - [ ] Naming - - [ ] Breaking changes -- [ ] Changesets - - [ ] Content - - [ ] Bump level -- [ ] Have tests been added for new features bug fixes? -- [ ] Has documentation been added? - ## Breaking Changes Identifying breaking changes can be quite tricky. You need to look at the changes from both the point of view of consumers and producers of APIs, as well as behavioral changes. In this section we explore a couple of methods for identifying whether a change is breaking or not. -### TypeScript +### Behavioral Changes + +These are changes where the behavior of the code changes, but the public API remains the same. They can be anything from tiny tweaks, like adding a bit of padding to a visual element, to a complete redesign and refactor of an entire plugin. + +It's hard to set up exact rules for when a behavioral change is breaking or not. In some cases it's obvious, for example if you remove important functionality of a system, while in other cases it can be very hard to tell. In the end what's important is whether a significant number of users of the package will be negatively impacted by the change. One question that you can ask yourself here is "is it likely that there are users that don't want the new behavior, or will need to change their code to adapt to the new behavior?" If the answer is yes, then it's likely a breaking change. You do also want to keep [xkcd.com/1172](https://xkcd.com/1172/) in mind though. + +Note that even a bug fix can be considered a breaking change in some situations. One things to lean on in that case is what the _documented_ behavior is. If the current behavior does not match the documented behavior, then a change to match the documentation is generally not a breaking change. That is unless it is likely that there are a significant number of users that will be impacted by the change. + +For tricky behavioral changes you may simply need to let end users provide feedback. This can be done either by hiding the new behavior behind an experimental feature switch, or by releasing the change early on in the release cycle, preferably in the first or second next line release. Be ready to respond to feedback and potentially revert the change if needed. + +### Public API Changes Typescript is a huge help when it comes to identifying breaking changes, as well as the API Reports that we generate for all packages. Most of the time it is enough to only look at the API Reports to determine whether a change is breaking or not. @@ -218,6 +223,7 @@ interface Point { y: number; } +// Output, since it's an exported function function getPoint(): Point; // Input, used by Box as parameter type @@ -225,6 +231,7 @@ interface BoxProps { point?: Point } +// Output, since it's an exported function function Box(props: BoxProps): JSX.Element; // Output, used by createWidget as return type @@ -247,8 +254,12 @@ interface WidgetOptions { render(props: WidgetProps): RenderedWidget; } +// Output, since it's an exported function function createWidget(options: WidgetOptions): Widget; // I/O, since it's not referenced anywhere else type LabelStyle = 'normal' | 'thin'; + +// Output, since it's an exported constant +const LABEL_SIZE: number; ``` From 8a4f4d1f307ebdf8fa216283bd80be9bff771358 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 19 Oct 2022 13:19:00 +0200 Subject: [PATCH 18/36] REVIEWING: versioning policy and changeset bump sections Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/REVIEWING.md b/REVIEWING.md index 074f568638..d118eee367 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -10,6 +10,12 @@ See [STYLE.md](./STYLE.md). In particular, make sure that naming follows establi Be sure to familiarize yourself with our [secure coding practices](./SECURITY.md#coding-practices). +## Release & Versioning Policy + +When reviewing pull requests it's important to consider our [versioning policy and release cycle](https://backstage.io/docs/overview/versioning-policy). Generally the most important bit is our [package versioning policy](https://backstage.io/docs/overview/versioning-policy#package-versioning-policy), which describes when and how we can ship breaking changes. We'll dive into how to identify breaking changes in a later section section. + +One other thing to keep in mind, especially when merging pull requests, is where in the release cycle we're currently at. In particular you want to avoid merging any large or risky changes towards the end of each release cycle. If there is a change that is ready to be merged, but you want to hold off until the next main line release, then you can label it with the `merge-after-release` label. + ## Changesets We use changesets to track the changes in all published packages. Changesets both define what should go into the changelog of each package, but also what kind of version bump should be done for the next release. @@ -19,6 +25,19 @@ When reviewing a changeset, the most important things to look for are the bump l ### Reviewing Changeset Bump Levels +The following table provides a reference for what type of version bump is needed for each individual package. This applies to each individual package separately, it does not matter what the scope of a change is in any other broader scope. + +| Scope | Current Package Version | Bump Level | +| --------------- | ----------------------- | ---------- | +| Breaking Change | `1.0` and above | `major` | +| New Feature | `1.0` and above | `minor` | +| Fix | `1.0` and above | `patch` | +| Breaking Change | `0.x` | `minor` | +| New Feature | `0.x` | `patch` | +| Fix | `0.x` | `patch` | + +The only situation where a package that is currently at `0.x` can have a `major` bump is if all owners and stakeholders of the package agree that the package is ready to be released as `1.0`. + ### Reviewing Changeset Content Each changeset should be written in a way that describes the impact of the change for the end user of each package. The changesets end up in the changelog of each package, for example [@backstage/core-plugin-api](./packages/core-plugin-api/CHANGELOG.md). The changelogs are intended to provide both a summary of the new features as well as guidance in the case of breaking changes or deprecations. From 78577cb46e673a23eaf5ae87c8e0f1b6d60b55ad Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 19 Oct 2022 13:22:41 +0200 Subject: [PATCH 19/36] REVIEWING: expand code style section Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/REVIEWING.md b/REVIEWING.md index d118eee367..32c2a797ae 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -4,7 +4,11 @@ This file provides pointers for reviewing pull requests. While the main audience ## Code Style -See [STYLE.md](./STYLE.md). In particular, make sure that naming follows established conventions within the project and/or package. +See our code style documented at [STYLE.md](./STYLE.md). + +In particular when it comes to naming, make sure that naming follows established conventions within the project and/or package. + +When adding new dependencies to packages it is always preferred to use version ranges that are already in use by other packages in the repository. This helps minimize lockfile changes and reduce package duplication, both in our repository as well as other Backstage installations. ## Secure Coding Practices From 1156ed81398abd69820d01c345d71bfbeb855828 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 19 Oct 2022 14:22:46 +0200 Subject: [PATCH 20/36] REVIEWING: couple more sections and tweaks Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 83 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 27 deletions(-) diff --git a/REVIEWING.md b/REVIEWING.md index 32c2a797ae..6a54ae875e 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -1,6 +1,6 @@ # Introduction -This file provides pointers for reviewing pull requests. While the main audience are reviewers, this can also be useful if you are contributing to the repository as well. +This file provides pointers for reviewing pull requests. While the main audience are reviewers, this can also be useful if you are contributing to this repository. ## Code Style @@ -16,20 +16,20 @@ Be sure to familiarize yourself with our [secure coding practices](./SECURITY.md ## Release & Versioning Policy -When reviewing pull requests it's important to consider our [versioning policy and release cycle](https://backstage.io/docs/overview/versioning-policy). Generally the most important bit is our [package versioning policy](https://backstage.io/docs/overview/versioning-policy#package-versioning-policy), which describes when and how we can ship breaking changes. We'll dive into how to identify breaking changes in a later section section. +When reviewing pull requests it's important to consider our [versioning policy and release cycle](https://backstage.io/docs/overview/versioning-policy). Generally the most important bit is our [package versioning policy](https://backstage.io/docs/overview/versioning-policy#package-versioning-policy), which describes when and how we can ship breaking changes. We'll dive into how to identify breaking changes in a different section. One other thing to keep in mind, especially when merging pull requests, is where in the release cycle we're currently at. In particular you want to avoid merging any large or risky changes towards the end of each release cycle. If there is a change that is ready to be merged, but you want to hold off until the next main line release, then you can label it with the `merge-after-release` label. ## Changesets -We use changesets to track the changes in all published packages. Changesets both define what should go into the changelog of each package, but also what kind of version bump should be done for the next release. +We use changesets to track changes in all published packages. Changesets both define what should go into the changelog of each package, but also what kind of version bump should be done for the next release. An introduction to changesets can be found in our [contribution guidelines](./CONTRIBUTING.md#creating-changesets). -When reviewing a changeset, the most important things to look for are the bump level, i.e. `major` / `minor` / `patch`, and whether the content is accurate and if it's written in a way that makes sense when reading it in the changelog for each package. +When reviewing a changeset, the most important things to look for are the bump levels, i.e. `major` / `minor` / `patch`, as well as whether the content is accurate and if it's written in a way that makes sense when reading it in the changelog for each package. ### Reviewing Changeset Bump Levels -The following table provides a reference for what type of version bump is needed for each individual package. This applies to each individual package separately, it does not matter what the scope of a change is in any other broader scope. +The following table provides a reference for what type of version bump is needed for each package. This is applied separately to each individual package, it does not matter what the scope of a change is in any other broader context. | Scope | Current Package Version | Bump Level | | --------------- | ----------------------- | ---------- | @@ -44,25 +44,25 @@ The only situation where a package that is currently at `0.x` can have a `major` ### Reviewing Changeset Content -Each changeset should be written in a way that describes the impact of the change for the end user of each package. The changesets end up in the changelog of each package, for example [@backstage/core-plugin-api](./packages/core-plugin-api/CHANGELOG.md). The changelogs are intended to provide both a summary of the new features as well as guidance in the case of breaking changes or deprecations. +Each changeset should be written in a way that describes the impact of the change for the users of each package. The contents of the changesets will end up in the changelog of each package, for example [@backstage/core-plugin-api](./packages/core-plugin-api/CHANGELOG.md). The changelogs are intended to provide both a summary of the new features as well as guidance in the case of breaking changes or deprecations. Some things that changeset should NOT contain are: -- Internal architecture details - these are generally not interesting to end users, focus on the impact towards end users instead. +- Internal architecture details - these are generally not interesting to users, focus on the impact towards users of the package instead. - Information related to a different package. - A large amount of content, consider for example a separate migration guide instead, either in the package README or [./docs/](./docs/), and then link to that instead. - Documentation - changesets can describe new features, but it should not be relied on for documenting them. Documentation should either be placed in [TSDoc](https://tsdoc.org) comments, package README, or [./docs/](./docs/). ### When is a changeset needed? -In general our changeset feedback bot will take care of informing whether a changeset is needed or not, but there are some edge cases. Whether a changeset is needed depends mostly on what files have been changed, but sometimes also +In general our changeset feedback bot will take care of informing whether a changeset is needed or not, but there are some edge cases. Whether a changeset is needed depends mostly on what files have been changed, but sometimes also on the kind of change that has been made. Changes that do NOT need a new changeset: - Changes to any test, storybook, or other local development files, for example, `MyComponent.test.tsx`, `MyComponent.stories.tsx, `**mocks**/MyMock.ts`, `.eslintrc.js`, `setupTests.ts`, or `api-report.md`. Explained differently, it is only files that affect the published package that need changesets, such as source files and additional resources like `package.json`, `README.md`, `config.d.ts`, etc. -- When tweaking a change that has not yet been released, you can rely on and potentially modify the existing changeset. +- When tweaking a change that has not yet been released, you can rely on and potentially modify the existing changeset instead. - Changes that do not belong to a published packages, either because it's not a package at all, such as `docs/`, or because the package is private, such as `packages/app`. -- Changes that do not end up having an effect on the published package can be skipped, such as whitespace fixes or code formatting changes. It is also alright to include a short changeset for these kind of changes too. +- Changes that do not end up having an effect on the published package, such as whitespace fixes or code formatting changes. Although it's also fine to have a short changeset for these kind of changes too. ### Changeset Example @@ -70,7 +70,7 @@ Consider the following scenario for a changeset: A new `EntityList` component has been added to `plugins/catalog-react`. -Below are examples of a good and two bad changesets for that change. +Below are examples of a good and three bad changesets for that change. **GOOD** @@ -89,18 +89,17 @@ The `@backstage/plugin-catalog-react` package has reached version `1.x`, which m ```md --- '@backstage/plugin-catalog-react': minor -'@backstage/plugin-catalog': patch +'@backstage/plugin-catalog': minor --- Added `EntityList` component. + Fixed a bug in the catalog index page. ``` This changeset is too short, it's best to give users an idea of how they can benefit from the new addition. -It also includes changes affecting both the Catalog and Catalog React library. It should be split into two separate changeset for each of the two packages, otherwise we'll end up with redundant and unrelated information in both changelogs. - -Lastly, the `@backstage/plugin-catalog-react` package has reached `1.x`, which means that new features should be introduced through a `minor` bump. We'd only use `patch` bumps for minor changes or fixes that do not affect the public API. +It also includes changes affecting both the Catalog and Catalog React library. It should be split into two separate changesets for each of the two packages, otherwise we'll end up with redundant and unrelated information in both changelogs. **BAD** @@ -120,17 +119,31 @@ It accepts the following properties: - dialog - An optional component that overrides the default details dialog. ``` -This changeset is getting too detailed. It's not always bad to get this much into the weeds, but keep in mind that changesets are not easy to browse when search for information about specific APIs. It's better to document things like this separately and keep the changeset more lean. Also avoid linking to assets in changesets, keep them text-only. +This changeset is getting too detailed. It's not always bad to get this much into the weeds, but keep in mind that changesets are not easy to browse when searching for information about specific APIs. It's better to document things like this separately and keep the changeset more lean. Also avoid linking to assets in changesets, keep them text-only. The change is also marked as a breaking `major` change. This should be changed to `minor` since adding new APIs is never a breaking change. +**BAD** + +```md +--- +'@backstage/plugin-catalog-react': patch +--- + +Added a new `EntityList` component that can be used to display detailed information about a list of entities. The `ListView` component was also refactored in order to make it possible to reuse it between the new `EntityList` and `KindList` components. +``` + +Assuming that the `ListView` component is not public API, this changeset goes into details that are not interesting to the user of the package. Internal changes do not need to be highlighted in changesets. If an internal refactor is the only change then it's alright to say something short like "Internal refactor to improve code reuse", but otherwise those details should be left out. + +The `@backstage/plugin-catalog-react` package has also reached `1.x`, which means that new features should be introduced through a `minor` bump. We'd only use `patch` bumps for minor changes or fixes that do not affect the public API. + ## Breaking Changes Identifying breaking changes can be quite tricky. You need to look at the changes from both the point of view of consumers and producers of APIs, as well as behavioral changes. In this section we explore a couple of methods for identifying whether a change is breaking or not. ### Behavioral Changes -These are changes where the behavior of the code changes, but the public API remains the same. They can be anything from tiny tweaks, like adding a bit of padding to a visual element, to a complete redesign and refactor of an entire plugin. +These are changes where the behavior of the code changes, but the public API is unchanged or doesn't have any breaking changes. They can be anything from tiny tweaks, like adding a bit of padding to a visual element, to a complete redesign and refactor of an entire plugin. It's hard to set up exact rules for when a behavioral change is breaking or not. In some cases it's obvious, for example if you remove important functionality of a system, while in other cases it can be very hard to tell. In the end what's important is whether a significant number of users of the package will be negatively impacted by the change. One question that you can ask yourself here is "is it likely that there are users that don't want the new behavior, or will need to change their code to adapt to the new behavior?" If the answer is yes, then it's likely a breaking change. You do also want to keep [xkcd.com/1172](https://xkcd.com/1172/) in mind though. @@ -140,15 +153,31 @@ For tricky behavioral changes you may simply need to let end users provide feedb ### Public API Changes -Typescript is a huge help when it comes to identifying breaking changes, as well as the API Reports that we generate for all packages. Most of the time it is enough to only look at the API Reports to determine whether a change is breaking or not. +Typescript is a huge help when it comes to identifying breaking changes, as well as the API Reports that we generate for all packages. Most of the time it is enough to only look at the API Reports to determine whether a change is breaking or not. If you determine that a change is breaking at the TypeScript level, then it is a breaking change. -In this section we will be talking about changed "types", but that refers to any kind of exported symbol from a packages, such as TypeScript types and interfaces, functions, classes, constants, etc. +In this section we will be talking about changed "types", but by that we mean any kind of exported symbol from a packages, such as TypeScript types aliases or interfaces, functions, classes, constants, etc. -An important distinction to make when looking at changes to an API Report is the direction of a changed type, that is whether it's used as input or output from the user's point of view. In the next two sections we'll dive into the different directions of a type, and how it affects whether a change is breaking or not. +#### API Reports + +We generate API Reports using the [API Extractor](https://api-extractor.com/) tool. These reports are generated for most packages in the Backstage repository, and are stored in the `api-report.md` file of each package. For CLI package we use custom tooling, and instead store the result in `cli-report.md`. Whenever the public API of a package changes, the API Report needs to be updated to reflect the new state of the API. Our CI checks will fail if the API reports are not up to date in a pull request. + +Each API report contains a list of all the exported types of each package. As long as the API report does not have any warnings it will contain the full publicly facing API of the package, meaning you do not need to consider any other changes to the package from the point of view of TypeScript API stability. + +Exported types can be marked with either `@public`, `@alpha` or `@beta` release tags. It is only the `@public` exports that we consider to be part of the stable API. The `@alpha` and `@beta` exports are considered unstable and can be changed at any time without needing a breaking package versions bump. However, this **ONLY** applies if the package has been configured to use experimental type builds, which looks like this in `package.json`: + +```json + "build": "backstage-cli package build --experimental-type-build" +``` + +If a package does not have this configuration, then all exported types are considered stable, even if they are marked as `@alpha` or `@beta`. + +#### Type Contract Direction + +An important distinction to make when looking at changes to an API Report is the direction of the contract of a changed type, that is, whether it's used as input or output from the user's point of view. In the next two sections we'll dive into the different directions of a type contract, and how it affects whether a change is breaking or not. #### Input Types -A input type is one that users need to provide to the package by consumers. The most common form of input type are function, constructor and method parameters. +A input type is one where a value needs to be provided by users of the package. The most common form of input type are function, constructor, and method parameters. The following is an example where `MyComponentProps` is an input type: @@ -161,7 +190,7 @@ type MyComponentProps = { function MyComponent(props: MyComponentProps): JSX.Element; ``` -And from the consumer's point of view it would look something like this: +And from the package user's point of view it would look something like this: ```tsx @@ -177,9 +206,9 @@ Another way to think about the rules for evolving input types is that the old ty #### Output Types -An output type is one that the user receives from the packages. One of the most obvious examples here are the top-level exports from the package itself, but it also includes function return types. +An output type is one that the user receives from the packages. One of the most obvious examples here are the top-level exports from the package itself, but it also includes for example function return types. -The following is an example where both `useSize` and `Size` are output types: +The following is an example where both `useBox` and `Box` are output types: ```ts type Box = { @@ -223,9 +252,9 @@ In this case `Point` is both an input and output type. This means that the only There are some cases where I/O types favor either input or output when it comes to API stability. For example, all types used by Utility APIs are I/O types, but the stability of the output is a lot more important than the stability of the input. That is because it's a lot easier for the single producer of the input interface to adapt to changes compared to all consumers of the API that use it as an output type. -#### Identifying the Direction +#### Identifying the Contract Direction -The only way to identify the direction of a type is to look at the context in which it's being used. In particular this can be tricky when looking at individual type aliases and interfaces, as you need to look at the rest of the package exports to see how the type is being used. +The only way to identify the contract direction of a type is to look at the context in which it's being used. In particular this can be tricky when looking at individual type aliases and interfaces, as you need to look at the rest of the package exports to see how the type is being used. One important rule is that the context considered for any type is limited to only the package in which the type is declared. Just because a type is imported in a different package and used as an input type does not make it an input type. @@ -234,7 +263,7 @@ The following rules can be used to identify the direction of a type alias or int - If the type is used in an input context, for example function parameter, then it's an input type. - If the type is used in an output context, for example function return type, then it's an output type. - If the type is referenced by another type, then it inherits the direction of that type, except if referenced through a function callback, in which case the direction is reversed. -- If the type is used or inherits both input and output context, then it's an I/O type. +- If the type is used or inherits both input and output contexts, then it's an I/O type. - If the type is not referenced anywhere else, then it's an I/O type. Below is an example of the public API of a package, with type directions assigned to each export: From c0098f1bdd968f518006603cf42d32325fd74441 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 19 Oct 2022 15:20:26 +0200 Subject: [PATCH 21/36] add TSDoc to vocab Signed-off-by: Patrik Oldsberg --- .github/vale/Vocab/Backstage/accept.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/vale/Vocab/Backstage/accept.txt b/.github/vale/Vocab/Backstage/accept.txt index e40dfffda1..19205d0c8d 100644 --- a/.github/vale/Vocab/Backstage/accept.txt +++ b/.github/vale/Vocab/Backstage/accept.txt @@ -341,6 +341,7 @@ transpiled transpiler transpilers truthy +TSDoc typeahead ui unbreak From 9c767e8f454f8e5203aae2f6886828a3a35d2e35 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 19 Oct 2022 14:02:09 +0000 Subject: [PATCH 22/36] Update SVGR monorepo packages to 6.5.x Signed-off-by: Renovate Bot --- .changeset/renovate-6fb5f1b.md | 8 ++ packages/cli/package.json | 8 +- yarn.lock | 164 +++++++++++++++++---------------- 3 files changed, 95 insertions(+), 85 deletions(-) create mode 100644 .changeset/renovate-6fb5f1b.md diff --git a/.changeset/renovate-6fb5f1b.md b/.changeset/renovate-6fb5f1b.md new file mode 100644 index 0000000000..f98cc64ea4 --- /dev/null +++ b/.changeset/renovate-6fb5f1b.md @@ -0,0 +1,8 @@ +--- +'@backstage/cli': patch +--- + +Updated dependency `@svgr/plugin-jsx` to `6.5.x`. +Updated dependency `@svgr/plugin-svgo` to `6.5.x`. +Updated dependency `@svgr/rollup` to `6.5.x`. +Updated dependency `@svgr/webpack` to `6.5.x`. diff --git a/packages/cli/package.json b/packages/cli/package.json index f460bfa4e0..a377bad909 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -48,10 +48,10 @@ "@spotify/eslint-config-typescript": "^14.0.0", "@sucrase/jest-plugin": "^2.1.1", "@sucrase/webpack-loader": "^2.0.0", - "@svgr/plugin-jsx": "6.3.x", - "@svgr/plugin-svgo": "6.3.x", - "@svgr/rollup": "6.3.x", - "@svgr/webpack": "6.3.x", + "@svgr/plugin-jsx": "6.5.x", + "@svgr/plugin-svgo": "6.5.x", + "@svgr/rollup": "6.5.x", + "@svgr/webpack": "6.5.x", "@swc/core": "^1.2.239", "@swc/helpers": "^0.4.7", "@swc/jest": "^0.2.22", diff --git a/yarn.lock b/yarn.lock index ef0c3fa984..b5614fe26a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3357,10 +3357,10 @@ __metadata: "@spotify/eslint-config-typescript": ^14.0.0 "@sucrase/jest-plugin": ^2.1.1 "@sucrase/webpack-loader": ^2.0.0 - "@svgr/plugin-jsx": 6.3.x - "@svgr/plugin-svgo": 6.3.x - "@svgr/rollup": 6.3.x - "@svgr/webpack": 6.3.x + "@svgr/plugin-jsx": 6.5.x + "@svgr/plugin-svgo": 6.5.x + "@svgr/rollup": 6.5.x + "@svgr/webpack": 6.5.x "@swc/core": ^1.2.239 "@swc/helpers": ^0.4.7 "@swc/jest": ^0.2.22 @@ -12333,147 +12333,149 @@ __metadata: languageName: node linkType: hard -"@svgr/babel-plugin-add-jsx-attribute@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:6.3.1" +"@svgr/babel-plugin-add-jsx-attribute@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 3a04515743af5f67c3c38cf414f225cb4c266db29fbf37f4bd970be0ab5b6a2c18e9e8c7de3303a70168909106077860b0fdfb9ee4de9c50d994181b4850e615 + checksum: f65ca26905240b685929a7766411618700bda233673cebd74eb9a8da45af8ce8e0536074a178b37762cd23db8868db494e15067e74b2d73e377b2d247895d054 languageName: node linkType: hard -"@svgr/babel-plugin-remove-jsx-attribute@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-remove-jsx-attribute@npm:6.3.1" +"@svgr/babel-plugin-remove-jsx-attribute@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-remove-jsx-attribute@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: ea78848a1d987a30320f84263399769d80064a593cf8af41bb5d4e1699869f9395d3ed18c7d35a06c85d4c46f93df3a9864981d6844296c7a26d19b6bfc39098 + checksum: 7a4dfc1345f5855b010684e9c5301731842bf91d72b82ce5cc4c82c80b94de1036e447a8a00fb306a6dd575cb4c640d8ce3cfee6607ddbb804796a77284c7f22 languageName: node linkType: hard -"@svgr/babel-plugin-remove-jsx-empty-expression@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-remove-jsx-empty-expression@npm:6.3.1" +"@svgr/babel-plugin-remove-jsx-empty-expression@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-remove-jsx-empty-expression@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 3975ee4ca649fde5acba30748f7766c1362b7b39b54d6164b8f27a13cee0b0f2b2cf05e8eda476a4c833be42697a1e0b47b0c8fae8a66563ba23ac9537fdd502 + checksum: 3e173f720d530f9f71f8506f3eb78583eec3d87d66e385efe1ef3b3ebfc4e3680ec30f36414726de6a163e99ca69f54886022967e49476dea522267e1986936e languageName: node linkType: hard -"@svgr/babel-plugin-replace-jsx-attribute-value@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-replace-jsx-attribute-value@npm:6.3.1" +"@svgr/babel-plugin-replace-jsx-attribute-value@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-replace-jsx-attribute-value@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 8a65eb8aa99e3c3e4710aff34d20099a4f2a610d79a5ef705ce4050ff28a25c1f22d813c5021a6c9399725559aba28580674f68b4b5a202028754541e3243453 + checksum: e8e77e4026f2e2f910a3495be8bd283f413865449b6e2f639318d76edc05b373e18d86e1210c808038a5477ae273858d3b313b2a7df8e6929450ed902c1441bc languageName: node linkType: hard -"@svgr/babel-plugin-svg-dynamic-title@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-svg-dynamic-title@npm:6.3.1" +"@svgr/babel-plugin-svg-dynamic-title@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-svg-dynamic-title@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 026f440d2e609532b1a40434dbd97cae54d0ed9090a6f4069d75523611f6d45ac9983a5c69c10cfd4a6ab76bc854c529c99c327e1a11fd8e65b6f59a930181b9 + checksum: 55f36f6e3ef986f2d0ba4cd9e2ebf6b17d68c5c2cf98821abc0dbd551d4fd7d92cf3cda83a91898d988ad7118a9768042ac5afe534ad594bdac024fe0009bae1 languageName: node linkType: hard -"@svgr/babel-plugin-svg-em-dimensions@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-svg-em-dimensions@npm:6.3.1" +"@svgr/babel-plugin-svg-em-dimensions@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-svg-em-dimensions@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 02aa7fa0afd6def11af7f00401918926626faba861f9869b7359d532d524dcf5062810728bf5e8117275dd4c340dc34a24d55c8c705c7a6d678988db8619428b + checksum: af6508c042a7d256081c09520e79e2d3278ecf361a74707dcc1bc61713845ec7fd6eeb52bbc3a2e114ecbbb1df49b16674caf8b97345570a6c5f0a631118cb5e languageName: node linkType: hard -"@svgr/babel-plugin-transform-react-native-svg@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-transform-react-native-svg@npm:6.3.1" +"@svgr/babel-plugin-transform-react-native-svg@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-transform-react-native-svg@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 2cbe20f7016eab8de3515c2bf9887a6399e20d8078614b1316952794ec03c331ea0127c689a258c115b5ca29c279fafef972238c8b491841c49a86b84f408088 + checksum: 0e7f1d85a25ef0c49b2bfacdc9ae80520959a0925304e030edc739684c75d41a7d4173ac4a1cd6ec8dee8bc618e7465da49ae59dd5638b87e75dccee05498f4c languageName: node linkType: hard -"@svgr/babel-plugin-transform-svg-component@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-transform-svg-component@npm:6.3.1" +"@svgr/babel-plugin-transform-svg-component@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-transform-svg-component@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 76113730f5cbcc58d42e2254168db98bc40201cd7e90d58cd3137f332fd8328ae113ce64a59c2a60e9ca92730030eb7e4ab8476fdbc31cf9ec0cc8221a7ffb96 + checksum: 8613ef673b7e881d661057188729419b9a9d0b3802247954283293698a9910d76414b4fe106b5255e06fb2329c41f9da147ac5e153149b3b82024e06ab87a2b3 languageName: node linkType: hard -"@svgr/babel-preset@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-preset@npm:6.3.1" +"@svgr/babel-preset@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-preset@npm:6.5.0" dependencies: - "@svgr/babel-plugin-add-jsx-attribute": ^6.3.1 - "@svgr/babel-plugin-remove-jsx-attribute": ^6.3.1 - "@svgr/babel-plugin-remove-jsx-empty-expression": ^6.3.1 - "@svgr/babel-plugin-replace-jsx-attribute-value": ^6.3.1 - "@svgr/babel-plugin-svg-dynamic-title": ^6.3.1 - "@svgr/babel-plugin-svg-em-dimensions": ^6.3.1 - "@svgr/babel-plugin-transform-react-native-svg": ^6.3.1 - "@svgr/babel-plugin-transform-svg-component": ^6.3.1 + "@svgr/babel-plugin-add-jsx-attribute": ^6.5.0 + "@svgr/babel-plugin-remove-jsx-attribute": ^6.5.0 + "@svgr/babel-plugin-remove-jsx-empty-expression": ^6.5.0 + "@svgr/babel-plugin-replace-jsx-attribute-value": ^6.5.0 + "@svgr/babel-plugin-svg-dynamic-title": ^6.5.0 + "@svgr/babel-plugin-svg-em-dimensions": ^6.5.0 + "@svgr/babel-plugin-transform-react-native-svg": ^6.5.0 + "@svgr/babel-plugin-transform-svg-component": ^6.5.0 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: c9cdb0889d63d8fa178c90b016e36515e6803ba5c96e980f0233798f63cac29a1e871effa2c17a40e3eaffdb150ad8e98676cc14c97dd8f978f67541c594a05f + checksum: 987f6eafebc347b061bfab7d15f87b48601eecdf7cde9ff0e4e99c44acad3bb126e554996b0c48f1ccf24d5e6785226b1662850bb4e2182927b2ded24f226ae8 languageName: node linkType: hard -"@svgr/core@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/core@npm:6.3.1" +"@svgr/core@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/core@npm:6.5.0" dependencies: - "@svgr/plugin-jsx": ^6.3.1 + "@babel/core": ^7.18.5 + "@svgr/babel-preset": ^6.5.0 + "@svgr/plugin-jsx": ^6.5.0 camelcase: ^6.2.0 cosmiconfig: ^7.0.1 - checksum: 753b043f48d5bfef8aa02976d5ed5a885c60e74e5ac0cdf2b00045e7867443ae722a57160ae46e7a2db563d1ecbe1fda4585a21e84ac1337b6b94113f25b9925 + checksum: 235747a1d1c0e8918aa16da7e44c9dd2024a9ebaadc6bdff00001756d604566437b25e42d61e521f6f38a32c386d44a18dee57a77a92aedef1116f2410b504e6 languageName: node linkType: hard -"@svgr/hast-util-to-babel-ast@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/hast-util-to-babel-ast@npm:6.3.1" +"@svgr/hast-util-to-babel-ast@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/hast-util-to-babel-ast@npm:6.5.0" dependencies: "@babel/types": ^7.18.4 entities: ^4.3.0 - checksum: e54b48a85795e103cfe918fa7102bf4603e6541dd35ee04061fad62edffa5a60d8aa210f709fe81b50c9fb6041f8e62fabf093443266323c649903200aaea604 + checksum: 77dcadb467eded0ce5cba71dbd4cbb4c7ffd7961f351828a4066ab6105d466d55533506bc3bc7db78f938af6692008ceda9fa2ea3dda75fd54e2b736b81ae458 languageName: node linkType: hard -"@svgr/plugin-jsx@npm:6.3.x, @svgr/plugin-jsx@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/plugin-jsx@npm:6.3.1" +"@svgr/plugin-jsx@npm:6.5.x, @svgr/plugin-jsx@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/plugin-jsx@npm:6.5.0" dependencies: "@babel/core": ^7.18.5 - "@svgr/babel-preset": ^6.3.1 - "@svgr/hast-util-to-babel-ast": ^6.3.1 + "@svgr/babel-preset": ^6.5.0 + "@svgr/hast-util-to-babel-ast": ^6.5.0 svg-parser: ^2.0.4 peerDependencies: "@svgr/core": ^6.0.0 - checksum: a2e487dc28d2b69b94b7d96e5cb2593857e559e64c8cb4f818035b8a43ba84e5ddb67f966d15f173b544e807e48a1cda1065da8f9064b94b8d62bbe8cb8c4d73 + checksum: dec7cd47f16cc1b23dd37e333594d596401e7e49825545f4d808c74fe0a3a12a56c1bd55f507794addd6675e1b81dd58346bac4f752daa04016d3edac03e1625 languageName: node linkType: hard -"@svgr/plugin-svgo@npm:6.3.x, @svgr/plugin-svgo@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/plugin-svgo@npm:6.3.1" +"@svgr/plugin-svgo@npm:6.5.x, @svgr/plugin-svgo@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/plugin-svgo@npm:6.5.0" dependencies: cosmiconfig: ^7.0.1 deepmerge: ^4.2.2 svgo: ^2.8.0 peerDependencies: "@svgr/core": ^6.0.0 - checksum: 037d6f91ba7f362764527408661f7fc4a4a296e9dc142a5f2e33fc88dc63dafd305452caae3091e9adb63adf029e0ce20c604d5af787b968f98aad261a834679 + checksum: d1a0ee79283a997aa4af2c2848f502dff71a9fd99623b47b0b2476effd7fe53077456afd1ff54283512a7dd10f30df91736f5d20eb931a462b34f281c90347e0 languageName: node linkType: hard -"@svgr/rollup@npm:6.3.x": - version: 6.3.1 - resolution: "@svgr/rollup@npm:6.3.1" +"@svgr/rollup@npm:6.5.x": + version: 6.5.0 + resolution: "@svgr/rollup@npm:6.5.0" dependencies: "@babel/core": ^7.18.5 "@babel/plugin-transform-react-constant-elements": ^7.17.12 @@ -12481,26 +12483,26 @@ __metadata: "@babel/preset-react": ^7.17.12 "@babel/preset-typescript": ^7.17.12 "@rollup/pluginutils": ^4.2.1 - "@svgr/core": ^6.3.1 - "@svgr/plugin-jsx": ^6.3.1 - "@svgr/plugin-svgo": ^6.3.1 - checksum: 8d96f95a4c89d96a14bc0095469a4ceb39a2ae4ea3517d08573ed8ca11b05416a505723ef09d00c3cee056adc2d95ddcea6c8055a116bbb461ba33d601bfced8 + "@svgr/core": ^6.5.0 + "@svgr/plugin-jsx": ^6.5.0 + "@svgr/plugin-svgo": ^6.5.0 + checksum: a3a043034689a335aa9657580251a8636225002439a4667c31fe2b0faa043980182b28aac7da0219804609ef19c0b3b397626850f056a7065c20f61c2f17b4f8 languageName: node linkType: hard -"@svgr/webpack@npm:6.3.x": - version: 6.3.1 - resolution: "@svgr/webpack@npm:6.3.1" +"@svgr/webpack@npm:6.5.x": + version: 6.5.0 + resolution: "@svgr/webpack@npm:6.5.0" dependencies: "@babel/core": ^7.18.5 "@babel/plugin-transform-react-constant-elements": ^7.17.12 "@babel/preset-env": ^7.18.2 "@babel/preset-react": ^7.17.12 "@babel/preset-typescript": ^7.17.12 - "@svgr/core": ^6.3.1 - "@svgr/plugin-jsx": ^6.3.1 - "@svgr/plugin-svgo": ^6.3.1 - checksum: 36784eacf80601462ede7eab66347423a8635e68aa9f152308c81878b071807adee152a28eed2cce9c72faaf6553dd500f68f00601062ec6821ec0a3a77f4e13 + "@svgr/core": ^6.5.0 + "@svgr/plugin-jsx": ^6.5.0 + "@svgr/plugin-svgo": ^6.5.0 + checksum: 2c0b18b20694b1301e86893e488269882ae136259cc17b4ab47c208c6edcdfeaefa23894b3fe68d36384e26380eafde23571b0cb143aadf538c027d72eb1d702 languageName: node linkType: hard From c1784a49809df21323b4762e464eed5fa7b045ed Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Mon, 17 Oct 2022 21:38:44 +0200 Subject: [PATCH 23/36] chore(integration): use consistent naming of `[gG]ithub` in code Relates to the discussion at PR #14039. Relates-to: PR #14039 Relates-to: PR #14174 Signed-off-by: Patrick Jungermann --- .changeset/gorgeous-queens-pull.md | 14 ++++ .changeset/sixty-pigs-shave.md | 13 ++++ packages/backend-common/api-report.md | 4 +- .../src/reading/GithubUrlReader.test.ts | 16 ++--- .../src/reading/GithubUrlReader.ts | 8 +-- packages/integration/api-report.md | 60 +++++++++++----- .../integration/src/ScmIntegrations.test.ts | 8 +-- packages/integration/src/ScmIntegrations.ts | 8 +-- .../DefaultGithubCredentialsProvider.test.ts | 4 +- ...tion.test.ts => GithubIntegration.test.ts} | 22 +++--- ...HubIntegration.ts => GithubIntegration.ts} | 22 +++--- ...SingleInstanceGithubCredentialsProvider.ts | 6 +- .../integration/src/github/config.test.ts | 34 ++++----- packages/integration/src/github/config.ts | 12 ++-- packages/integration/src/github/core.test.ts | 42 +++++------ packages/integration/src/github/core.ts | 10 +-- packages/integration/src/github/deprecated.ts | 71 +++++++++++++++++++ packages/integration/src/github/index.ts | 12 ++-- packages/integration/src/registry.ts | 4 +- .../api-report.md | 4 +- .../GithubMultiOrgReaderProcessor.ts | 4 +- .../src/providers/GithubEntityProvider.ts | 8 +-- .../providers/GithubOrgEntityProvider.test.ts | 4 +- .../src/providers/GithubOrgEntityProvider.ts | 4 +- .../src/api/CatalogImportClient.ts | 4 +- .../src/api/GitReleaseClient.ts | 4 +- .../src/api/GithubActionsClient.ts | 4 +- .../src/components/Cards/Cards.tsx | 4 +- .../Cards/RecentWorkflowRunsCard.tsx | 4 +- .../WorkflowRunDetails/WorkflowRunDetails.tsx | 4 +- .../WorkflowRunLogs/WorkflowRunLogs.tsx | 4 +- .../WorkflowRunsTable/WorkflowRunsTable.tsx | 4 +- .../github-issues/src/api/gitHubIssuesApi.ts | 4 +- .../src/api/useOctokitGraphQl.ts | 4 +- .../src/ReportIssue/hooks.ts | 4 +- .../reader/transformers/addGitFeedbackLink.ts | 4 +- 36 files changed, 283 insertions(+), 159 deletions(-) create mode 100644 .changeset/gorgeous-queens-pull.md create mode 100644 .changeset/sixty-pigs-shave.md rename packages/integration/src/github/{GitHubIntegration.test.ts => GithubIntegration.test.ts} (86%) rename packages/integration/src/github/{GitHubIntegration.ts => GithubIntegration.ts} (78%) create mode 100644 packages/integration/src/github/deprecated.ts diff --git a/.changeset/gorgeous-queens-pull.md b/.changeset/gorgeous-queens-pull.md new file mode 100644 index 0000000000..bb257e85f0 --- /dev/null +++ b/.changeset/gorgeous-queens-pull.md @@ -0,0 +1,14 @@ +--- +'@backstage/integration': minor +--- + +Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. + +Deprecates: + +- `getGitHubFileFetchUrl` replaced by `getGithubFileFetchUrl` +- `GitHubIntegrationConfig` replaced by `GithubIntegrationConfig` +- `GitHubIntegration` replaced by `GithubIntegration` +- `readGitHubIntegrationConfig` replaced by `readGithubIntegrationConfig` +- `readGitHubIntegrationConfigs` replaced by `readGithubIntegrationConfigs` +- `replaceGitHubUrlType` replaced by `replaceGithubUrlType` diff --git a/.changeset/sixty-pigs-shave.md b/.changeset/sixty-pigs-shave.md new file mode 100644 index 0000000000..e1f4443ad4 --- /dev/null +++ b/.changeset/sixty-pigs-shave.md @@ -0,0 +1,13 @@ +--- +'@backstage/backend-common': patch +'@backstage/plugin-catalog-backend-module-github': patch +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-git-release-manager': patch +'@backstage/plugin-github-actions': patch +'@backstage/plugin-github-issues': patch +'@backstage/plugin-github-pull-requests-board': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-techdocs-module-addons-contrib': patch +--- + +Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 600e3edef8..7e42157d8d 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -22,7 +22,7 @@ import { ErrorRequestHandler } from 'express'; import express from 'express'; import { GerritIntegration } from '@backstage/integration'; import { GithubCredentialsProvider } from '@backstage/integration'; -import { GitHubIntegration } from '@backstage/integration'; +import { GithubIntegration } from '@backstage/integration'; import { GitLabIntegration } from '@backstage/integration'; import { isChildPath } from '@backstage/cli-common'; import { JsonValue } from '@backstage/types'; @@ -409,7 +409,7 @@ export class Git { // @public export class GithubUrlReader implements UrlReader { constructor( - integration: GitHubIntegration, + integration: GithubIntegration, deps: { treeResponseFactory: ReadTreeResponseFactory; credentialsProvider: GithubCredentialsProvider; diff --git a/packages/backend-common/src/reading/GithubUrlReader.test.ts b/packages/backend-common/src/reading/GithubUrlReader.test.ts index fddb728648..a5f9d26de5 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.test.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.test.ts @@ -17,8 +17,8 @@ import { ConfigReader } from '@backstage/config'; import { GithubCredentialsProvider, - GitHubIntegration, - readGitHubIntegrationConfig, + GithubIntegration, + readGithubIntegrationConfig, } from '@backstage/integration'; import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; import fs from 'fs-extra'; @@ -46,8 +46,8 @@ const mockCredentialsProvider = { } as unknown as GithubCredentialsProvider; const githubProcessor = new GithubUrlReader( - new GitHubIntegration( - readGitHubIntegrationConfig( + new GithubIntegration( + readGithubIntegrationConfig( new ConfigReader({ host: 'github.com', apiBaseUrl: 'https://api.github.com', @@ -58,8 +58,8 @@ const githubProcessor = new GithubUrlReader( ); const gheProcessor = new GithubUrlReader( - new GitHubIntegration( - readGitHubIntegrationConfig( + new GithubIntegration( + readGithubIntegrationConfig( new ConfigReader({ host: 'ghe.github.com', apiBaseUrl: 'https://ghe.github.com/api/v3', @@ -539,8 +539,8 @@ describe('GithubUrlReader', () => { expect(() => { /* eslint-disable no-new */ new GithubUrlReader( - new GitHubIntegration( - readGitHubIntegrationConfig( + new GithubIntegration( + readGithubIntegrationConfig( new ConfigReader({ host: 'ghe.mycompany.net', }), diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index 3806253a6f..7abf1b2518 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -15,10 +15,10 @@ */ import { - getGitHubFileFetchUrl, + getGithubFileFetchUrl, DefaultGithubCredentialsProvider, GithubCredentialsProvider, - GitHubIntegration, + GithubIntegration, ScmIntegrations, } from '@backstage/integration'; import { RestEndpointMethodTypes } from '@octokit/rest'; @@ -72,7 +72,7 @@ export class GithubUrlReader implements UrlReader { }; constructor( - private readonly integration: GitHubIntegration, + private readonly integration: GithubIntegration, private readonly deps: { treeResponseFactory: ReadTreeResponseFactory; credentialsProvider: GithubCredentialsProvider; @@ -97,7 +97,7 @@ export class GithubUrlReader implements UrlReader { const credentials = await this.deps.credentialsProvider.getCredentials({ url, }); - const ghUrl = getGitHubFileFetchUrl( + const ghUrl = getGithubFileFetchUrl( url, this.integration.config, credentials, diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index cf52bec65b..0f24ded066 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -323,16 +323,19 @@ export function getGerritRequestOptions(config: GerritIntegrationConfig): { headers?: Record; }; +// @public @deprecated (undocumented) +export const getGitHubFileFetchUrl: typeof getGithubFileFetchUrl; + // @public -export function getGitHubFileFetchUrl( +export function getGithubFileFetchUrl( url: string, - config: GitHubIntegrationConfig, + config: GithubIntegrationConfig, credentials: GithubCredentials, ): string; // @public @deprecated export function getGitHubRequestOptions( - config: GitHubIntegrationConfig, + config: GithubIntegrationConfig, credentials: GithubCredentials, ): { headers: Record; @@ -366,7 +369,7 @@ export type GithubAppConfig = { // @public export class GithubAppCredentialsMux { - constructor(config: GitHubIntegrationConfig); + constructor(config: GithubIntegrationConfig); // (undocumented) getAllInstallations(): Promise< RestEndpointMethodTypes['apps']['listInstallations']['response']['data'] @@ -393,13 +396,22 @@ export interface GithubCredentialsProvider { // @public export type GithubCredentialType = 'app' | 'token'; -// @public -export class GitHubIntegration implements ScmIntegration { +// @public @deprecated (undocumented) +export class GitHubIntegration extends GithubIntegration { constructor(integrationConfig: GitHubIntegrationConfig); // (undocumented) get config(): GitHubIntegrationConfig; // (undocumented) static factory: ScmIntegrationsFactory; +} + +// @public +export class GithubIntegration implements ScmIntegration { + constructor(integrationConfig: GithubIntegrationConfig); + // (undocumented) + get config(): GithubIntegrationConfig; + // (undocumented) + static factory: ScmIntegrationsFactory; // (undocumented) resolveEditUrl(url: string): string; // (undocumented) @@ -414,8 +426,11 @@ export class GitHubIntegration implements ScmIntegration { get type(): string; } +// @public @deprecated (undocumented) +export type GitHubIntegrationConfig = GithubIntegrationConfig; + // @public -export type GitHubIntegrationConfig = { +export type GithubIntegrationConfig = { host: string; apiBaseUrl?: string; rawBaseUrl?: string; @@ -473,7 +488,7 @@ export interface IntegrationsByType { // (undocumented) gerrit: ScmIntegrationsGroup; // (undocumented) - github: ScmIntegrationsGroup; + github: ScmIntegrationsGroup; // (undocumented) gitlab: ScmIntegrationsGroup; } @@ -551,15 +566,21 @@ export function readGerritIntegrationConfigs( configs: Config[], ): GerritIntegrationConfig[]; -// @public -export function readGitHubIntegrationConfig( - config: Config, -): GitHubIntegrationConfig; +// @public @deprecated (undocumented) +export const readGitHubIntegrationConfig: typeof readGithubIntegrationConfig; // @public -export function readGitHubIntegrationConfigs( +export function readGithubIntegrationConfig( + config: Config, +): GithubIntegrationConfig; + +// @public @deprecated (undocumented) +export const readGitHubIntegrationConfigs: typeof readGithubIntegrationConfigs; + +// @public +export function readGithubIntegrationConfigs( configs: Config[], -): GitHubIntegrationConfig[]; +): GithubIntegrationConfig[]; // @public export function readGitLabIntegrationConfig( @@ -576,8 +597,11 @@ export function readGoogleGcsIntegrationConfig( config: Config, ): GoogleGcsIntegrationConfig; +// @public @deprecated (undocumented) +export const replaceGitHubUrlType: typeof replaceGithubUrlType; + // @public -export function replaceGitHubUrlType( +export function replaceGithubUrlType( url: string, type: 'blob' | 'tree' | 'edit', ): string; @@ -616,7 +640,7 @@ export interface ScmIntegrationRegistry // (undocumented) gerrit: ScmIntegrationsGroup; // (undocumented) - github: ScmIntegrationsGroup; + github: ScmIntegrationsGroup; // (undocumented) gitlab: ScmIntegrationsGroup; resolveEditUrl(url: string): string; @@ -649,7 +673,7 @@ export class ScmIntegrations implements ScmIntegrationRegistry { // (undocumented) get gerrit(): ScmIntegrationsGroup; // (undocumented) - get github(): ScmIntegrationsGroup; + get github(): ScmIntegrationsGroup; // (undocumented) get gitlab(): ScmIntegrationsGroup; // (undocumented) @@ -681,7 +705,7 @@ export class SingleInstanceGithubCredentialsProvider implements GithubCredentialsProvider { // (undocumented) - static create: (config: GitHubIntegrationConfig) => GithubCredentialsProvider; + static create: (config: GithubIntegrationConfig) => GithubCredentialsProvider; getCredentials(opts: { url: string }): Promise; } ``` diff --git a/packages/integration/src/ScmIntegrations.test.ts b/packages/integration/src/ScmIntegrations.test.ts index d9fcaf36f0..f5fd480609 100644 --- a/packages/integration/src/ScmIntegrations.test.ts +++ b/packages/integration/src/ScmIntegrations.test.ts @@ -29,8 +29,8 @@ import { } from './bitbucketServer'; import { GerritIntegrationConfig } from './gerrit'; import { GerritIntegration } from './gerrit/GerritIntegration'; -import { GitHubIntegrationConfig } from './github'; -import { GitHubIntegration } from './github/GitHubIntegration'; +import { GithubIntegrationConfig } from './github'; +import { GithubIntegration } from './github/GithubIntegration'; import { GitLabIntegrationConfig } from './gitlab'; import { GitLabIntegration } from './gitlab/GitLabIntegration'; import { basicIntegrations } from './helpers'; @@ -61,9 +61,9 @@ describe('ScmIntegrations', () => { host: 'gerrit.local', } as GerritIntegrationConfig); - const github = new GitHubIntegration({ + const github = new GithubIntegration({ host: 'github.local', - } as GitHubIntegrationConfig); + } as GithubIntegrationConfig); const gitlab = new GitLabIntegration({ host: 'gitlab.local', diff --git a/packages/integration/src/ScmIntegrations.ts b/packages/integration/src/ScmIntegrations.ts index 6d745dcf1c..f5decebc66 100644 --- a/packages/integration/src/ScmIntegrations.ts +++ b/packages/integration/src/ScmIntegrations.ts @@ -21,7 +21,7 @@ import { BitbucketCloudIntegration } from './bitbucketCloud/BitbucketCloudIntegr import { BitbucketIntegration } from './bitbucket/BitbucketIntegration'; import { BitbucketServerIntegration } from './bitbucketServer/BitbucketServerIntegration'; import { GerritIntegration } from './gerrit/GerritIntegration'; -import { GitHubIntegration } from './github/GitHubIntegration'; +import { GithubIntegration } from './github/GithubIntegration'; import { GitLabIntegration } from './gitlab/GitLabIntegration'; import { defaultScmResolveUrl } from './helpers'; import { ScmIntegration, ScmIntegrationsGroup } from './types'; @@ -42,7 +42,7 @@ export interface IntegrationsByType { bitbucketCloud: ScmIntegrationsGroup; bitbucketServer: ScmIntegrationsGroup; gerrit: ScmIntegrationsGroup; - github: ScmIntegrationsGroup; + github: ScmIntegrationsGroup; gitlab: ScmIntegrationsGroup; } @@ -62,7 +62,7 @@ export class ScmIntegrations implements ScmIntegrationRegistry { bitbucketCloud: BitbucketCloudIntegration.factory({ config }), bitbucketServer: BitbucketServerIntegration.factory({ config }), gerrit: GerritIntegration.factory({ config }), - github: GitHubIntegration.factory({ config }), + github: GithubIntegration.factory({ config }), gitlab: GitLabIntegration.factory({ config }), }); } @@ -98,7 +98,7 @@ export class ScmIntegrations implements ScmIntegrationRegistry { return this.byType.gerrit; } - get github(): ScmIntegrationsGroup { + get github(): ScmIntegrationsGroup { return this.byType.github; } diff --git a/packages/integration/src/github/DefaultGithubCredentialsProvider.test.ts b/packages/integration/src/github/DefaultGithubCredentialsProvider.test.ts index 7c2b53e822..98b8edecef 100644 --- a/packages/integration/src/github/DefaultGithubCredentialsProvider.test.ts +++ b/packages/integration/src/github/DefaultGithubCredentialsProvider.test.ts @@ -15,7 +15,7 @@ */ import { ScmIntegrations } from '../ScmIntegrations'; -import { GitHubIntegrationConfig } from './config'; +import { GithubIntegrationConfig } from './config'; import { SingleInstanceGithubCredentialsProvider } from './SingleInstanceGithubCredentialsProvider'; import { DefaultGithubCredentialsProvider } from './DefaultGithubCredentialsProvider'; @@ -65,7 +65,7 @@ describe('DefaultGithubCredentialsProvider tests', () => { ); jest.resetAllMocks(); SingleInstanceGithubCredentialsProvider.create = ( - config: GitHubIntegrationConfig, + config: GithubIntegrationConfig, ) => { return { getCredentials: (_opts: { url: string }) => { diff --git a/packages/integration/src/github/GitHubIntegration.test.ts b/packages/integration/src/github/GithubIntegration.test.ts similarity index 86% rename from packages/integration/src/github/GitHubIntegration.test.ts rename to packages/integration/src/github/GithubIntegration.test.ts index ccceb34d88..d8e6020a64 100644 --- a/packages/integration/src/github/GitHubIntegration.test.ts +++ b/packages/integration/src/github/GithubIntegration.test.ts @@ -15,11 +15,11 @@ */ import { ConfigReader } from '@backstage/config'; -import { GitHubIntegration, replaceGitHubUrlType } from './GitHubIntegration'; +import { GithubIntegration, replaceGithubUrlType } from './GithubIntegration'; -describe('GitHubIntegration', () => { +describe('GithubIntegration', () => { it('has a working factory', () => { - const integrations = GitHubIntegration.factory({ + const integrations = GithubIntegration.factory({ config: new ConfigReader({ integrations: { github: [ @@ -39,7 +39,7 @@ describe('GitHubIntegration', () => { }); it('returns the basics', () => { - const integration = new GitHubIntegration({ + const integration = new GithubIntegration({ host: 'h.com', apiBaseUrl: 'a', rawBaseUrl: 'r', @@ -51,7 +51,7 @@ describe('GitHubIntegration', () => { }); it('resolveUrl', () => { - const integration = new GitHubIntegration({ host: 'h.com' }); + const integration = new GithubIntegration({ host: 'h.com' }); expect( integration.resolveUrl({ @@ -70,7 +70,7 @@ describe('GitHubIntegration', () => { }); it('resolve edit URL', () => { - const integration = new GitHubIntegration({ host: 'h.com' }); + const integration = new GithubIntegration({ host: 'h.com' }); expect( integration.resolveEditUrl( @@ -80,28 +80,28 @@ describe('GitHubIntegration', () => { }); }); -describe('replaceGitHubUrlType', () => { +describe('replaceGithubUrlType', () => { it('should replace with expected type', () => { expect( - replaceGitHubUrlType( + replaceGithubUrlType( 'https://github.com/backstage/backstage/blob/master/README.md', 'edit', ), ).toBe('https://github.com/backstage/backstage/edit/master/README.md'); expect( - replaceGitHubUrlType( + replaceGithubUrlType( 'https://github.com/webmodules/blob/blob/master/test', 'tree', ), ).toBe('https://github.com/webmodules/blob/tree/master/test'); expect( - replaceGitHubUrlType( + replaceGithubUrlType( 'https://github.com/blob/blob/blob/master/test', 'tree', ), ).toBe('https://github.com/blob/blob/tree/master/test'); expect( - replaceGitHubUrlType( + replaceGithubUrlType( 'https://github.com/backstage/backstage/edit/tree/README.md', 'blob', ), diff --git a/packages/integration/src/github/GitHubIntegration.ts b/packages/integration/src/github/GithubIntegration.ts similarity index 78% rename from packages/integration/src/github/GitHubIntegration.ts rename to packages/integration/src/github/GithubIntegration.ts index 1db0b1fae5..50fc129ccf 100644 --- a/packages/integration/src/github/GitHubIntegration.ts +++ b/packages/integration/src/github/GithubIntegration.ts @@ -17,8 +17,8 @@ import { basicIntegrations, defaultScmResolveUrl } from '../helpers'; import { ScmIntegration, ScmIntegrationsFactory } from '../types'; import { - GitHubIntegrationConfig, - readGitHubIntegrationConfigs, + GithubIntegrationConfig, + readGithubIntegrationConfigs, } from './config'; /** @@ -26,18 +26,18 @@ import { * * @public */ -export class GitHubIntegration implements ScmIntegration { - static factory: ScmIntegrationsFactory = ({ config }) => { - const configs = readGitHubIntegrationConfigs( +export class GithubIntegration implements ScmIntegration { + static factory: ScmIntegrationsFactory = ({ config }) => { + const configs = readGithubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], ); return basicIntegrations( - configs.map(c => new GitHubIntegration(c)), + configs.map(c => new GithubIntegration(c)), i => i.config.host, ); }; - constructor(private readonly integrationConfig: GitHubIntegrationConfig) {} + constructor(private readonly integrationConfig: GithubIntegrationConfig) {} get type(): string { return 'github'; @@ -47,7 +47,7 @@ export class GitHubIntegration implements ScmIntegration { return this.integrationConfig.host; } - get config(): GitHubIntegrationConfig { + get config(): GithubIntegrationConfig { return this.integrationConfig; } @@ -59,11 +59,11 @@ export class GitHubIntegration implements ScmIntegration { // GitHub uses blob URLs for files and tree urls for directory listings. But // there is a redirect from tree to blob for files, so we can always return // tree urls here. - return replaceGitHubUrlType(defaultScmResolveUrl(options), 'tree'); + return replaceGithubUrlType(defaultScmResolveUrl(options), 'tree'); } resolveEditUrl(url: string): string { - return replaceGitHubUrlType(url, 'edit'); + return replaceGithubUrlType(url, 'edit'); } } @@ -74,7 +74,7 @@ export class GitHubIntegration implements ScmIntegration { * @param type - The desired type, e.g. "blob" * @public */ -export function replaceGitHubUrlType( +export function replaceGithubUrlType( url: string, type: 'blob' | 'tree' | 'edit', ): string { diff --git a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts index 02f050f69f..d42ad4597d 100644 --- a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts +++ b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts @@ -15,7 +15,7 @@ */ import parseGitUrl from 'git-url-parse'; -import { GithubAppConfig, GitHubIntegrationConfig } from './config'; +import { GithubAppConfig, GithubIntegrationConfig } from './config'; import { createAppAuth } from '@octokit/auth-app'; import { Octokit, RestEndpointMethodTypes } from '@octokit/rest'; import { DateTime } from 'luxon'; @@ -199,7 +199,7 @@ class GithubAppManager { export class GithubAppCredentialsMux { private readonly apps: GithubAppManager[]; - constructor(config: GitHubIntegrationConfig) { + constructor(config: GithubIntegrationConfig) { this.apps = config.apps?.map(ac => new GithubAppManager(ac, config.apiBaseUrl)) ?? []; } @@ -259,7 +259,7 @@ export class SingleInstanceGithubCredentialsProvider implements GithubCredentialsProvider { static create: ( - config: GitHubIntegrationConfig, + config: GithubIntegrationConfig, ) => GithubCredentialsProvider = config => { return new SingleInstanceGithubCredentialsProvider( new GithubAppCredentialsMux(config), diff --git a/packages/integration/src/github/config.test.ts b/packages/integration/src/github/config.test.ts index 73efb4f81a..c4d9eb1900 100644 --- a/packages/integration/src/github/config.test.ts +++ b/packages/integration/src/github/config.test.ts @@ -17,18 +17,18 @@ import { Config, ConfigReader } from '@backstage/config'; import { loadConfigSchema } from '@backstage/config-loader'; import { - GitHubIntegrationConfig, - readGitHubIntegrationConfig, - readGitHubIntegrationConfigs, + GithubIntegrationConfig, + readGithubIntegrationConfig, + readGithubIntegrationConfigs, } from './config'; -describe('readGitHubIntegrationConfig', () => { - function buildConfig(provider: Partial) { +describe('readGithubIntegrationConfig', () => { + function buildConfig(provider: Partial) { return new ConfigReader(provider); } async function buildFrontendConfig( - data: Partial, + data: Partial, ): Promise { const fullSchema = await loadConfigSchema({ dependencies: ['@backstage/integration'], @@ -52,7 +52,7 @@ describe('readGitHubIntegrationConfig', () => { } it('reads all values', () => { - const output = readGitHubIntegrationConfig( + const output = readGithubIntegrationConfig( buildConfig({ host: 'a.com', apiBaseUrl: 'https://a.com/api', @@ -69,7 +69,7 @@ describe('readGitHubIntegrationConfig', () => { }); it('injects the correct GitHub API base URL when missing', () => { - const output = readGitHubIntegrationConfig( + const output = readGithubIntegrationConfig( buildConfig({ host: 'github.com' }), ); expect(output).toEqual({ @@ -87,22 +87,22 @@ describe('readGitHubIntegrationConfig', () => { token: 't', }; expect(() => - readGitHubIntegrationConfig(buildConfig({ ...valid, host: 7 })), + readGithubIntegrationConfig(buildConfig({ ...valid, host: 7 })), ).toThrow(/host/); expect(() => - readGitHubIntegrationConfig(buildConfig({ ...valid, apiBaseUrl: 7 })), + readGithubIntegrationConfig(buildConfig({ ...valid, apiBaseUrl: 7 })), ).toThrow(/apiBaseUrl/); expect(() => - readGitHubIntegrationConfig(buildConfig({ ...valid, rawBaseUrl: 7 })), + readGithubIntegrationConfig(buildConfig({ ...valid, rawBaseUrl: 7 })), ).toThrow(/rawBaseUrl/); expect(() => - readGitHubIntegrationConfig(buildConfig({ ...valid, token: 7 })), + readGithubIntegrationConfig(buildConfig({ ...valid, token: 7 })), ).toThrow(/token/); }); it('works on the frontend', async () => { expect( - readGitHubIntegrationConfig( + readGithubIntegrationConfig( await buildFrontendConfig({ host: 'a.com', apiBaseUrl: 'https://a.com/api', @@ -118,15 +118,15 @@ describe('readGitHubIntegrationConfig', () => { }); }); -describe('readGitHubIntegrationConfigs', () => { +describe('readGithubIntegrationConfigs', () => { function buildConfig( - providers: Partial[], + providers: Partial[], ): Config[] { return providers.map(provider => new ConfigReader(provider)); } it('reads all values', () => { - const output = readGitHubIntegrationConfigs( + const output = readGithubIntegrationConfigs( buildConfig([ { host: 'a.com', @@ -145,7 +145,7 @@ describe('readGitHubIntegrationConfigs', () => { }); it('adds a default GitHub entry when missing', () => { - const output = readGitHubIntegrationConfigs(buildConfig([])); + const output = readGithubIntegrationConfigs(buildConfig([])); expect(output).toEqual([ { host: 'github.com', diff --git a/packages/integration/src/github/config.ts b/packages/integration/src/github/config.ts index 93801e96fb..cd76eba3d3 100644 --- a/packages/integration/src/github/config.ts +++ b/packages/integration/src/github/config.ts @@ -27,7 +27,7 @@ const GITHUB_RAW_BASE_URL = 'https://raw.githubusercontent.com'; * * @public */ -export type GitHubIntegrationConfig = { +export type GithubIntegrationConfig = { /** * The host of the target that this matches on, e.g. "github.com" */ @@ -117,9 +117,9 @@ export type GithubAppConfig = { * @param config - The config object of a single integration * @public */ -export function readGitHubIntegrationConfig( +export function readGithubIntegrationConfig( config: Config, -): GitHubIntegrationConfig { +): GithubIntegrationConfig { const host = config.getOptionalString('host') ?? GITHUB_HOST; let apiBaseUrl = config.getOptionalString('apiBaseUrl'); let rawBaseUrl = config.getOptionalString('rawBaseUrl'); @@ -163,11 +163,11 @@ export function readGitHubIntegrationConfig( * @param configs - All of the integration config objects * @public */ -export function readGitHubIntegrationConfigs( +export function readGithubIntegrationConfigs( configs: Config[], -): GitHubIntegrationConfig[] { +): GithubIntegrationConfig[] { // First read all the explicit integrations - const result = configs.map(readGitHubIntegrationConfig); + const result = configs.map(readGithubIntegrationConfig); // If no explicit github.com integration was added, put one in the list as // a convenience diff --git a/packages/integration/src/github/core.test.ts b/packages/integration/src/github/core.test.ts index c5d04f5b4f..982a096b43 100644 --- a/packages/integration/src/github/core.test.ts +++ b/packages/integration/src/github/core.test.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { GitHubIntegrationConfig } from './config'; -import { getGitHubFileFetchUrl, getGitHubRequestOptions } from './core'; +import { GithubIntegrationConfig } from './config'; +import { getGithubFileFetchUrl, getGitHubRequestOptions } from './core'; import { GithubCredentials } from './types'; describe('github core', () => { @@ -37,12 +37,12 @@ describe('github core', () => { describe('getGitHubRequestOptions', () => { it('inserts a token when needed', () => { - const withToken: GitHubIntegrationConfig = { + const withToken: GithubIntegrationConfig = { host: '', rawBaseUrl: '', token: 'A', }; - const withoutToken: GitHubIntegrationConfig = { + const withoutToken: GithubIntegrationConfig = { host: '', rawBaseUrl: '', }; @@ -57,21 +57,21 @@ describe('github core', () => { }); }); - describe('getGitHubFileFetchUrl', () => { + describe('getGithubFileFetchUrl', () => { it('rejects targets that do not look like URLs', () => { - const config: GitHubIntegrationConfig = { host: '', apiBaseUrl: '' }; - expect(() => getGitHubFileFetchUrl('a/b', config, noCredentials)).toThrow( + const config: GithubIntegrationConfig = { host: '', apiBaseUrl: '' }; + expect(() => getGithubFileFetchUrl('a/b', config, noCredentials)).toThrow( /Incorrect URL: a\/b/, ); }); it('happy path for github api', () => { - const config: GitHubIntegrationConfig = { + const config: GithubIntegrationConfig = { host: 'github.com', apiBaseUrl: 'https://api.github.com', }; expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://github.com/a/b/blob/branchname/path/to/c.yaml', config, appCredentials, @@ -80,7 +80,7 @@ describe('github core', () => { 'https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=branchname', ); expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://github.com/a/b/blob/branchname/path/to/c.yaml', config, tokenCredentials, @@ -91,12 +91,12 @@ describe('github core', () => { }); it('happy path for ghe api', () => { - const config: GitHubIntegrationConfig = { + const config: GithubIntegrationConfig = { host: 'ghe.mycompany.net', apiBaseUrl: 'https://ghe.mycompany.net/api/v3', }; expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://ghe.mycompany.net/a/b/blob/branchname/path/to/c.yaml', config, appCredentials, @@ -105,7 +105,7 @@ describe('github core', () => { 'https://ghe.mycompany.net/api/v3/repos/a/b/contents/path/to/c.yaml?ref=branchname', ); expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://ghe.mycompany.net/a/b/blob/branchname/path/to/c.yaml', config, tokenCredentials, @@ -116,12 +116,12 @@ describe('github core', () => { }); it('happy path for github tree', () => { - const config: GitHubIntegrationConfig = { + const config: GithubIntegrationConfig = { host: 'github.com', apiBaseUrl: 'https://api.github.com', }; expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://github.com/a/b/tree/branchname/path/to/c.yaml', config, tokenCredentials, @@ -132,12 +132,12 @@ describe('github core', () => { }); it('happy path for ghe tree', () => { - const config: GitHubIntegrationConfig = { + const config: GithubIntegrationConfig = { host: 'ghe.mycompany.net', apiBaseUrl: 'https://ghe.mycompany.net/api/v3', }; expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://ghe.mycompany.net/a/b/tree/branchname/path/to/c.yaml', config, tokenCredentials, @@ -148,12 +148,12 @@ describe('github core', () => { }); it('happy path for github raw', () => { - const config: GitHubIntegrationConfig = { + const config: GithubIntegrationConfig = { host: 'github.com', rawBaseUrl: 'https://raw.githubusercontent.com', }; expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://github.com/a/b/blob/branchname/path/to/c.yaml', config, tokenCredentials, @@ -164,12 +164,12 @@ describe('github core', () => { }); it('happy path for ghe raw', () => { - const config: GitHubIntegrationConfig = { + const config: GithubIntegrationConfig = { host: 'ghe.mycompany.net', rawBaseUrl: 'https://ghe.mycompany.net/raw', }; expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://ghe.mycompany.net/a/b/blob/branchname/path/to/c.yaml', config, tokenCredentials, diff --git a/packages/integration/src/github/core.ts b/packages/integration/src/github/core.ts index 5b630d5182..a755f3383c 100644 --- a/packages/integration/src/github/core.ts +++ b/packages/integration/src/github/core.ts @@ -15,7 +15,7 @@ */ import parseGitUrl from 'git-url-parse'; -import { GitHubIntegrationConfig } from './config'; +import { GithubIntegrationConfig } from './config'; import { GithubCredentials } from './types'; /** @@ -33,9 +33,9 @@ import { GithubCredentials } from './types'; * @param config - The relevant provider config * @public */ -export function getGitHubFileFetchUrl( +export function getGithubFileFetchUrl( url: string, - config: GitHubIntegrationConfig, + config: GithubIntegrationConfig, credentials: GithubCredentials, ): string { try { @@ -71,7 +71,7 @@ export function getGitHubFileFetchUrl( * @public */ export function getGitHubRequestOptions( - config: GitHubIntegrationConfig, + config: GithubIntegrationConfig, credentials: GithubCredentials, ): { headers: Record } { const headers: Record = {}; @@ -88,7 +88,7 @@ export function getGitHubRequestOptions( } export function chooseEndpoint( - config: GitHubIntegrationConfig, + config: GithubIntegrationConfig, credentials: GithubCredentials, ): 'api' | 'raw' { if (config.apiBaseUrl && (credentials.token || !config.rawBaseUrl)) { diff --git a/packages/integration/src/github/deprecated.ts b/packages/integration/src/github/deprecated.ts new file mode 100644 index 0000000000..127040c1a7 --- /dev/null +++ b/packages/integration/src/github/deprecated.ts @@ -0,0 +1,71 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + GithubIntegrationConfig, + readGithubIntegrationConfig, + readGithubIntegrationConfigs, +} from './config'; +import { getGithubFileFetchUrl } from './core'; +import { GithubIntegration, replaceGithubUrlType } from './GithubIntegration'; +import { ScmIntegrationsFactory } from '../types'; + +/** + * @public + * @deprecated Use {@link getGithubFileFetchUrl} instead. + */ +export const getGitHubFileFetchUrl = getGithubFileFetchUrl; + +/** + * @public + * @deprecated Use {@link GithubIntegrationConfig} instead. + */ +export type GitHubIntegrationConfig = GithubIntegrationConfig; + +/** + * @public + * @deprecated Use {@link GithubIntegration} instead. + */ +export class GitHubIntegration extends GithubIntegration { + static factory: ScmIntegrationsFactory = + GithubIntegration.factory; + + constructor(integrationConfig: GitHubIntegrationConfig) { + super(integrationConfig as GithubIntegrationConfig); + } + + get config(): GitHubIntegrationConfig { + return super.config as GitHubIntegrationConfig; + } +} + +/** + * @public + * @deprecated Use {@link readGithubIntegrationConfig} instead. + */ +export const readGitHubIntegrationConfig = readGithubIntegrationConfig; + +/** + * @public + * @deprecated Use {@link readGithubIntegrationConfigs} instead. + */ +export const readGitHubIntegrationConfigs = readGithubIntegrationConfigs; + +/** + * @public + * @deprecated Use {@link replaceGithubUrlType} instead. + */ +export const replaceGitHubUrlType = replaceGithubUrlType; diff --git a/packages/integration/src/github/index.ts b/packages/integration/src/github/index.ts index c6a7799c3d..816b0d2ff9 100644 --- a/packages/integration/src/github/index.ts +++ b/packages/integration/src/github/index.ts @@ -15,11 +15,11 @@ */ export { - readGitHubIntegrationConfig, - readGitHubIntegrationConfigs, + readGithubIntegrationConfig, + readGithubIntegrationConfigs, } from './config'; -export type { GithubAppConfig, GitHubIntegrationConfig } from './config'; -export { getGitHubFileFetchUrl, getGitHubRequestOptions } from './core'; +export type { GithubAppConfig, GithubIntegrationConfig } from './config'; +export { getGithubFileFetchUrl, getGitHubRequestOptions } from './core'; export { DefaultGithubCredentialsProvider } from './DefaultGithubCredentialsProvider'; export { GithubAppCredentialsMux, @@ -30,4 +30,6 @@ export type { GithubCredentialsProvider, GithubCredentialType, } from './types'; -export { GitHubIntegration, replaceGitHubUrlType } from './GitHubIntegration'; +export { GithubIntegration, replaceGithubUrlType } from './GithubIntegration'; + +export * from './deprecated'; diff --git a/packages/integration/src/registry.ts b/packages/integration/src/registry.ts index f4f759cfe9..debb819156 100644 --- a/packages/integration/src/registry.ts +++ b/packages/integration/src/registry.ts @@ -21,7 +21,7 @@ import { BitbucketCloudIntegration } from './bitbucketCloud/BitbucketCloudIntegr import { BitbucketIntegration } from './bitbucket/BitbucketIntegration'; import { BitbucketServerIntegration } from './bitbucketServer/BitbucketServerIntegration'; import { GerritIntegration } from './gerrit/GerritIntegration'; -import { GitHubIntegration } from './github/GitHubIntegration'; +import { GithubIntegration } from './github/GithubIntegration'; import { GitLabIntegration } from './gitlab/GitLabIntegration'; /** @@ -40,7 +40,7 @@ export interface ScmIntegrationRegistry bitbucketCloud: ScmIntegrationsGroup; bitbucketServer: ScmIntegrationsGroup; gerrit: ScmIntegrationsGroup; - github: ScmIntegrationsGroup; + github: ScmIntegrationsGroup; gitlab: ScmIntegrationsGroup; /** diff --git a/plugins/catalog-backend-module-github/api-report.md b/plugins/catalog-backend-module-github/api-report.md index 5b94f7169e..195b150dea 100644 --- a/plugins/catalog-backend-module-github/api-report.md +++ b/plugins/catalog-backend-module-github/api-report.md @@ -12,7 +12,7 @@ import { Entity } from '@backstage/catalog-model'; import { EntityProvider } from '@backstage/plugin-catalog-backend'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { GithubCredentialsProvider } from '@backstage/integration'; -import { GitHubIntegrationConfig } from '@backstage/integration'; +import { GithubIntegrationConfig } from '@backstage/integration'; import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; @@ -160,7 +160,7 @@ export class GithubOrgEntityProvider implements EntityProvider { constructor(options: { id: string; orgUrl: string; - gitHubConfig: GitHubIntegrationConfig; + gitHubConfig: GithubIntegrationConfig; logger: Logger; githubCredentialsProvider?: GithubCredentialsProvider; }); diff --git a/plugins/catalog-backend-module-github/src/processors/GithubMultiOrgReaderProcessor.ts b/plugins/catalog-backend-module-github/src/processors/GithubMultiOrgReaderProcessor.ts index c5315ad166..7bb73990b3 100644 --- a/plugins/catalog-backend-module-github/src/processors/GithubMultiOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-github/src/processors/GithubMultiOrgReaderProcessor.ts @@ -19,7 +19,7 @@ import { DefaultGithubCredentialsProvider, GithubAppCredentialsMux, GithubCredentialsProvider, - GitHubIntegrationConfig, + GithubIntegrationConfig, ScmIntegrationRegistry, ScmIntegrations, } from '@backstage/integration'; @@ -182,7 +182,7 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor { // Note: Does not support usage of PATs private async getAllOrgs( - gitHubConfig: GitHubIntegrationConfig, + gitHubConfig: GithubIntegrationConfig, ): Promise { const githubAppMux = new GithubAppCredentialsMux(gitHubConfig); const installs = await githubAppMux.getAllInstallations(); diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts index 2004cb7469..378e1a96a7 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts @@ -19,8 +19,8 @@ import { Config } from '@backstage/config'; import { GithubCredentialsProvider, ScmIntegrations, - GitHubIntegrationConfig, - GitHubIntegration, + GithubIntegrationConfig, + GithubIntegration, SingleInstanceGithubCredentialsProvider, } from '@backstage/integration'; import { @@ -51,7 +51,7 @@ import { satisfiesTopicFilter } from '../lib/util'; export class GithubEntityProvider implements EntityProvider { private readonly config: GithubEntityProviderConfig; private readonly logger: Logger; - private readonly integration: GitHubIntegrationConfig; + private readonly integration: GithubIntegrationConfig; private readonly scheduleFn: () => Promise; private connection?: EntityProviderConnection; private readonly githubCredentialsProvider: GithubCredentialsProvider; @@ -101,7 +101,7 @@ export class GithubEntityProvider implements EntityProvider { private constructor( config: GithubEntityProviderConfig, - integration: GitHubIntegration, + integration: GithubIntegration, logger: Logger, taskRunner: TaskRunner, ) { diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts index 1d8d125d91..b3fc5022ec 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts @@ -18,7 +18,7 @@ import { getVoidLogger } from '@backstage/backend-common'; import { GroupEntity, UserEntity } from '@backstage/catalog-model'; import { GithubCredentialsProvider, - GitHubIntegrationConfig, + GithubIntegrationConfig, } from '@backstage/integration'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { graphql } from '@octokit/graphql'; @@ -87,7 +87,7 @@ describe('GithubOrgEntityProvider', () => { }; const logger = getVoidLogger(); - const gitHubConfig: GitHubIntegrationConfig = { + const gitHubConfig: GithubIntegrationConfig = { host: 'https://github.com', }; diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts index f8fafbe53c..2fc8ce7300 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts @@ -24,7 +24,7 @@ import { Config } from '@backstage/config'; import { DefaultGithubCredentialsProvider, GithubCredentialsProvider, - GitHubIntegrationConfig, + GithubIntegrationConfig, ScmIntegrations, SingleInstanceGithubCredentialsProvider, } from '@backstage/integration'; @@ -134,7 +134,7 @@ export class GithubOrgEntityProvider implements EntityProvider { private options: { id: string; orgUrl: string; - gitHubConfig: GitHubIntegrationConfig; + gitHubConfig: GithubIntegrationConfig; logger: Logger; githubCredentialsProvider?: GithubCredentialsProvider; }, diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index 0663122253..4981331c89 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -21,7 +21,7 @@ import { IdentityApi, } from '@backstage/core-plugin-api'; import { - GitHubIntegrationConfig, + GithubIntegrationConfig, ScmIntegrationRegistry, } from '@backstage/integration'; import { ScmAuthApi } from '@backstage/integration-react'; @@ -239,7 +239,7 @@ the component will become available.\n\nFor more information, read an \ body: string; fileContent: string; repositoryUrl: string; - githubIntegrationConfig: GitHubIntegrationConfig; + githubIntegrationConfig: GithubIntegrationConfig; }): Promise<{ link: string; location: string }> { const { owner, diff --git a/plugins/git-release-manager/src/api/GitReleaseClient.ts b/plugins/git-release-manager/src/api/GitReleaseClient.ts index 996b8bfdd3..91b1dbbecc 100644 --- a/plugins/git-release-manager/src/api/GitReleaseClient.ts +++ b/plugins/git-release-manager/src/api/GitReleaseClient.ts @@ -15,7 +15,7 @@ */ import { Octokit } from '@octokit/rest'; -import { GitHubIntegration, ScmIntegrations } from '@backstage/integration'; +import { GithubIntegration, ScmIntegrations } from '@backstage/integration'; import { DISABLE_CACHE } from '../constants/constants'; import { Project } from '../contexts/ProjectContext'; @@ -50,7 +50,7 @@ export class GitReleaseClient implements GitReleaseApi { private getGithubIntegrationConfig({ gitHubIntegrations, }: { - gitHubIntegrations: GitHubIntegration[]; + gitHubIntegrations: GithubIntegration[]; }) { const defaultIntegration = gitHubIntegrations.find( ({ config: { host } }) => host === 'github.com', diff --git a/plugins/github-actions/src/api/GithubActionsClient.ts b/plugins/github-actions/src/api/GithubActionsClient.ts index ebe3d92c4f..724081320f 100644 --- a/plugins/github-actions/src/api/GithubActionsClient.ts +++ b/plugins/github-actions/src/api/GithubActionsClient.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; import { GithubActionsApi } from './GithubActionsApi'; import { Octokit, RestEndpointMethodTypes } from '@octokit/rest'; import { ConfigApi, OAuthApi } from '@backstage/core-plugin-api'; @@ -36,7 +36,7 @@ export class GithubActionsClient implements GithubActionsApi { private async getOctokit(hostname?: string): Promise { // TODO: Get access token for the specified hostname const token = await this.githubAuthApi.getAccessToken(['repo']); - const configs = readGitHubIntegrationConfigs( + const configs = readGithubIntegrationConfigs( this.configApi.getOptionalConfigArray('integrations.github') ?? [], ); const githubIntegrationConfig = configs.find( diff --git a/plugins/github-actions/src/components/Cards/Cards.tsx b/plugins/github-actions/src/components/Cards/Cards.tsx index 79777ed8c3..e23f7f7d59 100644 --- a/plugins/github-actions/src/components/Cards/Cards.tsx +++ b/plugins/github-actions/src/components/Cards/Cards.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; import { useEntity } from '@backstage/plugin-catalog-react'; import { LinearProgress, @@ -88,7 +88,7 @@ export const LatestWorkflowRunCard = (props: { const config = useApi(configApiRef); const errorApi = useApi(errorApiRef); // TODO: Get github hostname from metadata annotation - const hostname = readGitHubIntegrationConfigs( + const hostname = readGithubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], )[0].host; const [owner, repo] = ( diff --git a/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx b/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx index 931b5b676e..89fd40da33 100644 --- a/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx +++ b/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; import { useEntity } from '@backstage/plugin-catalog-react'; import React, { useEffect } from 'react'; import { Link as RouterLink } from 'react-router-dom'; @@ -52,7 +52,7 @@ export const RecentWorkflowRunsCard = (props: { const errorApi = useApi(errorApiRef); // TODO: Get github hostname from metadata annotation - const hostname = readGitHubIntegrationConfigs( + const hostname = readGithubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], )[0].host; diff --git a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx index 4e920a4e3a..cda0b9c219 100644 --- a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx +++ b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import { Entity } from '@backstage/catalog-model'; -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; import { Accordion, AccordionDetails, @@ -170,7 +170,7 @@ export const WorkflowRunDetails = ({ entity }: { entity: Entity }) => { const projectName = getProjectNameFromEntity(entity); // TODO: Get github hostname from metadata annotation - const hostname = readGitHubIntegrationConfigs( + const hostname = readGithubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], )[0].host; const [owner, repo] = (projectName && projectName.split('/')) || []; diff --git a/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx b/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx index bf66399288..075b73dd98 100644 --- a/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx +++ b/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx @@ -17,7 +17,7 @@ import { Entity } from '@backstage/catalog-model'; import { LogViewer } from '@backstage/core-components'; import { configApiRef, useApi } from '@backstage/core-plugin-api'; -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; import { Accordion, AccordionSummary, @@ -80,7 +80,7 @@ export const WorkflowRunLogs = ({ const projectName = getProjectNameFromEntity(entity); // TODO: Get github hostname from metadata annotation - const hostname = readGitHubIntegrationConfigs( + const hostname = readGithubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], )[0].host; const [owner, repo] = (projectName && projectName.split('/')) || []; diff --git a/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx b/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx index c8c42eb1bc..fb80376354 100644 --- a/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx +++ b/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx @@ -31,7 +31,7 @@ import SyncIcon from '@material-ui/icons/Sync'; import { buildRouteRef } from '../../routes'; import { getProjectNameFromEntity } from '../getProjectNameFromEntity'; import { Entity } from '@backstage/catalog-model'; -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; import { EmptyState, Table, TableColumn } from '@backstage/core-components'; import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api'; @@ -159,7 +159,7 @@ export const WorkflowRunsTable = ({ const config = useApi(configApiRef); const projectName = getProjectNameFromEntity(entity); // TODO: Get github hostname from metadata annotation - const hostname = readGitHubIntegrationConfigs( + const hostname = readGithubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], )[0].host; const [owner, repo] = (projectName ?? '/').split('/'); diff --git a/plugins/github-issues/src/api/gitHubIssuesApi.ts b/plugins/github-issues/src/api/gitHubIssuesApi.ts index b4be445d13..bef1b20441 100644 --- a/plugins/github-issues/src/api/gitHubIssuesApi.ts +++ b/plugins/github-issues/src/api/gitHubIssuesApi.ts @@ -20,7 +20,7 @@ import { ErrorApi, OAuthApi, } from '@backstage/core-plugin-api'; -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; import { ForwardedError } from '@backstage/errors'; /** @internal */ @@ -115,7 +115,7 @@ export const gitHubIssuesApi = ( let octokit: Octokit; const getOctokit = async () => { - const baseUrl = readGitHubIntegrationConfigs( + const baseUrl = readGithubIntegrationConfigs( configApi.getOptionalConfigArray('integrations.github') ?? [], )[0].apiBaseUrl; diff --git a/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts b/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts index a864f9950e..6dadcef6d2 100644 --- a/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts +++ b/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts @@ -19,7 +19,7 @@ import { githubAuthApiRef, configApiRef, } from '@backstage/core-plugin-api'; -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; let octokit: any; @@ -27,7 +27,7 @@ export const useOctokitGraphQl = () => { const auth = useApi(githubAuthApiRef); const config = useApi(configApiRef); - const baseUrl = readGitHubIntegrationConfigs( + const baseUrl = readGithubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], )[0].apiBaseUrl; diff --git a/plugins/techdocs-module-addons-contrib/src/ReportIssue/hooks.ts b/plugins/techdocs-module-addons-contrib/src/ReportIssue/hooks.ts index 19cb47ec40..22dc359942 100644 --- a/plugins/techdocs-module-addons-contrib/src/ReportIssue/hooks.ts +++ b/plugins/techdocs-module-addons-contrib/src/ReportIssue/hooks.ts @@ -18,7 +18,7 @@ import parseGitUrl from 'git-url-parse'; import { useApi } from '@backstage/core-plugin-api'; import { - replaceGitHubUrlType, + replaceGithubUrlType, replaceGitLabUrlType, } from '@backstage/integration'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; @@ -31,7 +31,7 @@ import { PAGE_EDIT_LINK_SELECTOR } from './constants'; const resolveBlobUrl = (url: string, type: string) => { if (type === 'github') { - return replaceGitHubUrlType(url, 'blob'); + return replaceGithubUrlType(url, 'blob'); } else if (type === 'gitlab') { return replaceGitLabUrlType(url, 'blob'); } diff --git a/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts b/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts index 9c7865936c..3fca7ddeb9 100644 --- a/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts +++ b/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts @@ -16,7 +16,7 @@ import type { Transformer } from './index'; import { - replaceGitHubUrlType, + replaceGithubUrlType, ScmIntegrationRegistry, } from '@backstage/integration'; import FeedbackOutlinedIcon from '@material-ui/icons/FeedbackOutlined'; @@ -59,7 +59,7 @@ export const addGitFeedbackLink = ( // Convert GitHub edit url to blob type so it can be parsed by git-url-parse correctly const gitUrl = integration?.type === 'github' - ? replaceGitHubUrlType(sourceURL.href, 'blob') + ? replaceGithubUrlType(sourceURL.href, 'blob') : sourceURL.href; const gitInfo = parseGitUrl(gitUrl); const repoPath = `/${gitInfo.organization}/${gitInfo.name}`; From e0aa20ab120fcfbcd6cff50e4ef798586f639efa Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 19 Oct 2022 16:21:12 +0200 Subject: [PATCH 24/36] REVIEWING: use valid png link Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REVIEWING.md b/REVIEWING.md index 6a54ae875e..29bdcc3877 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -110,7 +110,7 @@ It also includes changes affecting both the Catalog and Catalog React library. I Added a new `EntityList` component that can be used to display detailed information about a list of entities. The component looks like this: -![EntityList screenshot](./screenshot.png) +![EntityList screenshot](./docs/assets/headline.png) It accepts the following properties: From c20e2b2f33f6571e92bca42d5e72e303c0afbf0b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 19 Oct 2022 16:26:07 +0200 Subject: [PATCH 25/36] Update REVIEWING.md Co-authored-by: Phil Kuang Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REVIEWING.md b/REVIEWING.md index 29bdcc3877..1e34938a0f 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -200,7 +200,7 @@ When modifying an input type, any change that increases constraints are breaking On the other hand, it's fine to relax constraints without it being a breaking change. For example, if we made the `title` prop optional, that would not be breaking. Likewise, if we changed the type of `size` to `'small' | 'medium' | 'large' | 'huge'`, that would not be breaking either. It is also possible to add new properties without it being a breaking change, as long as they are optional. -There's an edge-case where completely removing a property is also considered a breaking change. That's because of TypeScript being strict and refusing unknown properties, rather than a runtime breaking change. It is typically and easy thing for consumers to fix though. +There's an edge-case where completely removing a property is also considered a breaking change. That's because of TypeScript being strict and refusing unknown properties, rather than a runtime breaking change. It is typically an easy thing for consumers to fix though. Another way to think about the rules for evolving input types is that the old type must be assignable to the new type. In this case for example `_props: NewComponentProps = {} as OldComponentProps`. It's not a silver bullet though, because of edge-cases like the one mentioned above. From b573005f62f5f10220b3e5a841443906b7d0306c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 19 Oct 2022 16:26:35 +0200 Subject: [PATCH 26/36] Update REVIEWING.md Co-authored-by: Phil Kuang Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REVIEWING.md b/REVIEWING.md index 1e34938a0f..38bae08423 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -155,7 +155,7 @@ For tricky behavioral changes you may simply need to let end users provide feedb Typescript is a huge help when it comes to identifying breaking changes, as well as the API Reports that we generate for all packages. Most of the time it is enough to only look at the API Reports to determine whether a change is breaking or not. If you determine that a change is breaking at the TypeScript level, then it is a breaking change. -In this section we will be talking about changed "types", but by that we mean any kind of exported symbol from a packages, such as TypeScript types aliases or interfaces, functions, classes, constants, etc. +In this section we will be talking about changed "types", but by that we mean any kind of exported symbol from packages, such as TypeScript types aliases or interfaces, functions, classes, constants, etc. #### API Reports From 1fc2b3efe2136bab76a10d04c46c621cd97daff9 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 19 Oct 2022 16:30:03 +0200 Subject: [PATCH 27/36] Update REVIEWING.md Co-authored-by: Phil Kuang Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REVIEWING.md b/REVIEWING.md index 38bae08423..9d88c36b36 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -59,7 +59,7 @@ In general our changeset feedback bot will take care of informing whether a chan Changes that do NOT need a new changeset: -- Changes to any test, storybook, or other local development files, for example, `MyComponent.test.tsx`, `MyComponent.stories.tsx, `**mocks**/MyMock.ts`, `.eslintrc.js`, `setupTests.ts`, or `api-report.md`. Explained differently, it is only files that affect the published package that need changesets, such as source files and additional resources like `package.json`, `README.md`, `config.d.ts`, etc. +- Changes to any test, storybook, or other local development files, for example, `MyComponent.test.tsx`, `MyComponent.stories.tsx`, `**mocks**/MyMock.ts`, `.eslintrc.js`, `setupTests.ts`, or `api-report.md`. Explained differently, it is only files that affect the published package that need changesets, such as source files and additional resources like `package.json`, `README.md`, `config.d.ts`, etc. - When tweaking a change that has not yet been released, you can rely on and potentially modify the existing changeset instead. - Changes that do not belong to a published packages, either because it's not a package at all, such as `docs/`, or because the package is private, such as `packages/app`. - Changes that do not end up having an effect on the published package, such as whitespace fixes or code formatting changes. Although it's also fine to have a short changeset for these kind of changes too. From cc8bfc56c36bfad880109924792efa77e49aa664 Mon Sep 17 00:00:00 2001 From: Simon Ninon Date: Sun, 9 Oct 2022 20:08:14 -0700 Subject: [PATCH 28/36] plugin-github-pull-requests-board: support team members pull requests Signed-off-by: Simon Ninon --- .changeset/nasty-crabs-share.md | 8 ++ .../github-pull-requests-board/package.json | 1 + .../src/api/useGetPullRequestDetails.ts | 3 + .../api/useGetPullRequestsFromRepository.ts | 18 ++- .../src/api/useGetPullRequestsFromUser.ts | 108 ++++++++++++++++++ .../EntityTeamPullRequestsCard.tsx | 37 +++++- .../EntityTeamPullRequestsContent.tsx | 35 +++++- .../src/components/icons/DraftPr/DraftPr.tsx | 12 +- .../src/hooks/usePullRequestsByTeam.tsx | 79 ++++++++++--- .../src/hooks/useUserRepositories.tsx | 47 -------- .../src/hooks/useUserRepositoriesAndTeam.tsx | 81 +++++++++++++ .../src/utils/functions.ts | 49 +++++++- .../src/utils/types.tsx | 46 +++++--- yarn.lock | 1 + 14 files changed, 426 insertions(+), 99 deletions(-) create mode 100644 .changeset/nasty-crabs-share.md create mode 100644 plugins/github-pull-requests-board/src/api/useGetPullRequestsFromUser.ts delete mode 100644 plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx create mode 100644 plugins/github-pull-requests-board/src/hooks/useUserRepositoriesAndTeam.tsx diff --git a/.changeset/nasty-crabs-share.md b/.changeset/nasty-crabs-share.md new file mode 100644 index 0000000000..d09eb09044 --- /dev/null +++ b/.changeset/nasty-crabs-share.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-github-pull-requests-board': patch +--- + +Add a new "Team" Filter Options to the Github Pull Requests Dashboard. + +When toggling this option on, the dashboard will displays all of the PRs opened +by the members of that team on any repositories of the organization. diff --git a/plugins/github-pull-requests-board/package.json b/plugins/github-pull-requests-board/package.json index e4e9d0827a..e68e6b1377 100644 --- a/plugins/github-pull-requests-board/package.json +++ b/plugins/github-pull-requests-board/package.json @@ -45,6 +45,7 @@ "@material-ui/lab": "4.0.0-alpha.57", "@octokit/rest": "^19.0.3", "luxon": "^3.0.0", + "p-limit": "^4.0.0", "react-use": "^17.2.4" }, "devDependencies": { diff --git a/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts b/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts index d44c36b54d..3f818c63fc 100644 --- a/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts +++ b/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts @@ -33,6 +33,9 @@ export const useGetPullRequestDetails = () => { id repository { name + owner { + login + } } title url diff --git a/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts b/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts index 2295a01442..e1daf491cc 100644 --- a/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts +++ b/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts @@ -33,7 +33,7 @@ export const useGetPullRequestsFromRepository = () => { const limit = pullRequestLimit ?? PULL_REQUEST_LIMIT; const [organisation, repositoryName] = repo.split('/'); - return await getPullRequestEdges( + return await getPullRequestNodes( graphql, repositoryName, organisation, @@ -45,7 +45,7 @@ export const useGetPullRequestsFromRepository = () => { return fn.current; }; -async function getPullRequestEdges( +async function getPullRequestNodes( graphql: ( path: string, options?: any, @@ -54,7 +54,7 @@ async function getPullRequestEdges( organisation: string, pullRequestLimit: number, ): Promise { - const pullRequestEdges: PullRequestsNumber[] = []; + const pullRequestNodes: PullRequestsNumber[] = []; let result: GraphQlPullRequests | undefined = undefined; do { @@ -68,10 +68,8 @@ async function getPullRequestEdges( ) { repository(name: $name, owner: $owner) { pullRequests(states: OPEN, first: $first, after: $endCursor) { - edges { - node { - number - } + nodes { + number } pageInfo { hasNextPage @@ -94,10 +92,10 @@ async function getPullRequestEdges( }, ); - pullRequestEdges.push(...result.repository.pullRequests.edges); + pullRequestNodes.push(...result.repository.pullRequests.nodes); - if (pullRequestEdges.length >= pullRequestLimit) return pullRequestEdges; + if (pullRequestNodes.length >= pullRequestLimit) return pullRequestNodes; } while (result.repository.pullRequests.pageInfo.hasNextPage); - return pullRequestEdges; + return pullRequestNodes; } diff --git a/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromUser.ts b/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromUser.ts new file mode 100644 index 0000000000..fb4fb66bd3 --- /dev/null +++ b/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromUser.ts @@ -0,0 +1,108 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; + +import { + GraphQlUserPullRequests, + PullRequestsNumberAndOwner, +} from '../utils/types'; +import { useOctokitGraphQl } from './useOctokitGraphQl'; + +const PULL_REQUEST_LIMIT = 10; +const GITHUB_GRAPHQL_MAX_ITEMS = 100; + +export const useGetPullRequestsFromUser = () => { + const graphql = + useOctokitGraphQl>(); + + const fn = React.useRef( + async ( + userLogin: string, + organization?: string, + pullRequestLimit?: number, + ): Promise => { + const limit = pullRequestLimit ?? PULL_REQUEST_LIMIT; + + return await getPullRequestNodes(graphql, userLogin, limit, organization); + }, + ); + + return fn.current; +}; + +async function getPullRequestNodes( + graphql: ( + path: string, + options?: any, + ) => Promise>, + userLogin: string, + pullRequestLimit: number, + organization?: string, +): Promise { + const pullRequestNodes: PullRequestsNumberAndOwner[] = []; + let result: + | GraphQlUserPullRequests + | undefined = undefined; + + do { + result = await graphql( + ` + query ($login: String!, $first: Int, $endCursor: String) { + user(login: $login) { + pullRequests(states: OPEN, first: $first, after: $endCursor) { + nodes { + number + repository { + name + owner { + login + } + } + } + pageInfo { + hasNextPage + endCursor + } + } + } + } + `, + { + login: userLogin, + first: + pullRequestLimit > GITHUB_GRAPHQL_MAX_ITEMS + ? GITHUB_GRAPHQL_MAX_ITEMS + : pullRequestLimit, + endCursor: result + ? result.user.pullRequests.pageInfo.endCursor + : undefined, + }, + ); + + pullRequestNodes.push( + ...result.user.pullRequests.nodes.filter( + edge => + !organization || + edge?.repository?.owner?.login?.toLocaleLowerCase('en-US') === + organization.toLocaleLowerCase('en-US'), + ), + ); + + if (pullRequestNodes.length >= pullRequestLimit) return pullRequestNodes; + } while (result.user.pullRequests.pageInfo.hasNextPage); + + return pullRequestNodes; +} diff --git a/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.tsx b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.tsx index 2ae33c6b07..95d7745c62 100644 --- a/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.tsx +++ b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.tsx @@ -16,6 +16,7 @@ import React, { useState } from 'react'; import { Grid, Typography } from '@material-ui/core'; import FullscreenIcon from '@material-ui/icons/Fullscreen'; +import PeopleIcon from '@material-ui/icons/People'; import { Progress, InfoCard } from '@backstage/core-components'; @@ -25,8 +26,9 @@ import { Wrapper } from '../Wrapper'; import { PullRequestCard } from '../PullRequestCard'; import { usePullRequestsByTeam } from '../../hooks/usePullRequestsByTeam'; import { PRCardFormating } from '../../utils/types'; +import { shouldDisplayCard } from '../../utils/functions'; import { DraftPrIcon } from '../icons/DraftPr'; -import { useUserRepositories } from '../../hooks/useUserRepositories'; +import { useUserRepositoriesAndTeam } from '../../hooks/useUserRepositoriesAndTeam'; /** @public */ export interface EntityTeamPullRequestsCardProps { @@ -36,9 +38,20 @@ export interface EntityTeamPullRequestsCardProps { const EntityTeamPullRequestsCard = (props: EntityTeamPullRequestsCardProps) => { const { pullRequestLimit } = props; const [infoCardFormat, setInfoCardFormat] = useState([]); - const { repositories } = useUserRepositories(); - const { loading, pullRequests, refreshPullRequests } = usePullRequestsByTeam( + const { + loading: loadingReposAndTeam, repositories, + teamMembers, + teamMembersOrganization, + } = useUserRepositoriesAndTeam(); + const { + loading: loadingPRs, + pullRequests, + refreshPullRequests, + } = usePullRequestsByTeam( + repositories, + teamMembers, + teamMembersOrganization, pullRequestLimit, ); @@ -48,6 +61,11 @@ const EntityTeamPullRequestsCard = (props: EntityTeamPullRequestsCardProps) => { onClickOption={newFormats => setInfoCardFormat(newFormats)} value={infoCardFormat} options={[ + { + icon: , + value: 'team', + ariaLabel: 'Show PRs from your team', + }, { icon: , value: 'draft', @@ -56,7 +74,7 @@ const EntityTeamPullRequestsCard = (props: EntityTeamPullRequestsCardProps) => { { icon: , value: 'fullscreen', - ariaLabel: 'Info card is set to fullscreen', + ariaLabel: 'Set card to fullscreen', }, ]} /> @@ -64,7 +82,7 @@ const EntityTeamPullRequestsCard = (props: EntityTeamPullRequestsCardProps) => { ); const getContent = () => { - if (loading) { + if (loadingReposAndTeam || loadingPRs) { return ; } @@ -92,7 +110,14 @@ const EntityTeamPullRequestsCard = (props: EntityTeamPullRequestsCardProps) => { }, index, ) => - infoCardFormat.includes('draft') === isDraft && ( + shouldDisplayCard( + repository, + author, + repositories, + teamMembers, + infoCardFormat, + isDraft, + ) && ( { const { pullRequestLimit } = props; const [infoCardFormat, setInfoCardFormat] = useState([]); - const { repositories } = useUserRepositories(); - const { loading, pullRequests, refreshPullRequests } = usePullRequestsByTeam( + const { + loading: loadingReposAndTeam, repositories, + teamMembers, + teamMembersOrganization, + } = useUserRepositoriesAndTeam(); + const { + loading: loadingPRs, + pullRequests, + refreshPullRequests, + } = usePullRequestsByTeam( + repositories, + teamMembers, + teamMembersOrganization, pullRequestLimit, ); @@ -48,6 +61,11 @@ const EntityTeamPullRequestsContent = ( onClickOption={newFormats => setInfoCardFormat(newFormats)} value={infoCardFormat} options={[ + { + icon: , + value: 'team', + ariaLabel: 'Show PRs from your team', + }, { icon: , value: 'draft', @@ -59,7 +77,7 @@ const EntityTeamPullRequestsContent = ( ); const getContent = () => { - if (loading) { + if (loadingReposAndTeam || loadingPRs) { return ; } @@ -84,7 +102,14 @@ const EntityTeamPullRequestsContent = ( }, index, ) => - infoCardFormat.includes('draft') === isDraft && ( + shouldDisplayCard( + repository, + author, + repositories, + teamMembers, + infoCardFormat, + isDraft, + ) && ( ( width="16" data-view-component="true" > - - + + + + ); diff --git a/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx b/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx index 2e6553f778..de36d806ff 100644 --- a/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx +++ b/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx @@ -13,48 +13,93 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import pLimit from 'p-limit'; import { useCallback, useEffect, useState } from 'react'; import { formatPRsByReviewDecision } from '../utils/functions'; import { PullRequests, PullRequestsColumn } from '../utils/types'; import { useGetPullRequestsFromRepository } from '../api/useGetPullRequestsFromRepository'; +import { useGetPullRequestsFromUser } from '../api/useGetPullRequestsFromUser'; import { useGetPullRequestDetails } from '../api/useGetPullRequestDetails'; export function usePullRequestsByTeam( repositories: string[], + members: string[], + organization?: string, pullRequestLimit?: number, ) { const [pullRequests, setPullRequests] = useState([]); const [loading, setLoading] = useState(true); - const getPullRequests = useGetPullRequestsFromRepository(); + const getPullRequestsFromRepository = useGetPullRequestsFromRepository(); + const getPullRequestsFromUser = useGetPullRequestsFromUser(); const getPullRequestDetails = useGetPullRequestDetails(); const getPRsPerRepository = useCallback( async (repository: string): Promise => { - const pullRequestsNumbers = await getPullRequests( + const concurrencyLimit = pLimit(5); + const pullRequestsNumbers = await getPullRequestsFromRepository( repository, pullRequestLimit, ); const pullRequestsWithDetails = await Promise.all( - pullRequestsNumbers.map(({ node }) => - getPullRequestDetails(repository, node.number), + pullRequestsNumbers.map(node => + concurrencyLimit(() => + getPullRequestDetails(repository, node.number), + ), ), ); return pullRequestsWithDetails; }, - [getPullRequests, getPullRequestDetails, pullRequestLimit], + [getPullRequestsFromRepository, getPullRequestDetails, pullRequestLimit], + ); + + const getPRsPerTeamMember = useCallback( + async ( + teamMember: string, + teamOrganization?: string, + ): Promise => { + const concurrencyLimit = pLimit(3); + const pullRequestsNumbers = await getPullRequestsFromUser( + teamMember, + teamOrganization, + pullRequestLimit, + ); + + const pullRequestsWithDetails = await Promise.all( + pullRequestsNumbers.map(node => + concurrencyLimit(() => + getPullRequestDetails( + `${node.repository.owner.login}/${node.repository.name}`, + node.number, + ), + ), + ), + ); + + return pullRequestsWithDetails; + }, + [getPullRequestsFromUser, getPullRequestDetails, pullRequestLimit], ); const getPRsFromTeam = useCallback( - async (teamRepositories: string[]): Promise => { + async ( + teamRepositories: string[], + teamMembers: string[], + teamOrganization?: string, + ): Promise => { const teamRepositoriesPromises = teamRepositories.map(repository => getPRsPerRepository(repository), ); - const teamPullRequests = await Promise.allSettled( - teamRepositoriesPromises, - ).then(promises => + const teamMembersPromises = teamMembers.map(teamMember => + getPRsPerTeamMember(teamMember, teamOrganization), + ); + + const teamPullRequests = await Promise.allSettled([ + ...teamRepositoriesPromises, + ...teamMembersPromises, + ]).then(promises => promises.reduce((acc, curr) => { if (curr.status === 'fulfilled') { return [...acc, ...curr.value]; @@ -63,18 +108,26 @@ export function usePullRequestsByTeam( }, [] as PullRequests), ); - return teamPullRequests; + const uniqueTeamPullRequests = teamPullRequests.filter( + (lhs, i) => teamPullRequests.findIndex(rhs => lhs.id === rhs.id) === i, + ); + + return uniqueTeamPullRequests; }, - [getPRsPerRepository], + [getPRsPerRepository, getPRsPerTeamMember], ); const getAllPullRequests = useCallback(async () => { setLoading(true); - const teamPullRequests = await getPRsFromTeam(repositories); + const teamPullRequests = await getPRsFromTeam( + repositories, + members, + organization, + ); setPullRequests(formatPRsByReviewDecision(teamPullRequests)); setLoading(false); - }, [getPRsFromTeam, repositories]); + }, [getPRsFromTeam, repositories, members, organization]); useEffect(() => { getAllPullRequests(); diff --git a/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx b/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx deleted file mode 100644 index ed0d3580f0..0000000000 --- a/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { stringifyEntityRef } from '@backstage/catalog-model'; -import { useApi } from '@backstage/core-plugin-api'; -import { catalogApiRef, useEntity } from '@backstage/plugin-catalog-react'; -import { useCallback, useEffect, useState } from 'react'; -import { getProjectNameFromEntity } from '../utils/functions'; - -export function useUserRepositories() { - const { entity: teamEntity } = useEntity(); - const catalogApi = useApi(catalogApiRef); - const [repositories, setRepositories] = useState([]); - - const getRepositoriesNames = useCallback(async () => { - const entitiesList = await catalogApi.getEntities({ - filter: { 'relations.ownedBy': stringifyEntityRef(teamEntity) }, - }); - - const entitiesNames: string[] = entitiesList.items.map(componentEntity => - getProjectNameFromEntity(componentEntity), - ); - - setRepositories([...new Set(entitiesNames)]); - }, [catalogApi, teamEntity]); - - useEffect(() => { - getRepositoriesNames(); - }, [getRepositoriesNames]); - - return { - repositories, - }; -} diff --git a/plugins/github-pull-requests-board/src/hooks/useUserRepositoriesAndTeam.tsx b/plugins/github-pull-requests-board/src/hooks/useUserRepositoriesAndTeam.tsx new file mode 100644 index 0000000000..162dadcfbf --- /dev/null +++ b/plugins/github-pull-requests-board/src/hooks/useUserRepositoriesAndTeam.tsx @@ -0,0 +1,81 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { stringifyEntityRef } from '@backstage/catalog-model'; +import { useApi } from '@backstage/core-plugin-api'; +import { catalogApiRef, useEntity } from '@backstage/plugin-catalog-react'; +import { useCallback, useEffect, useState } from 'react'; +import { + getProjectNameFromEntity, + getGithubOrganizationFromEntity, + getUserNameFromEntity, +} from '../utils/functions'; + +export function useUserRepositoriesAndTeam() { + const { entity: teamEntity } = useEntity(); + const catalogApi = useApi(catalogApiRef); + const [loading, setLoading] = useState(true); + const [teamData, setTeamData] = useState<{ + repositories: string[]; + teamMembers: string[]; + }>({ + repositories: [], + teamMembers: [], + }); + + const getTeamData = useCallback(async () => { + setLoading(true); + + // get team repositories and members + const entitiesList = await catalogApi.getEntities({ + filter: [ + { 'relations.ownedBy': stringifyEntityRef(teamEntity) }, + { 'relations.memberOf': stringifyEntityRef(teamEntity) }, + ], + }); + + const repositories = entitiesList.items.filter( + entity => entity.kind === 'Component', + ); + const repositoriesNames: string[] = repositories + .map(componentEntity => getProjectNameFromEntity(componentEntity) ?? '') + .filter(projectName => !!projectName); + + const teamMembers = entitiesList.items.filter( + entity => entity.kind === 'User', + ); + const teamMembersNames: string[] = teamMembers + .map(componentEntity => getUserNameFromEntity(componentEntity) ?? '') + .filter(userName => !!userName); + + setTeamData({ + repositories: [...new Set(repositoriesNames)], + teamMembers: [...new Set(teamMembersNames)], + }); + setLoading(false); + }, [catalogApi, teamEntity]); + + useEffect(() => { + getTeamData(); + }, [getTeamData]); + + return { + loading, + repositories: teamData.repositories, + teamMembers: teamData.teamMembers, + teamMembersOrganization: getGithubOrganizationFromEntity(teamEntity), + }; +} diff --git a/plugins/github-pull-requests-board/src/utils/functions.ts b/plugins/github-pull-requests-board/src/utils/functions.ts index 5644530689..227cad459c 100644 --- a/plugins/github-pull-requests-board/src/utils/functions.ts +++ b/plugins/github-pull-requests-board/src/utils/functions.ts @@ -21,13 +21,29 @@ import { ReviewDecision, PullRequestsColumn, Author, + PRCardFormating, + Repository, } from './types'; import { COLUMNS } from './constants'; const GITHUB_PULL_REQUESTS_ANNOTATION = 'github.com/project-slug'; +const GITHUB_USER_LOGIN_ANNOTATION = 'github.com/user-login'; -export const getProjectNameFromEntity = (entity: Entity): string => { - return entity?.metadata.annotations?.[GITHUB_PULL_REQUESTS_ANNOTATION] ?? ''; +export const getProjectNameFromEntity = ( + entity: Entity, +): string | undefined => { + return entity?.metadata.annotations?.[GITHUB_PULL_REQUESTS_ANNOTATION]; +}; + +export const getUserNameFromEntity = (entity: Entity): string | undefined => { + return entity?.metadata.annotations?.[GITHUB_USER_LOGIN_ANNOTATION]; +}; + +export const getGithubOrganizationFromEntity = (entity: Entity): string => { + return ( + entity?.metadata?.annotations?.['github.com/team-slug']?.split('/')?.[0] ?? + '' + ); }; export const getApprovedReviews = (reviews: Reviews = []): Reviews => { @@ -107,3 +123,32 @@ export const formatPRsByReviewDecision = ( { title: COLUMNS.APPROVED, content: reviewDecisions.APPROVED }, ]; }; + +export const shouldDisplayCard = ( + repository: Repository, + author: Author, + teamRepositories: string[], + teamMembers: string[], + infoCardFormat: PRCardFormating[], + isDraft: boolean, +) => { + // hide draft PRs unless "draft" filter is toggled + if (infoCardFormat.includes('draft') !== isDraft) { + return false; + } + + // when "team" filter is toggled on, only shows PR from team members + if (infoCardFormat.includes('team')) { + return teamMembers.includes(author.login); + } + + const fullRepoName = + `${repository.owner.login}/${repository.name}`.toLocaleLowerCase('en-US'); + + const repositories = teamRepositories.map(repo => + repo.toLocaleLowerCase('en-US'), + ); + + // when "team" filter is toggled off, only shows PR on team repos + return repositories.includes(fullRepoName); +}; diff --git a/plugins/github-pull-requests-board/src/utils/types.tsx b/plugins/github-pull-requests-board/src/utils/types.tsx index a1e2dbfda3..8a9f15b967 100644 --- a/plugins/github-pull-requests-board/src/utils/types.tsx +++ b/plugins/github-pull-requests-board/src/utils/types.tsx @@ -19,22 +19,33 @@ export type GraphQlPullRequest = { }; }; +export type Connection = { + nodes: T; + pageInfo: { + hasNextPage: boolean; + endCursor?: string; + }; +}; + export type GraphQlPullRequests = { repository: { - pullRequests: { - edges: T; - pageInfo: { - hasNextPage: boolean; - endCursor?: string; - }; - }; + pullRequests: Connection; + }; +}; + +export type GraphQlUserPullRequests = { + user: { + pullRequests: Connection; }; }; export type PullRequestsNumber = { - node: { - number: number; - }; + number: number; +}; + +export type PullRequestsNumberAndOwner = { + number: number; + repository: Repository; }; export type Review = { @@ -57,11 +68,16 @@ export type Author = { name: string; }; +export type Repository = { + name: string; + owner: { + login: string; + }; +}; + export type PullRequest = { id: string; - repository: { - name: string; - }; + repository: Repository; title: string; url: string; lastEditedAt: string; @@ -83,6 +99,8 @@ export type PullRequestsColumn = { content: PullRequests; }; -export type PRCardFormating = 'compacted' | 'fullscreen' | 'draft'; +export type PRCardFormating = 'compacted' | 'fullscreen' | 'draft' | 'team'; export type ReviewDecision = 'IN_PROGRESS' | 'APPROVED' | 'REVIEW_REQUIRED'; + +export type PullRequestsSourceType = 'TeamRepositories' | 'TeamMembers'; diff --git a/yarn.lock b/yarn.lock index 00cd26f161..d0335aa767 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5665,6 +5665,7 @@ __metadata: cross-fetch: ^3.1.5 luxon: ^3.0.0 msw: ^0.47.0 + p-limit: ^4.0.0 react-use: ^17.2.4 peerDependencies: react: ^16.13.1 || ^17.0.0 From a6d779d58af41fad346648e62332a62a4f6da2ba Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Thu, 20 Oct 2022 00:18:44 +0200 Subject: [PATCH 29/36] chore: remove explicit default visibility at `config.d.ts` files Signed-off-by: Patrick Jungermann --- .changeset/unlucky-buttons-poke.md | 15 +++++++++++++++ packages/integration/config.d.ts | 5 ----- plugins/catalog-backend-module-aws/config.d.ts | 3 --- plugins/catalog-backend-module-gerrit/config.d.ts | 3 --- plugins/stack-overflow-backend/config.d.ts | 1 - plugins/techdocs-backend/config.d.ts | 9 --------- 6 files changed, 15 insertions(+), 21 deletions(-) create mode 100644 .changeset/unlucky-buttons-poke.md diff --git a/.changeset/unlucky-buttons-poke.md b/.changeset/unlucky-buttons-poke.md new file mode 100644 index 0000000000..b0ccd2039a --- /dev/null +++ b/.changeset/unlucky-buttons-poke.md @@ -0,0 +1,15 @@ +--- +'@backstage/integration': patch +'@backstage/plugin-catalog-backend-module-aws': patch +'@backstage/plugin-catalog-backend-module-gerrit': patch +'@backstage/plugin-stack-overflow-backend': patch +'@backstage/plugin-techdocs-backend': patch +--- + +Remove explicit default visibility at `config.d.ts` files. + +```ts +/** + * @visibility backend + */ +``` diff --git a/packages/integration/config.d.ts b/packages/integration/config.d.ts index 7c897d637a..dc68643bdd 100644 --- a/packages/integration/config.d.ts +++ b/packages/integration/config.d.ts @@ -164,7 +164,6 @@ export interface Config { /** * GitHub Apps configuration - * @visibility backend */ apps?: Array<{ /** @@ -233,7 +232,6 @@ export interface Config { googleGcs?: { /** * Service account email used to authenticate requests. - * @visibility backend */ clientEmail?: string; /** @@ -265,7 +263,6 @@ export interface Config { /** * Account access key used to authenticate requests. - * @visibility backend */ accessKeyId?: string; /** @@ -276,13 +273,11 @@ export interface Config { /** * ARN of the role to be assumed - * @visibility backend */ roleArn?: string; /** * External ID to use when assuming role - * @visibility backend */ externalId?: string; }>; diff --git a/plugins/catalog-backend-module-aws/config.d.ts b/plugins/catalog-backend-module-aws/config.d.ts index d6efe10c90..b3610ccfc5 100644 --- a/plugins/catalog-backend-module-aws/config.d.ts +++ b/plugins/catalog-backend-module-aws/config.d.ts @@ -64,20 +64,17 @@ export interface Config { { /** * (Required) AWS S3 Bucket Name - * @visibility backend */ bucketName: string; /** * (Optional) AWS S3 Object key prefix * If not set, all keys will be accepted, no filtering will be applied. - * @visibility backend */ prefix?: string; /** * (Optional) AWS Region. * If not set, AWS_REGION environment variable or aws config file will be used. * @see https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-region.html - * @visibility backend */ region?: string; /** diff --git a/plugins/catalog-backend-module-gerrit/config.d.ts b/plugins/catalog-backend-module-gerrit/config.d.ts index 744917a198..d8b48d2967 100644 --- a/plugins/catalog-backend-module-gerrit/config.d.ts +++ b/plugins/catalog-backend-module-gerrit/config.d.ts @@ -30,19 +30,16 @@ export interface Config { { /** * (Required) The host of the Gerrit integration to use. - * @visibility backend */ host: string; /** * (Required) The query to use for the "List Projects" API call. Used to limit the * scope of the projects that the provider tries to ingest. - * @visibility backend */ query: string; /** * (Optional) Branch. * The branch where the provider will try to find entities. Defaults to "master". - * @visibility backend */ branch?: string; } diff --git a/plugins/stack-overflow-backend/config.d.ts b/plugins/stack-overflow-backend/config.d.ts index d329a4389f..617524f0a3 100644 --- a/plugins/stack-overflow-backend/config.d.ts +++ b/plugins/stack-overflow-backend/config.d.ts @@ -21,7 +21,6 @@ export interface Config { stackoverflow?: { /** * The base url of the Stack Overflow API used for the plugin - * @visibility backend */ baseUrl?: string; diff --git a/plugins/techdocs-backend/config.d.ts b/plugins/techdocs-backend/config.d.ts index cb9d4ceabf..ec35505a78 100644 --- a/plugins/techdocs-backend/config.d.ts +++ b/plugins/techdocs-backend/config.d.ts @@ -101,13 +101,11 @@ export interface Config { secretAccessKey?: string; /** * ARN of role to be assumed - * @visibility backend */ roleArn?: string; }; /** * (Required) Cloud Storage Bucket Name - * @visibility backend */ bucketName: string; /** @@ -128,7 +126,6 @@ export interface Config { * (Optional) Whether to use path style URLs when communicating with S3. * Defaults to false. * This allows providers like LocalStack, Minio and Wasabi (and possibly others) to be used to host tech docs. - * @visibility backend */ s3ForcePathStyle?: boolean; @@ -167,17 +164,14 @@ export interface Config { }; /** * (Required) Cloud Storage Container Name - * @visibility backend */ containerName: string; /** * (Required) Auth url sometimes OpenStack uses different port check your OpenStack apis. - * @visibility backend */ authUrl: string; /** * (Required) Swift URL - * @visibility backend */ swiftUrl: string; }; @@ -209,7 +203,6 @@ export interface Config { }; /** * (Required) Cloud Storage Container Name - * @visibility backend */ containerName: string; }; @@ -223,7 +216,6 @@ export interface Config { googleGcs?: { /** * (Required) Cloud Storage Bucket Name - * @visibility backend */ bucketName: string; /** @@ -237,7 +229,6 @@ export interface Config { * (Optional) GCP project ID that contains the bucket. Should be * set if credentials is not set, or if the service account in * the credentials belongs to a different project to the bucket. - * @visibility backend */ projectId?: string; }; From 599b9d00698c2b7b5549ad12f8ccf80cc006690b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 20 Oct 2022 09:41:45 +0200 Subject: [PATCH 30/36] Update REVIEWING.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REVIEWING.md b/REVIEWING.md index 9d88c36b36..cccbe90b3d 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -147,7 +147,7 @@ These are changes where the behavior of the code changes, but the public API is It's hard to set up exact rules for when a behavioral change is breaking or not. In some cases it's obvious, for example if you remove important functionality of a system, while in other cases it can be very hard to tell. In the end what's important is whether a significant number of users of the package will be negatively impacted by the change. One question that you can ask yourself here is "is it likely that there are users that don't want the new behavior, or will need to change their code to adapt to the new behavior?" If the answer is yes, then it's likely a breaking change. You do also want to keep [xkcd.com/1172](https://xkcd.com/1172/) in mind though. -Note that even a bug fix can be considered a breaking change in some situations. One things to lean on in that case is what the _documented_ behavior is. If the current behavior does not match the documented behavior, then a change to match the documentation is generally not a breaking change. That is unless it is likely that there are a significant number of users that will be impacted by the change. +Note that even a bug fix can be considered a breaking change in some situations. One thing to lean on in that case is what the _documented_ behavior is. If the current behavior does not match the documented behavior, then a change to match the documentation is generally not a breaking change. That is unless it is likely that there are a significant number of users that will be impacted by the change. For tricky behavioral changes you may simply need to let end users provide feedback. This can be done either by hiding the new behavior behind an experimental feature switch, or by releasing the change early on in the release cycle, preferably in the first or second next line release. Be ready to respond to feedback and potentially revert the change if needed. From dacd03daf84c53481b6df969da752a9bf875623e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 20 Oct 2022 09:45:38 +0200 Subject: [PATCH 31/36] Update REVIEWING.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REVIEWING.md b/REVIEWING.md index cccbe90b3d..686844b88e 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -177,7 +177,7 @@ An important distinction to make when looking at changes to an API Report is the #### Input Types -A input type is one where a value needs to be provided by users of the package. The most common form of input type are function, constructor, and method parameters. +An input type is one where a value needs to be provided by users of the package. The most common form of input type are function, constructor, and method parameters. The following is an example where `MyComponentProps` is an input type: From ff621dbb5b507baad96510625032f2b65b922717 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 20 Oct 2022 09:49:38 +0200 Subject: [PATCH 32/36] Update REVIEWING.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REVIEWING.md b/REVIEWING.md index 686844b88e..89d9212274 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -229,7 +229,7 @@ When modifying an output type, any change that reduces constraints are breaking. Adding new properties is not a breaking change, regardless of whether they are optional or not. Removing properties is on the other hand always breaking. -It is generally fine to increase constraints without it being a breaking change. For example, if we made the `title` property required, that would not be breaking. +It is generally fine to increase constraints without it being a breaking change. For example, if we made the `shape` property required, that would not be breaking. There are some edge-cases though, for example if `shape` was changed to just `'square'`, that would be a breaking change because consumers might be checking for `box.shape === 'rounded'`, which would then be breaking. It's typically a quite easy thing for consumers to fix though. More generally, type unions and discriminated unions are quite troublesome in output types, as both adding and removing types from them are considered breaking changes. From d6e5523326e94f91369d2699fa8a6fd2cc10e9d1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 20 Oct 2022 09:50:28 +0200 Subject: [PATCH 33/36] Update REVIEWING.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REVIEWING.md b/REVIEWING.md index 89d9212274..469ac249f7 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -149,7 +149,7 @@ It's hard to set up exact rules for when a behavioral change is breaking or not. Note that even a bug fix can be considered a breaking change in some situations. One thing to lean on in that case is what the _documented_ behavior is. If the current behavior does not match the documented behavior, then a change to match the documentation is generally not a breaking change. That is unless it is likely that there are a significant number of users that will be impacted by the change. -For tricky behavioral changes you may simply need to let end users provide feedback. This can be done either by hiding the new behavior behind an experimental feature switch, or by releasing the change early on in the release cycle, preferably in the first or second next line release. Be ready to respond to feedback and potentially revert the change if needed. +For tricky behavioral changes you may simply need to let end users provide feedback. This can be done either by hiding the new behavior behind an experimental feature switch, or by releasing the change early on in the release cycle, preferably in the first or second `next`-line release. Be ready to respond to feedback and potentially revert the change if needed. ### Public API Changes From f44782ac99477ee41af842b67bd515e10b7a7caf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 20 Oct 2022 08:23:49 +0000 Subject: [PATCH 34/36] Update dependency cron to v2.1.0 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2392f41e62..01b98f1614 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18985,11 +18985,11 @@ __metadata: linkType: hard "cron@npm:^2.0.0": - version: 2.0.0 - resolution: "cron@npm:2.0.0" + version: 2.1.0 + resolution: "cron@npm:2.1.0" dependencies: luxon: ^1.23.x - checksum: 179ec137ada4ceb44cafe51c55491e84954308d7012d2a44539f0dadbbb1ffbbe3072c2f7aaa88595d60bd56e0d536aae2e4aaa4430c869317d6c2abd966988b + checksum: 9395875c091f56db7964491c249cb143d2e4ba77560d7132da783943c1b0537ef1814eb8f552c81eda5a2aa153216dd3f5b7ff63e372a68a063fcfafe8231f91 languageName: node linkType: hard From bd8e745d37fadc51348cab4c917d2299540858a6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 20 Oct 2022 09:15:24 +0000 Subject: [PATCH 35/36] Update dependency cronstrue to v2.14.0 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 01b98f1614..17f0242675 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18994,9 +18994,9 @@ __metadata: linkType: hard "cronstrue@npm:^2.2.0": - version: 2.11.0 - resolution: "cronstrue@npm:2.11.0" - checksum: 9a96c052ffbefd5e88458fb3afe7655d2d1c807660c35da982de7b387d507e471d21f12e275dae0abb11f15df471847ea9157a9637e126bf8439d7baff0b69ca + version: 2.14.0 + resolution: "cronstrue@npm:2.14.0" + checksum: ffbceca5211575513b37a454253fe7aeb16fe841ed12c1e8a2241f0e55f46a72d4cb97fc3b56aa6bfc58c7d249f8c168f89bfd2011841f237098f86f435f6fcf languageName: node linkType: hard From 4144890749842247b903d51a6c03940c30df39a1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 20 Oct 2022 09:59:17 +0000 Subject: [PATCH 36/36] Update dependency @oriflame/backstage-plugin-score-card to v0.5.4 Signed-off-by: Renovate Bot --- yarn.lock | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/yarn.lock b/yarn.lock index df3322283b..06c2864ecf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3301,7 +3301,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/catalog-model@^1.1.0-next.2, @backstage/catalog-model@^1.1.1, @backstage/catalog-model@workspace:^, @backstage/catalog-model@workspace:packages/catalog-model": +"@backstage/catalog-model@^1.1.1, @backstage/catalog-model@^1.1.2, @backstage/catalog-model@workspace:^, @backstage/catalog-model@workspace:packages/catalog-model": version: 0.0.0-use.local resolution: "@backstage/catalog-model@workspace:packages/catalog-model" dependencies: @@ -3515,7 +3515,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/config@^1.0.1, @backstage/config@workspace:^, @backstage/config@workspace:packages/config": +"@backstage/config@^1.0.3, @backstage/config@workspace:^, @backstage/config@workspace:packages/config": version: 0.0.0-use.local resolution: "@backstage/config@workspace:packages/config" dependencies: @@ -3561,7 +3561,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-components@^0.11.0, @backstage/core-components@^0.11.1, @backstage/core-components@workspace:^, @backstage/core-components@workspace:packages/core-components": +"@backstage/core-components@^0.11.1, @backstage/core-components@^0.11.2, @backstage/core-components@workspace:^, @backstage/core-components@workspace:packages/core-components": version: 0.0.0-use.local resolution: "@backstage/core-components@workspace:packages/core-components" dependencies: @@ -3633,7 +3633,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-plugin-api@^1.0.3, @backstage/core-plugin-api@^1.0.6, @backstage/core-plugin-api@workspace:^, @backstage/core-plugin-api@workspace:packages/core-plugin-api": +"@backstage/core-plugin-api@^1.0.6, @backstage/core-plugin-api@^1.0.7, @backstage/core-plugin-api@workspace:^, @backstage/core-plugin-api@workspace:packages/core-plugin-api": version: 0.0.0-use.local resolution: "@backstage/core-plugin-api@workspace:packages/core-plugin-api" dependencies: @@ -4772,7 +4772,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-common@^1.0.4-next.0, @backstage/plugin-catalog-common@workspace:^, @backstage/plugin-catalog-common@workspace:plugins/catalog-common": +"@backstage/plugin-catalog-common@^1.0.7, @backstage/plugin-catalog-common@workspace:^, @backstage/plugin-catalog-common@workspace:plugins/catalog-common": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-common@workspace:plugins/catalog-common" dependencies: @@ -4900,7 +4900,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-react@^1.1.2-next.2, @backstage/plugin-catalog-react@^1.1.4, @backstage/plugin-catalog-react@workspace:^, @backstage/plugin-catalog-react@workspace:plugins/catalog-react": +"@backstage/plugin-catalog-react@^1.1.4, @backstage/plugin-catalog-react@^1.2.0, @backstage/plugin-catalog-react@workspace:^, @backstage/plugin-catalog-react@workspace:plugins/catalog-react": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-react@workspace:plugins/catalog-react" dependencies: @@ -7674,7 +7674,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/theme@^0.2.16, @backstage/theme@^0.2.16-next.1, @backstage/theme@workspace:^, @backstage/theme@workspace:packages/theme": +"@backstage/theme@^0.2.16, @backstage/theme@workspace:^, @backstage/theme@workspace:packages/theme": version: 0.0.0-use.local resolution: "@backstage/theme@workspace:packages/theme" dependencies: @@ -11674,16 +11674,16 @@ __metadata: linkType: hard "@oriflame/backstage-plugin-score-card@npm:^0.5.1": - version: 0.5.3 - resolution: "@oriflame/backstage-plugin-score-card@npm:0.5.3" + version: 0.5.4 + resolution: "@oriflame/backstage-plugin-score-card@npm:0.5.4" dependencies: - "@backstage/catalog-model": ^1.1.0-next.2 - "@backstage/config": ^1.0.1 - "@backstage/core-components": ^0.11.0 - "@backstage/core-plugin-api": ^1.0.3 - "@backstage/plugin-catalog-common": ^1.0.4-next.0 - "@backstage/plugin-catalog-react": ^1.1.2-next.2 - "@backstage/theme": ^0.2.16-next.1 + "@backstage/catalog-model": ^1.1.2 + "@backstage/config": ^1.0.3 + "@backstage/core-components": ^0.11.2 + "@backstage/core-plugin-api": ^1.0.7 + "@backstage/plugin-catalog-common": ^1.0.7 + "@backstage/plugin-catalog-react": ^1.2.0 + "@backstage/theme": ^0.2.16 "@backstage/types": ^1.0.0 "@backstage/version-bridge": ^1.0.1 "@material-ui/core": ^4.12.2 @@ -11693,7 +11693,7 @@ __metadata: peerDependencies: react: ^16.13.1 || ^17.0.0 react-dom: ^16.13.1 || ^17.0.0 - checksum: 2f567d8838ecd938215899ce92e5569438bad8cdc521a938864a565497d4133f41f3710516abf3e375aaba881808de68ac41b249672943bda76fa54aa0352b06 + checksum: 1c9e08ec4bac5487941d2823ccd923c474ec18a99d62b97047d4bd0cdd1c33660339cfba1085dc8e756675a3f03cfff9e33cb395afa8fb74cacd11ab1251547c languageName: node linkType: hard