From 9a7570c5bdcad1955bb9bee1d0d505dec5c94abc Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Thu, 3 Apr 2025 19:51:39 +0100 Subject: [PATCH 01/17] Add ability replace the built in entity processors Signed-off-by: Brian Fletcher --- plugins/catalog-backend/config.d.ts | 9 +++++++++ plugins/catalog-backend/src/service/CatalogBuilder.ts | 10 ++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-backend/config.d.ts b/plugins/catalog-backend/config.d.ts index d7816fa856..7e68c4bea9 100644 --- a/plugins/catalog-backend/config.d.ts +++ b/plugins/catalog-backend/config.d.ts @@ -148,6 +148,15 @@ export interface Config { */ disableRelationsCompatibility?: boolean; + /** + * Disables the default backstage processors. + * + * Enabling this option allows more complete control of which processors are included + * in the backstage processing loop. + * + */ + disableDefaultProcessors?: boolean; + /** * The strategy to use for entities that are orphaned, i.e. no longer have * any other entities or providers referencing them. The default value is diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 99752ca5cf..a69f0bcfb0 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -736,8 +736,14 @@ export class CatalogBuilder { processors.push(builtinKindsEntityProcessor); } - // These are only added unless the user replaced them all - if (!this.processorsReplace) { + const disableDefaultProcessors = config.getOptionalBoolean( + 'catalog.disableDefaultProcessors', + ); + + // Add default processors if: + // - processors have NOT been explicitly replaced + // - and default processors are NOT disabled via config + if (!this.processorsReplace && !disableDefaultProcessors) { processors.push(...this.getDefaultProcessors()); } From d88b922353c21ce14ddb3f0cd9f40496ae19f59a Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Thu, 3 Apr 2025 19:54:02 +0100 Subject: [PATCH 02/17] add changeset Signed-off-by: Brian Fletcher --- .changeset/sharp-ligers-beg.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/sharp-ligers-beg.md diff --git a/.changeset/sharp-ligers-beg.md b/.changeset/sharp-ligers-beg.md new file mode 100644 index 0000000000..a89ce8ee05 --- /dev/null +++ b/.changeset/sharp-ligers-beg.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': minor +--- + +Adds the ability to disable the default entity processors using a new boolean app config item `catalog.disableCatalogProcessors`. From 611c941d8ebef584eac9224c6d7025bb68bbdc93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20R=C3=A4ntil=C3=A4?= Date: Thu, 6 Feb 2025 14:17:05 +0100 Subject: [PATCH 03/17] Add support for providing values and labels to the search filters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gustaf Räntilä --- .changeset/mighty-carrots-decide.md | 5 ++ .../app/src/components/search/SearchModal.tsx | 9 ++-- .../app/src/components/search/SearchPage.tsx | 9 ++-- plugins/search-react/report.api.md | 11 +++- .../SearchFilter.Autocomplete.tsx | 27 +++++++--- .../components/SearchFilter/SearchFilter.tsx | 27 ++++++---- .../components/SearchFilter/hooks.test.tsx | 20 +++++-- .../src/components/SearchFilter/hooks.ts | 24 +++++++-- .../src/components/SearchFilter/index.ts | 1 + .../src/components/SearchFilter/types.ts | 53 +++++++++++++++++++ 10 files changed, 152 insertions(+), 34 deletions(-) create mode 100644 .changeset/mighty-carrots-decide.md create mode 100644 plugins/search-react/src/components/SearchFilter/types.ts diff --git a/.changeset/mighty-carrots-decide.md b/.changeset/mighty-carrots-decide.md new file mode 100644 index 0000000000..68f210da48 --- /dev/null +++ b/.changeset/mighty-carrots-decide.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-react': minor +--- + +Allow search filters to provide labels and values separately, and not only values diff --git a/packages/app/src/components/search/SearchModal.tsx b/packages/app/src/components/search/SearchModal.tsx index 77c95399d0..04f49d6ddb 100644 --- a/packages/app/src/components/search/SearchModal.tsx +++ b/packages/app/src/components/search/SearchModal.tsx @@ -148,15 +148,18 @@ export const SearchModal = ({ toggleModal }: { toggleModal: () => void }) => { values={async () => { // Return a list of entities which are documented. const { items } = await catalogApi.getEntities({ - fields: ['metadata.name'], + fields: ['metadata.name', 'metadata.title'], filter: { 'metadata.annotations.backstage.io/techdocs-ref': CATALOG_FILTER_EXISTS, }, }); - const names = items.map(entity => entity.metadata.name); - names.sort(); + const names = items.map(entity => ({ + value: entity.metadata.name, + label: entity.metadata.title ?? entity.metadata.name, + })); + names.sort((a, b) => a.label.localeCompare(b.label)); return names; }} /> diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx index 9d45583044..743a08561c 100644 --- a/packages/app/src/components/search/SearchPage.tsx +++ b/packages/app/src/components/search/SearchPage.tsx @@ -97,15 +97,18 @@ const SearchPage = () => { values={async () => { // Return a list of entities which are documented. const { items } = await catalogApi.getEntities({ - fields: ['metadata.name'], + fields: ['metadata.name', 'metadata.title'], filter: { 'metadata.annotations.backstage.io/techdocs-ref': CATALOG_FILTER_EXISTS, }, }); - const names = items.map(entity => entity.metadata.name); - names.sort(); + const names = items.map(entity => ({ + value: entity.metadata.name, + label: entity.metadata.title ?? entity.metadata.name, + })); + names.sort((a, b) => a.label.localeCompare(b.label)); return names; }} /> diff --git a/plugins/search-react/report.api.md b/plugins/search-react/report.api.md index 1e74e16132..f0ec6030fd 100644 --- a/plugins/search-react/report.api.md +++ b/plugins/search-react/report.api.md @@ -63,6 +63,15 @@ export type DefaultResultListItemProps = { toggleModal?: () => void; }; +// @public (undocumented) +export type FilterValue = string | FilterValueWithLabel; + +// @public (undocumented) +export type FilterValueWithLabel = { + value: string; + label: string; +}; + // @public (undocumented) export const HighlightedSearchResultText: ( props: HighlightedSearchResultTextProps, @@ -215,7 +224,7 @@ export type SearchFilterComponentProps = { className?: string; name: string; label?: string; - values?: string[] | ((partial: string) => Promise); + values?: FilterValue[] | ((partial: string) => Promise); defaultValue?: string[] | string | null; valuesDebounceMs?: number; }; diff --git a/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.tsx b/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.tsx index fbdfbb1e4c..f95f0153fe 100644 --- a/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.tsx +++ b/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.tsx @@ -25,6 +25,7 @@ import Autocomplete, { import { useSearch } from '../../context'; import { useAsyncFilterValues, useDefaultFilterValue } from './hooks'; import { SearchFilterComponentProps } from './SearchFilter'; +import { ensureFilterValueWithLabel, FilterValueWithLabel } from './types'; /** * @public @@ -55,7 +56,9 @@ export const AutocompleteFilter = (props: SearchAutocompleteFilterProps) => { const asyncValues = typeof givenValues === 'function' ? givenValues : undefined; const defaultValues = - typeof givenValues === 'function' ? undefined : givenValues; + typeof givenValues === 'function' + ? undefined + : givenValues?.map(v => ensureFilterValueWithLabel(v)); const { value: values, loading } = useAsyncFilterValues( asyncValues, inputValue, @@ -63,19 +66,26 @@ export const AutocompleteFilter = (props: SearchAutocompleteFilterProps) => { valuesDebounceMs, ); const { filters, setFilters } = useSearch(); - const filterValue = - (filters[name] as string | string[] | undefined) || (multiple ? [] : null); + const filterValueWithLabel = ensureFilterValueWithLabel( + filters[name] as string | string[] | undefined, + ); + const filterValue = filterValueWithLabel || (multiple ? [] : null); // Set new filter values on input change. const handleChange = ( _: ChangeEvent<{}>, - newValue: string | string[] | null, + newValue: FilterValueWithLabel | FilterValueWithLabel[] | null, ) => { setFilters(prevState => { const { [name]: filter, ...others } = prevState; if (newValue) { - return { ...others, [name]: newValue }; + return { + ...others, + [name]: Array.isArray(newValue) + ? newValue.map(v => v.value) + : newValue.value, + }; } return { ...others }; }); @@ -94,11 +104,11 @@ export const AutocompleteFilter = (props: SearchAutocompleteFilterProps) => { // Render tags as primary-colored chips. const renderTags = ( - tagValue: string[], + tagValue: FilterValueWithLabel[], getTagProps: AutocompleteGetTagProps, ) => - tagValue.map((option: string, index: number) => ( - + tagValue.map((option, index: number) => ( + )); return ( @@ -113,6 +123,7 @@ export const AutocompleteFilter = (props: SearchAutocompleteFilterProps) => { value={filterValue} onChange={handleChange} onInputChange={(_, newValue) => setInputValue(newValue)} + getOptionLabel={option => option.label} renderInput={renderInput} renderTags={renderTags} /> diff --git a/plugins/search-react/src/components/SearchFilter/SearchFilter.tsx b/plugins/search-react/src/components/SearchFilter/SearchFilter.tsx index e21c8a8697..da96c711c4 100644 --- a/plugins/search-react/src/components/SearchFilter/SearchFilter.tsx +++ b/plugins/search-react/src/components/SearchFilter/SearchFilter.tsx @@ -30,6 +30,7 @@ import { SearchAutocompleteFilterProps, } from './SearchFilter.Autocomplete'; import { useAsyncFilterValues, useDefaultFilterValue } from './hooks'; +import { ensureFilterValueWithLabel, FilterValue } from './types'; const useStyles = makeStyles({ label: { @@ -60,7 +61,7 @@ export type SearchFilterComponentProps = { * input value is provided as an input to allow values to be filtered. This * function is debounced and values cached. */ - values?: string[] | ((partial: string) => Promise); + values?: FilterValue[] | ((partial: string) => Promise); defaultValue?: string[] | string | null; /** * Debounce time in milliseconds, used when values is an async callback. @@ -84,7 +85,7 @@ export const CheckboxFilter = (props: SearchFilterComponentProps) => { const { className, defaultValue, - label, + label: formLabel, name, values: givenValues = [], valuesDebounceMs, @@ -95,7 +96,9 @@ export const CheckboxFilter = (props: SearchFilterComponentProps) => { const asyncValues = typeof givenValues === 'function' ? givenValues : undefined; const defaultValues = - typeof givenValues === 'function' ? undefined : givenValues; + typeof givenValues === 'function' + ? undefined + : givenValues.map(v => ensureFilterValueWithLabel(v)); const { value: values = [], loading } = useAsyncFilterValues( asyncValues, '', @@ -123,21 +126,23 @@ export const CheckboxFilter = (props: SearchFilterComponentProps) => { fullWidth data-testid="search-checkboxfilter-next" > - {label ? {label} : null} - {values.map((value: string) => ( + {!!formLabel && ( + {formLabel} + )} + {values.map(({ value, label }) => ( @@ -164,7 +169,9 @@ export const SelectFilter = (props: SearchFilterComponentProps) => { const asyncValues = typeof givenValues === 'function' ? givenValues : undefined; const defaultValues = - typeof givenValues === 'function' ? undefined : givenValues; + typeof givenValues === 'function' + ? undefined + : givenValues?.map(v => ensureFilterValueWithLabel(v)); const { value: values = [], loading } = useAsyncFilterValues( asyncValues, '', @@ -184,7 +191,7 @@ export const SelectFilter = (props: SearchFilterComponentProps) => { }); }; - const items = [allOption, ...values.map(value => ({ value, label: value }))]; + const items = [allOption, ...values]; return ( { describe('useAsyncFilterValues', () => { it('should immediately return given values when provided', () => { - const givenValues = ['value1', 'value2']; + const givenValues = [ + { value: 'value1', label: 'value 1' }, + { value: 'value2', label: 'value 2' }, + ]; const { result } = renderHook(() => useAsyncFilterValues(undefined, '', givenValues), ); @@ -245,7 +248,10 @@ describe('SearchFilter.hooks', () => { }); it('should return resolved values of provided async function', async () => { - const expectedValues = ['value1', 'value2']; + const expectedValues = [ + { value: 'value1', label: 'value 1' }, + { value: 'value2', label: 'value 2' }, + ]; const asyncFn = () => Promise.resolve(expectedValues); const { result } = renderHook(() => useAsyncFilterValues(asyncFn, '', undefined, 1000), @@ -262,7 +268,10 @@ describe('SearchFilter.hooks', () => { }); it('should debounce method invocation', async () => { - const expectedValues = ['value1', 'value2']; + const expectedValues = [ + { value: 'value1', label: 'value 1' }, + { value: 'value2', label: 'value 2' }, + ]; const asyncFn = jest.fn().mockResolvedValue(expectedValues); renderHook(() => useAsyncFilterValues(asyncFn, '', undefined, 1000)); @@ -307,7 +316,10 @@ describe('SearchFilter.hooks', () => { }); it('should not call provided method more than once when re-rendered with same input', async () => { - const expectedValues = ['value1', 'value2']; + const expectedValues = [ + { value: 'value1', label: 'value 1' }, + { value: 'value2', label: 'value 2' }, + ]; const asyncFn = jest.fn().mockResolvedValue(expectedValues); const { rerender } = renderHook( (props: { inputValue: string } = { inputValue: '' }) => diff --git a/plugins/search-react/src/components/SearchFilter/hooks.ts b/plugins/search-react/src/components/SearchFilter/hooks.ts index 987c85bb7c..48b842b914 100644 --- a/plugins/search-react/src/components/SearchFilter/hooks.ts +++ b/plugins/search-react/src/components/SearchFilter/hooks.ts @@ -14,11 +14,16 @@ * limitations under the License. */ -import { useEffect, useRef } from 'react'; +import { useCallback, useEffect, useRef } from 'react'; import useAsyncFn from 'react-use/esm/useAsyncFn'; import useDebounce from 'react-use/esm/useDebounce'; import { useSearch } from '../../context'; +import { + ensureFilterValueWithLabel, + FilterValue, + FilterValueWithLabel, +} from './types'; /** * Utility hook for either asynchronously loading filter values from a given @@ -27,13 +32,22 @@ import { useSearch } from '../../context'; * @public */ export const useAsyncFilterValues = ( - fn: ((partial: string) => Promise) | undefined, + fn: ((partial: string) => Promise) | undefined, inputValue: string, - defaultValues: string[] = [], + defaultValues: FilterValueWithLabel[] = [], debounce: number = 250, ) => { - const valuesMemo = useRef>>({}); - const definiteFn = fn || (() => Promise.resolve([])); + const valuesMemo = useRef< + Record> + >({}); + const definiteFn = useCallback( + async (partial: string) => { + return ( + (await fn?.(partial))?.map(v => ensureFilterValueWithLabel(v)) || [] + ); + }, + [fn], + ); const [state, callback] = useAsyncFn(definiteFn, [inputValue], { loading: true, diff --git a/plugins/search-react/src/components/SearchFilter/index.ts b/plugins/search-react/src/components/SearchFilter/index.ts index 9f92c7c8e2..d26cd25911 100644 --- a/plugins/search-react/src/components/SearchFilter/index.ts +++ b/plugins/search-react/src/components/SearchFilter/index.ts @@ -21,3 +21,4 @@ export type { SearchFilterWrapperProps, } from './SearchFilter'; export type { SearchAutocompleteFilterProps } from './SearchFilter.Autocomplete'; +export type { FilterValueWithLabel, FilterValue } from './types'; diff --git a/plugins/search-react/src/components/SearchFilter/types.ts b/plugins/search-react/src/components/SearchFilter/types.ts new file mode 100644 index 0000000000..32350abb92 --- /dev/null +++ b/plugins/search-react/src/components/SearchFilter/types.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2025 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. + */ + +/** + * @public + */ +export type FilterValueWithLabel = { value: string; label: string }; + +/** + * @public + */ +export type FilterValue = string | FilterValueWithLabel; + +/** + * Ensure a value is on object form, with a label. + * Accepts undefined, a single value, or an array of values and returns the + * expected result - a filter value/label or an array of such, if any. + */ +export function ensureFilterValueWithLabel( + value: T | T[] | undefined, +): typeof value extends undefined + ? undefined + : typeof value extends ArrayLike + ? FilterValueWithLabel[] + : FilterValueWithLabel { + if (value === undefined) { + return undefined as any; + } + + if (Array.isArray(value)) { + return value.map( + v => ensureFilterValueWithLabel(v) as FilterValueWithLabel, + ) as any; + } + + if (typeof value === 'string') { + return { value, label: value } as FilterValueWithLabel as any; + } + return value as FilterValueWithLabel as any; +} From 08ba4484c312c2aca3d945d6ab5137a31a6cb9fb Mon Sep 17 00:00:00 2001 From: Julius Sudds Date: Mon, 24 Mar 2025 11:04:11 -0500 Subject: [PATCH 04/17] display entity-ref in group profile card Signed-off-by: Julius Sudds --- .changeset/real-rings-smoke.md | 5 +++++ .../Cards/Group/GroupProfile/GroupProfileCard.tsx | 14 ++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 .changeset/real-rings-smoke.md diff --git a/.changeset/real-rings-smoke.md b/.changeset/real-rings-smoke.md new file mode 100644 index 0000000000..832d7e3c6e --- /dev/null +++ b/.changeset/real-rings-smoke.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-org': patch +--- + +display entity-ref in GroupProfileCard so groups can easily determine their Group ID diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx index 5c4eb077b8..04959c1ddc 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx @@ -51,6 +51,7 @@ import CachedIcon from '@material-ui/icons/Cached'; import EditIcon from '@material-ui/icons/Edit'; import EmailIcon from '@material-ui/icons/Email'; import GroupIcon from '@material-ui/icons/Group'; +import PermIdentityIcon from '@material-ui/icons/PermIdentity'; import { LinksGroup } from '../../Meta'; import { useEntityPermission } from '@backstage/plugin-catalog-react/alpha'; import { catalogEntityRefreshPermission } from '@backstage/plugin-catalog-common/alpha'; @@ -202,6 +203,19 @@ export const GroupProfileCard = (props: { secondary="Child Groups" /> + {stringifyEntityRef(group) && ( + + + + + + + + + )} {props?.showLinks && } From 06d82b49dac944e34aee966a4aa36281c4e41346 Mon Sep 17 00:00:00 2001 From: Julius Sudds Date: Mon, 14 Apr 2025 10:53:45 -0500 Subject: [PATCH 05/17] changed text to Entity Ref Signed-off-by: Julius Sudds --- .../Group/GroupProfile/GroupProfileCard.tsx | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx index 04959c1ddc..da94092445 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx @@ -203,19 +203,17 @@ export const GroupProfileCard = (props: { secondary="Child Groups" /> - {stringifyEntityRef(group) && ( - - - - - - - - - )} + + + + + + + + {props?.showLinks && } From 7668881972cd6e74775a55a62716d6f6bb9f0204 Mon Sep 17 00:00:00 2001 From: jona Date: Sun, 20 Apr 2025 13:13:03 +0300 Subject: [PATCH 06/17] =?UTF-8?q?=F0=9F=90=9B=20(useEntityListProvider)=20?= =?UTF-8?q?Adjust=20filter=20handling=20for=20user=20and=20group=20types?= =?UTF-8?q?=20to=20fix=20EntityOwnerPicker=20for=20users/groups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jona Warnecke --- plugins/catalog-react/src/hooks/useEntityListProvider.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx index 1d3afd8d9b..b0a0a24fb1 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx @@ -241,7 +241,13 @@ export const EntityListProvider = ( // based on the requested filters changing. const [{ loading, error }, refresh] = useAsyncFn( async () => { - const compacted = compact(Object.values(requestedFilters)); + const kindValue = + requestedFilters.kind?.value?.toLocaleLowerCase('en-US'); + const effectiveFilters = + kindValue === 'user' || kindValue === 'group' + ? { ...requestedFilters, owners: undefined } + : requestedFilters; + const compacted = compact(Object.values(effectiveFilters)); const queryParams = Object.keys(requestedFilters).reduce( (params, key) => { From 0ca570aa9cf6657cf0a9d21bcc6d88a02e09fb28 Mon Sep 17 00:00:00 2001 From: Jona Warnecke Date: Sun, 20 Apr 2025 17:13:44 +0300 Subject: [PATCH 07/17] =?UTF-8?q?=F0=9F=90=9B=20(useEntityListProvider)=20?= =?UTF-8?q?Adjust=20filter=20handling=20for=20user=20and=20group=20types?= =?UTF-8?q?=20to=20fix=20EntityOwnerPicker=20for=20users/groups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jona Warnecke --- plugins/catalog-react/src/hooks/useEntityListProvider.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx index b0a0a24fb1..560e2ba987 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx @@ -243,11 +243,11 @@ export const EntityListProvider = ( async () => { const kindValue = requestedFilters.kind?.value?.toLocaleLowerCase('en-US'); - const effectiveFilters = + const adjustedFilters = kindValue === 'user' || kindValue === 'group' ? { ...requestedFilters, owners: undefined } : requestedFilters; - const compacted = compact(Object.values(effectiveFilters)); + const compacted = compact(Object.values(adjustedFilters)); const queryParams = Object.keys(requestedFilters).reduce( (params, key) => { From 6d7f0d5475ba8d38ff9668239a95d44ee015e050 Mon Sep 17 00:00:00 2001 From: Jona Warnecke Date: Sun, 20 Apr 2025 17:23:40 +0300 Subject: [PATCH 08/17] =?UTF-8?q?=F0=9F=90=9B=20(useEntityListProvider)=20?= =?UTF-8?q?Adjust=20filter=20handling=20for=20user=20and=20group=20types?= =?UTF-8?q?=20to=20fix=20EntityOwnerPicker=20for=20users/groups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jona Warnecke --- .changeset/pretty-seas-hug.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/pretty-seas-hug.md diff --git a/.changeset/pretty-seas-hug.md b/.changeset/pretty-seas-hug.md new file mode 100644 index 0000000000..e512e1e234 --- /dev/null +++ b/.changeset/pretty-seas-hug.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Add filter handling for user and group types to fix EntityOwnerPicker for users/groups From b7f1d15db2cc7e3339e9d8773b097c054679839e Mon Sep 17 00:00:00 2001 From: Jona-Soerensen <144896291+Jona-Soerensen@users.noreply.github.com> Date: Tue, 22 Apr 2025 14:17:48 +0300 Subject: [PATCH 09/17] Update .changeset/pretty-seas-hug.md Co-authored-by: Vincenzo Scamporlino Signed-off-by: Jona-Soerensen <144896291+Jona-Soerensen@users.noreply.github.com> --- .changeset/pretty-seas-hug.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/pretty-seas-hug.md b/.changeset/pretty-seas-hug.md index e512e1e234..216f9af18f 100644 --- a/.changeset/pretty-seas-hug.md +++ b/.changeset/pretty-seas-hug.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-react': patch --- -Add filter handling for user and group types to fix EntityOwnerPicker for users/groups +Fixed an issue causing entities of kind user and group to be empty when an owner was selected From aac6d44cc48373f4bed368266956cf14f136b37c Mon Sep 17 00:00:00 2001 From: jona Date: Tue, 22 Apr 2025 15:58:35 +0300 Subject: [PATCH 10/17] :white_check_mark: (useEntityListProvider.test) Add tests to omit owners filter for user and group kinds Signed-off-by: Jona --- .../src/hooks/useEntityListProvider.test.tsx | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx index 45ed6079cc..de85fa1042 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx @@ -32,6 +32,7 @@ import { catalogApiRef } from '../api'; import { MockStarredEntitiesApi, starredEntitiesApiRef } from '../apis'; import { EntityKindFilter, + EntityOwnerFilter, EntityTextFilter, EntityTypeFilter, EntityUserFilter, @@ -298,6 +299,48 @@ describe('', () => { expect(result.current.pageInfo).toBeUndefined(); }); + + it('should omit owners filter when kind is "user"', async () => { + const { result } = renderHook(() => useEntityList(), { + wrapper: createWrapper({ pagination }), + }); + + act(() => { + result.current.updateFilters({ + kind: new EntityKindFilter('user', 'User'), + owners: new EntityOwnerFilter(['user:default/guest']), + }); + }); + + await waitFor(() => { + expect(mockCatalogApi.getEntities).toHaveBeenCalled(); + }); + + expect(mockCatalogApi.getEntities).toHaveBeenCalledWith({ + filter: { kind: 'user' }, + }); + }); + + it('should omit owners filter when kind is "group"', async () => { + const { result } = renderHook(() => useEntityList(), { + wrapper: createWrapper({ pagination }), + }); + + act(() => { + result.current.updateFilters({ + kind: new EntityKindFilter('group', 'Group'), + owners: new EntityOwnerFilter(['group:default/team-a']), + }); + }); + + await waitFor(() => { + expect(mockCatalogApi.getEntities).toHaveBeenCalled(); + }); + + expect(mockCatalogApi.getEntities).toHaveBeenCalledWith({ + filter: { kind: 'group' }, + }); + }); }); describe('', () => { @@ -552,6 +595,52 @@ describe('', () => { }); }); }); + + it('should omit owners filter when kind is "user"', async () => { + const { result } = renderHook(() => useEntityList(), { + wrapper: createWrapper({ pagination }), + }); + + act(() => { + result.current.updateFilters({ + kind: new EntityKindFilter('user', 'User'), + owners: new EntityOwnerFilter(['user:default/guest']), + }); + }); + + await waitFor(() => { + expect(mockCatalogApi.queryEntities).toHaveBeenCalled(); + }); + + expect(mockCatalogApi.queryEntities).toHaveBeenCalledWith( + expect.objectContaining({ + filter: { kind: 'user' }, + }), + ); + }); + + it('should omit owners filter when kind is "group"', async () => { + const { result } = renderHook(() => useEntityList(), { + wrapper: createWrapper({ pagination }), + }); + + act(() => { + result.current.updateFilters({ + kind: new EntityKindFilter('group', 'Group'), + owners: new EntityOwnerFilter(['group:default/team-a']), + }); + }); + + await waitFor(() => { + expect(mockCatalogApi.queryEntities).toHaveBeenCalled(); + }); + + expect(mockCatalogApi.queryEntities).toHaveBeenCalledWith( + expect.objectContaining({ + filter: { kind: 'group' }, + }), + ); + }); }); }); @@ -801,4 +890,50 @@ describe(``, () => { expect(result.current.error).toBeDefined(); }); }); + + it('should omit owners filter when kind is "user"', async () => { + const { result } = renderHook(() => useEntityList(), { + wrapper: createWrapper({ pagination }), + }); + + act(() => { + result.current.updateFilters({ + kind: new EntityKindFilter('user', 'User'), + owners: new EntityOwnerFilter(['user:default/guest']), + }); + }); + + await waitFor(() => { + expect(mockCatalogApi.queryEntities).toHaveBeenCalled(); + }); + + expect(mockCatalogApi.queryEntities).toHaveBeenCalledWith( + expect.objectContaining({ + filter: { kind: 'user' }, + }), + ); + }); + + it('should omit owners filter when kind is "group"', async () => { + const { result } = renderHook(() => useEntityList(), { + wrapper: createWrapper({ pagination }), + }); + + act(() => { + result.current.updateFilters({ + kind: new EntityKindFilter('group', 'Group'), + owners: new EntityOwnerFilter(['group:default/team-a']), + }); + }); + + await waitFor(() => { + expect(mockCatalogApi.queryEntities).toHaveBeenCalled(); + }); + + expect(mockCatalogApi.queryEntities).toHaveBeenCalledWith( + expect.objectContaining({ + filter: { kind: 'group' }, + }), + ); + }); }); From 72d019d663cbc19035e82ebecf74b207faacab26 Mon Sep 17 00:00:00 2001 From: Gabriel Dugny Date: Fri, 18 Apr 2025 14:19:31 +0200 Subject: [PATCH 11/17] chore(typos): Fix low-impact typos Signed-off-by: Gabriel Dugny --- .changeset/early-colts-accept.md | 6 +++ .changeset/short-teeth-juggle.md | 49 +++++++++++++++++++ .../src/wiring/BackendInitializer.ts | 2 +- .../entrypoints/auth/DefaultAuthService.ts | 2 +- .../urlReader/lib/GerritUrlReader.test.ts | 2 +- .../report.api.md | 3 ++ .../backend-plugin-api/package.json | 2 +- .../src/features/features.test.ts | 2 +- .../src/loader/CommonJSModuleLoader.ts | 6 +-- .../src/server/frontendRemotesServer.ts | 10 +++- .../src/server/router.ts | 4 +- .../services/definitions/HttpAuthService.ts | 2 +- .../services/definitions/UrlReaderService.ts | 6 +-- packages/canon/src/components/Select/types.ts | 2 +- .../cli/config/jestCachingModuleLoader.js | 4 +- .../migrate/commands/versions/bump.test.ts | 4 +- .../migrate/commands/versions/migrate.test.ts | 8 +-- .../src/modules/start/commands/repo/start.ts | 2 +- .../src/sources/RemoteConfigSource.ts | 2 +- packages/config-loader/src/sources/types.ts | 2 +- .../src/collectEntityPageContents.ts | 2 +- .../src/components/AutoLogout/AutoLogout.tsx | 2 +- .../src/components/Chip/Chip.stories.tsx | 4 +- .../src/hooks/useQueryParamState.ts | 2 +- .../src/wiring/createSpecializedApp.test.tsx | 2 +- .../src/wiring/createSpecializedApp.tsx | 2 +- .../src/wiring/createExtension.test.ts | 6 +-- packages/repo-tools/src/commands/index.ts | 2 +- plugins/api-docs/README-alpha.md | 32 ++++++------ plugins/api-docs/src/alpha.tsx | 12 ++--- .../auth-backend/src/identity/TokenFactory.ts | 2 +- .../useCookieAuthRefresh.tsx | 2 +- .../src/processors/AwsEKSClusterProcessor.ts | 4 +- .../InternalOpenApiDocumentationProvider.ts | 2 +- .../src/index.ts | 2 +- .../src/lib/types.ts | 2 +- .../catalog-info.yaml | 2 +- .../catalog-backend-module-logs/package.json | 2 +- .../src/microsoftGraph/client.test.ts | 4 +- plugins/catalog-graph/README-alpha.md | 10 ++-- plugins/home/README.md | 2 +- plugins/home/src/api/VisitsStorageApi.ts | 2 +- .../home/src/api/VisitsWebStorageApi.test.ts | 2 +- .../Toolkit/Toolkit.stories.tsx | 2 +- plugins/kubernetes-common/src/types.ts | 2 +- plugins/kubernetes-node/src/types/types.ts | 2 +- .../KubernetesAuthProviders.test.ts | 2 +- plugins/org/README-alpha.md | 8 +-- .../src/PermissionClient.test.ts | 2 +- .../src/actions/bitbucketCloud.examples.ts | 2 +- .../src/actions/bitbucketCloudPullRequest.ts | 2 +- .../src/actions/bitbucketServerPullRequest.ts | 2 +- .../src/actions/bitbucket.examples.ts | 2 +- .../src/actions/gerrit.examples.ts | 2 +- .../src/actions/github.examples.test.ts | 2 +- .../src/actions/github.test.ts | 2 +- .../src/actions/gitlab.examples.ts | 2 +- .../src/actions/gitlabMergeRequest.test.ts | 12 ++--- .../src/actions/gitlabMergeRequest.ts | 2 +- .../src/util.test.ts | 2 +- .../actions/builtin/catalog/register.test.ts | 2 +- plugins/scaffolder-node/src/scm/git.test.ts | 6 +-- .../src/hooks/useCustomFieldExtensions.ts | 2 +- .../src/next/components/Stepper/utils.test.ts | 2 +- .../TemplateGroups/TemplateGroups.test.tsx | 2 +- .../components/Workflow/Workflow.test.tsx | 2 +- ...laygroud.tsx => CustomFieldPlayground.tsx} | 2 +- .../TemplateEditorToolbar.tsx | 4 +- .../TemplateFormPreviewer.tsx | 2 +- .../FileBrowser/FileBrowser.test.tsx | 40 +++++++-------- .../components/FileBrowser/FileBrowser.tsx | 4 +- .../MultiEntityPicker.test.tsx | 2 +- .../BitbucketRepoBranchPicker.test.tsx | 2 +- .../GitHubRepoBranchPicker.test.tsx | 2 +- .../BitbucketRepoPicker.test.tsx | 4 +- .../RepoUrlPicker/GithubRepoPicker.test.tsx | 2 +- .../src/engines/ElasticSearchSearchEngine.ts | 2 +- .../src/module.ts | 2 +- .../src/service/CachedEntityLoader.test.ts | 2 +- .../stages/generate/DockerContainerRunner.ts | 2 +- plugins/techdocs-react/src/context.test.tsx | 2 +- .../Grids/EntityListDocsGrid.test.tsx | 2 +- .../TechDocsReaderPage/context.test.tsx | 2 +- 83 files changed, 218 insertions(+), 150 deletions(-) create mode 100644 .changeset/early-colts-accept.md create mode 100644 .changeset/short-teeth-juggle.md rename plugins/scaffolder/src/alpha/components/TemplateEditorPage/{CustomFieldPlaygroud.tsx => CustomFieldPlayground.tsx} (99%) diff --git a/.changeset/early-colts-accept.md b/.changeset/early-colts-accept.md new file mode 100644 index 0000000000..ff583144f0 --- /dev/null +++ b/.changeset/early-colts-accept.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-dynamic-feature-service': patch +--- + +Fixed various typos. +`FrontendRemoteResolver`'s misspelled `getAdditionaRemoteInfo` has been deprecated. Use the correct spelling `getAdditionalRemoteInfo` instead. diff --git a/.changeset/short-teeth-juggle.md b/.changeset/short-teeth-juggle.md new file mode 100644 index 0000000000..e0f1073db3 --- /dev/null +++ b/.changeset/short-teeth-juggle.md @@ -0,0 +1,49 @@ +--- +'@backstage/plugin-scaffolder-backend-module-bitbucket-server': patch +'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch +'@backstage/plugin-catalog-backend-module-backstage-openapi': patch +'@backstage/plugin-scaffolder-backend-module-bitbucket': patch +'@backstage/plugin-search-backend-module-elasticsearch': patch +'@backstage/plugin-catalog-backend-module-github-org': patch +'@backstage/plugin-scaffolder-backend-module-gerrit': patch +'@backstage/plugin-scaffolder-backend-module-github': patch +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +'@backstage/plugin-catalog-backend-module-msgraph': patch +'@backstage/plugin-catalog-backend-module-gitlab': patch +'@backstage/plugin-catalog-backend-module-logs': patch +'@backstage/plugin-catalog-backend-module-aws': patch +'@backstage/frontend-plugin-api': patch +'@backstage/backend-plugin-api': patch +'@backstage/plugin-kubernetes-backend': patch +'@backstage/plugin-kubernetes-cluster': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/backend-defaults': patch +'@backstage/frontend-app-api': patch +'@backstage/plugin-kubernetes-common': patch +'@backstage/plugin-permission-common': patch +'@backstage/backend-app-api': patch +'@backstage/core-compat-api': patch +'@backstage/core-components': patch +'@backstage/plugin-kubernetes-react': patch +'@backstage/plugin-scaffolder-react': patch +'@backstage/plugin-techdocs-backend': patch +'@backstage/plugin-kubernetes-node': patch +'@backstage/plugin-scaffolder-node': patch +'@backstage/config-loader': patch +'@backstage/plugin-techdocs-react': patch +'@backstage/plugin-catalog-graph': patch +'@backstage/plugin-techdocs-node': patch +'@backstage/plugin-auth-backend': patch +'@backstage/repo-tools': patch +'@backstage/plugin-auth-react': patch +'@backstage/plugin-kubernetes': patch +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-api-docs': patch +'@backstage/plugin-techdocs': patch +'@backstage/canon': patch +'@backstage/cli': patch +'@backstage/plugin-home': patch +'@backstage/plugin-org': patch +--- + +Removed various typos diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts index f521966d4d..84f8393409 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts @@ -472,7 +472,7 @@ export class BackendInitializer { const rootLifecycleService = await this.#getRootLifecycleImpl(); - // Root services like the health one need to immediatelly be notified of the shutdown + // Root services like the health one need to immediately be notified of the shutdown await rootLifecycleService.beforeShutdown(); // Get all plugins. diff --git a/packages/backend-defaults/src/entrypoints/auth/DefaultAuthService.ts b/packages/backend-defaults/src/entrypoints/auth/DefaultAuthService.ts index 189976ed7d..d8cb60e8b5 100644 --- a/packages/backend-defaults/src/entrypoints/auth/DefaultAuthService.ts +++ b/packages/backend-defaults/src/entrypoints/auth/DefaultAuthService.ts @@ -151,7 +151,7 @@ export class DefaultAuthService implements AuthService { } // check whether a plugin support the new auth system - // by checking the public keys endpoint existance. + // by checking the public keys endpoint existence. switch (type) { // TODO: Check whether the principal is ourselves case 'service': diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GerritUrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GerritUrlReader.test.ts index 64feeca52b..246c07631b 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GerritUrlReader.test.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GerritUrlReader.test.ts @@ -412,7 +412,7 @@ describe.skip('GerritUrlReader', () => { gerritProcessor.readTree(shaTreeUrl, { etag: sha }), ).rejects.toThrow(NotModifiedError); }); - it('can fetch files for a specifc sha.', async () => { + it('can fetch files for a specific sha.', async () => { const response = await gerritProcessorWithGitiles.readTree( `https://gerrit.com/gitiles/app/web/+/${sha}/`, ); diff --git a/packages/backend-dynamic-feature-service/report.api.md b/packages/backend-dynamic-feature-service/report.api.md index 8aa1470e60..5664b7b76c 100644 --- a/packages/backend-dynamic-feature-service/report.api.md +++ b/packages/backend-dynamic-feature-service/report.api.md @@ -230,6 +230,9 @@ export type FrontendRemoteResolver = { getRemoteEntryType?: ( manifestContent: JsonObject, ) => 'manifest' | 'javascript'; + getAdditionalRemoteInfo?: ( + manifestContent: JsonObject, + ) => AdditionalRemoteInfo; getAdditionaRemoteInfo?: ( manifestContent: JsonObject, ) => AdditionalRemoteInfo; diff --git a/packages/backend-dynamic-feature-service/src/features/__fixtures__/node_modules/@backstage/backend-plugin-api/package.json b/packages/backend-dynamic-feature-service/src/features/__fixtures__/node_modules/@backstage/backend-plugin-api/package.json index 7b2e68e264..47560e9643 100644 --- a/packages/backend-dynamic-feature-service/src/features/__fixtures__/node_modules/@backstage/backend-plugin-api/package.json +++ b/packages/backend-dynamic-feature-service/src/features/__fixtures__/node_modules/@backstage/backend-plugin-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-plugin-api", "version": "0.0.0", - "description": "dummy backstage package that should be skipped by the ComonJSLoduleLoader", + "description": "dummy backstage package that should be skipped by the CommonJSModuleLoader", "main": "index.js", "dependencies": {} } diff --git a/packages/backend-dynamic-feature-service/src/features/features.test.ts b/packages/backend-dynamic-feature-service/src/features/features.test.ts index eb2901340c..99495cf1b0 100644 --- a/packages/backend-dynamic-feature-service/src/features/features.test.ts +++ b/packages/backend-dynamic-feature-service/src/features/features.test.ts @@ -558,7 +558,7 @@ Require stack: for: () => ({ assetsPathFromPackage: 'dist-alternate', getRemoteEntryType: () => 'javascript', - getAdditionaRemoteInfo: manifest => ({ + getAdditionalRemoteInfo: manifest => ({ type: (manifest as any).metaData.remoteEntry.type, }), overrideExposedModules: exposedModules => diff --git a/packages/backend-dynamic-feature-service/src/loader/CommonJSModuleLoader.ts b/packages/backend-dynamic-feature-service/src/loader/CommonJSModuleLoader.ts index 50a6028422..28203a2019 100644 --- a/packages/backend-dynamic-feature-service/src/loader/CommonJSModuleLoader.ts +++ b/packages/backend-dynamic-feature-service/src/loader/CommonJSModuleLoader.ts @@ -68,7 +68,7 @@ export class CommonJSModuleLoader implements ModuleLoader { return filtered; }; - // The whole piece of code below is a way to accomodate the limitations of + // The whole piece of code below is a way to accommodate the limitations of // the current `resolvePackagePath` implementation, which cannot be provided // some custom locations where it should find the assets of some given packages. // @@ -104,7 +104,7 @@ export class CommonJSModuleLoader implements ModuleLoader { mod?.path && !dynamicPluginsPaths.some(p => mod.path.startsWith(p)); - // If not, we don't need the dedicated specfic case below. + // If not, we don't need the dedicated specific case below. if (!resolvingPackageJsonFromBackstageApplication) { throw errorToThrow; } @@ -133,7 +133,7 @@ export class CommonJSModuleLoader implements ModuleLoader { } // If a custom resolution is provided, use it. - // This allows accomodating alternate ways to package dynamic plugins: + // This allows accommodating alternate ways to package dynamic plugins: // static plugin package wrapped inside a distinct dynamic plugin package for example. if (this.options.customResolveDynamicPackage) { const resolvedPath = this.options.customResolveDynamicPackage( diff --git a/packages/backend-dynamic-feature-service/src/server/frontendRemotesServer.ts b/packages/backend-dynamic-feature-service/src/server/frontendRemotesServer.ts index fa7e574bf3..f1a245b1ac 100644 --- a/packages/backend-dynamic-feature-service/src/server/frontendRemotesServer.ts +++ b/packages/backend-dynamic-feature-service/src/server/frontendRemotesServer.ts @@ -37,7 +37,7 @@ export type AdditionalRemoteInfo = Omit; * */ export type FrontendRemoteResolver = { /** - * Relative path to the module federation assets folder from thr root folder of the plugin package. + * Relative path to the module federation assets folder from the root folder of the plugin package. * Default value is `dist`. */ assetsPathFromPackage?: string; @@ -59,6 +59,14 @@ export type FrontendRemoteResolver = { /** * Additional module federation fields, which might be required if the remote entry type is 'javascript'. */ + getAdditionalRemoteInfo?: ( + manifestContent: JsonObject, + ) => AdditionalRemoteInfo; + + /** + * Additional module federation fields, which might be required if the remote entry type is 'javascript'. + * @deprecated Use `getAdditionalRemoteInfo` instead. + */ getAdditionaRemoteInfo?: ( manifestContent: JsonObject, ) => AdditionalRemoteInfo; diff --git a/packages/backend-dynamic-feature-service/src/server/router.ts b/packages/backend-dynamic-feature-service/src/server/router.ts index 4233147069..61c97797e7 100644 --- a/packages/backend-dynamic-feature-service/src/server/router.ts +++ b/packages/backend-dynamic-feature-service/src/server/router.ts @@ -121,8 +121,10 @@ export async function createRouter({ } const getAdditionalRemoteInfo = + providedResolver?.getAdditionalRemoteInfo ?? providedResolver?.getAdditionaRemoteInfo ?? - defaultResolver.getAdditionaRemoteInfo; + defaultResolver.getAdditionalRemoteInfo ?? + defaultResolver?.getAdditionaRemoteInfo; const getRemoteEntryType = providedResolver?.getRemoteEntryType ?? defaultResolver.getRemoteEntryType; diff --git a/packages/backend-plugin-api/src/services/definitions/HttpAuthService.ts b/packages/backend-plugin-api/src/services/definitions/HttpAuthService.ts index 152bb205de..28eb214f70 100644 --- a/packages/backend-plugin-api/src/services/definitions/HttpAuthService.ts +++ b/packages/backend-plugin-api/src/services/definitions/HttpAuthService.ts @@ -93,7 +93,7 @@ export interface HttpAuthService { * * Normally you do not have to specify this option, because the default * behavior is to extract the credentials from the request that - * corresponded to the given respnse. + * corresponded to the given response. */ credentials?: BackstageCredentials; }, diff --git a/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts b/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts index 9b327cfbf0..e462979707 100644 --- a/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts +++ b/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts @@ -110,7 +110,7 @@ export type UrlReaderServiceReadUrlOptions = { * @remarks * * By default all URL Readers will use the integrations config which is supplied - * when creating the Readers. Sometimes it might be desireable to use the already + * when creating the Readers. Sometimes it might be desirable to use the already * created URLReaders but with a different token, maybe that's supplied by the user * at runtime. */ @@ -209,7 +209,7 @@ export type UrlReaderServiceReadTreeOptions = { * @remarks * * By default all URL Readers will use the integrations config which is supplied - * when creating the Readers. Sometimes it might be desireable to use the already + * when creating the Readers. Sometimes it might be desirable to use the already * created URLReaders but with a different token, maybe that's supplied by the user * at runtime. */ @@ -323,7 +323,7 @@ export type UrlReaderServiceSearchOptions = { * @remarks * * By default all URL Readers will use the integrations config which is supplied - * when creating the Readers. Sometimes it might be desireable to use the already + * when creating the Readers. Sometimes it might be desirable to use the already * created URLReaders but with a different token, maybe that's supplied by the user * at runtime. */ diff --git a/packages/canon/src/components/Select/types.ts b/packages/canon/src/components/Select/types.ts index adf8568b98..955eb14c5e 100644 --- a/packages/canon/src/components/Select/types.ts +++ b/packages/canon/src/components/Select/types.ts @@ -84,7 +84,7 @@ export interface SelectProps { onValueChange?: (value: string) => void; /** - * Callbak that is called when the select field is opened or closed + * Callback that is called when the select field is opened or closed */ onOpenChange?: (open: boolean) => void; diff --git a/packages/cli/config/jestCachingModuleLoader.js b/packages/cli/config/jestCachingModuleLoader.js index dd22f25f6f..b2b6a4f8ae 100644 --- a/packages/cli/config/jestCachingModuleLoader.js +++ b/packages/cli/config/jestCachingModuleLoader.js @@ -19,8 +19,8 @@ const { default: JestRuntime } = require('jest-runtime'); const scriptTransformCache = new Map(); module.exports = class CachingJestRuntime extends JestRuntime { - constructor(config, ...restAgs) { - super(config, ...restAgs); + constructor(config, ...restArgs) { + super(config, ...restArgs); this.allowLoadAsEsm = config.extensionsToTreatAsEsm.includes('.mts'); } diff --git a/packages/cli/src/modules/migrate/commands/versions/bump.test.ts b/packages/cli/src/modules/migrate/commands/versions/bump.test.ts index bce9888842..a309ddb192 100644 --- a/packages/cli/src/modules/migrate/commands/versions/bump.test.ts +++ b/packages/cli/src/modules/migrate/commands/versions/bump.test.ts @@ -131,10 +131,10 @@ const lockfileMock = `${HEADER} // Avoid flakes by comparing sorted log lines. File system access is async, which leads to the log line order being indeterministic const expectLogsToMatch = ( - recievedLogs: String[], + receivedLogs: String[], expected: String[], ): void => { - expect(recievedLogs.filter(Boolean).sort()).toEqual(expected.sort()); + expect(receivedLogs.filter(Boolean).sort()).toEqual(expected.sort()); }; describe('bump', () => { diff --git a/packages/cli/src/modules/migrate/commands/versions/migrate.test.ts b/packages/cli/src/modules/migrate/commands/versions/migrate.test.ts index 7a0a1269c4..9ff00fd285 100644 --- a/packages/cli/src/modules/migrate/commands/versions/migrate.test.ts +++ b/packages/cli/src/modules/migrate/commands/versions/migrate.test.ts @@ -51,8 +51,8 @@ jest.mock('../../../../lib/run', () => { }; }); -function expectLogsToMatch(recievedLogs: String[], expected: String[]): void { - expect(recievedLogs.filter(Boolean).sort()).toEqual(expected.sort()); +function expectLogsToMatch(receivedLogs: String[], expected: String[]): void { + expect(receivedLogs.filter(Boolean).sort()).toEqual(expected.sort()); } describe('versions:migrate', () => { @@ -169,7 +169,7 @@ describe('versions:migrate', () => { }); }); - it('should replace the occurences of the moved package in files inside the correct package', async () => { + it('should replace the occurrences of the moved package in files inside the correct package', async () => { mockDir.setContent({ 'package.json': JSON.stringify({ workspaces: { @@ -259,7 +259,7 @@ describe('versions:migrate', () => { ); }); - it('should replaces the occurences of changed packages, and is careful', async () => { + it('should replaces the occurrences of changed packages, and is careful', async () => { mockDir.setContent({ 'package.json': JSON.stringify({ workspaces: { diff --git a/packages/cli/src/modules/start/commands/repo/start.ts b/packages/cli/src/modules/start/commands/repo/start.ts index b0bb8f5c01..8905aec4b1 100644 --- a/packages/cli/src/modules/start/commands/repo/start.ts +++ b/packages/cli/src/modules/start/commands/repo/start.ts @@ -69,7 +69,7 @@ export async function findTargetPackages( const packages = await PackageGraph.listTargetPackages(); - // Priorotize plugin options, so that the `start` script can contain a list of packages, + // Prioritize plugin options, so that the `start` script can contain a list of packages, // but make them easy to override by running for example `yarn start --plugin catalog` for (const pluginId of pluginIds) { const matchingPackages = packages.filter(pkg => { diff --git a/packages/config-loader/src/sources/RemoteConfigSource.ts b/packages/config-loader/src/sources/RemoteConfigSource.ts index d8fa07d708..5fd086f8da 100644 --- a/packages/config-loader/src/sources/RemoteConfigSource.ts +++ b/packages/config-loader/src/sources/RemoteConfigSource.ts @@ -147,7 +147,7 @@ export class RemoteConfigSource implements ConfigSource { if (rawData === undefined) { /** * This error message is/was coupled to the implementation and with refactoring it is no longer truly accurate - * This behavior is also inconsistent with {@link FileConfigSource}, which doesn't error on unparseable or empty + * This behavior is also inconsistent with {@link FileConfigSource}, which doesn't error on unparsable or empty * content * * Preserving to not make a breaking change diff --git a/packages/config-loader/src/sources/types.ts b/packages/config-loader/src/sources/types.ts index fedce213c5..a8cfccc3c6 100644 --- a/packages/config-loader/src/sources/types.ts +++ b/packages/config-loader/src/sources/types.ts @@ -78,7 +78,7 @@ export interface ConfigSource { } /** - * A custom function to be used for substitution withing configuration files. + * A custom function to be used for substitution within configuration files. * * @remarks * diff --git a/packages/core-compat-api/src/collectEntityPageContents.ts b/packages/core-compat-api/src/collectEntityPageContents.ts index 6748c8939a..582dee38f9 100644 --- a/packages/core-compat-api/src/collectEntityPageContents.ts +++ b/packages/core-compat-api/src/collectEntityPageContents.ts @@ -30,7 +30,7 @@ import { normalizeRoutePath } from './normalizeRoutePath'; const ENTITY_SWITCH_KEY = 'core.backstage.entitySwitch'; const ENTITY_ROUTE_KEY = 'plugin.catalog.entityLayoutRoute'; -// Placeholder to make sure internal types here are consitent +// Placeholder to make sure internal types here are consistent type Entity = { apiVersion: string; kind: string }; type EntityFilter = (entity: Entity, ctx: { apis: ApiHolder }) => boolean; diff --git a/packages/core-components/src/components/AutoLogout/AutoLogout.tsx b/packages/core-components/src/components/AutoLogout/AutoLogout.tsx index b328705e51..932ac7432b 100644 --- a/packages/core-components/src/components/AutoLogout/AutoLogout.tsx +++ b/packages/core-components/src/components/AutoLogout/AutoLogout.tsx @@ -42,7 +42,7 @@ type AutoLogoutTrackableEvent = EventsType; export type AutoLogoutProps = { /** * Enable/disable the AutoLogoutMechanism. - * defauls to true. + * defaults to true. */ enabled?: boolean; /** diff --git a/packages/core-components/src/components/Chip/Chip.stories.tsx b/packages/core-components/src/components/Chip/Chip.stories.tsx index 4423a2ca58..a20a6d5699 100644 --- a/packages/core-components/src/components/Chip/Chip.stories.tsx +++ b/packages/core-components/src/components/Chip/Chip.stories.tsx @@ -58,7 +58,7 @@ export default { export const Default = (args: ChipProps) => ; Default.args = defaultArgs; -export const Deleteable = (args: ChipProps) => ( +export const Deletable = (args: ChipProps) => ( ({})} /> ); -Deleteable.args = defaultArgs; +Deletable.args = defaultArgs; diff --git a/packages/core-components/src/hooks/useQueryParamState.ts b/packages/core-components/src/hooks/useQueryParamState.ts index 16cd9e18c8..498cd7143e 100644 --- a/packages/core-components/src/hooks/useQueryParamState.ts +++ b/packages/core-components/src/hooks/useQueryParamState.ts @@ -58,7 +58,7 @@ type SetQueryParams = (params: T) => void; export function useQueryParamState( stateName: string, - /** @deprecated Don't configure a custom debouceTime */ + /** @deprecated Don't configure a custom debounceTime */ debounceTime: number = 250, ): [T | undefined, SetQueryParams] { const [searchParams, setSearchParams] = useSearchParams(); diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx index 96450f06fb..a921f9a08c 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx @@ -244,7 +244,7 @@ describe('createSpecializedApp', () => { `); }); - it('should intitialize the APIs in the correct order to allow for overrides', () => { + it('should initialize the APIs in the correct order to allow for overrides', () => { const mockAnalyticsApi = jest.fn(() => ({ captureEvent: jest.fn() })); const app = createSpecializedApp({ diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx index 21fde6547b..1a4c7dbc78 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx @@ -195,7 +195,7 @@ class RouteResolutionApiProxy implements RouteResolutionApi { /** * Creates an empty app without any default features. This is a low-level API is - * intended for use in tests or specialized setups. Typically wou want to use + * intended for use in tests or specialized setups. Typically you want to use * `createApp` from `@backstage/frontend-defaults` instead. * * @public diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 07a1bfcdb0..00dd981e26 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -669,7 +669,7 @@ describe('createExtension', () => { }, }); - const overriden = testExtension.override({ + const overridden = testExtension.override({ config: { schema: { bar: z => z.string().default('hello'), @@ -684,12 +684,12 @@ describe('createExtension', () => { }, }); - expect(createExtensionTester(overriden).get(stringDataRef)).toBe( + expect(createExtensionTester(overridden).get(stringDataRef)).toBe( 'foo-boom-override-hello', ); expect( - createExtensionTester(overriden, { + createExtensionTester(overridden, { config: { foo: 'hello', bar: 'world' }, }).get(stringDataRef), ).toBe('foo-hello-override-world'); diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index b737d918bd..7515feb8a4 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -187,7 +187,7 @@ export function registerCommands(program: Command) { ) .option( '-o, --omit-messages ', - 'select some message code to be omited on the API Extractor (comma separated values i.e ae-cyclic-inherit-doc,ae-missing-getter )', + 'select some message code to be omitted on the API Extractor (comma separated values i.e ae-cyclic-inherit-doc,ae-missing-getter )', ) .option( '--validate-release-tags', diff --git a/plugins/api-docs/README-alpha.md b/plugins/api-docs/README-alpha.md index ddff429135..5cdd386b89 100644 --- a/plugins/api-docs/README-alpha.md +++ b/plugins/api-docs/README-alpha.md @@ -77,12 +77,12 @@ To link that a component provides or consumes an API, see the [`providesApis`](h # Shows a table of components that provides a particular api - entity-card:api-docs/providing-components: config: - # Presenting the card ony for entites of kind api + # Presenting the card only for entities of kind api filter: kind:api # Shows a table of components that consumes a particular api - entity-card:api-docs/consuming-components: config: - # Presenting the card ony for entites of kind api + # Presenting the card only for entities of kind api filter: kind:api # Enabling some contents # The contents will be displayed in the same order it appears in this setting list @@ -285,7 +285,7 @@ export default createFrontendModule({ pluginId: 'api-docs', extensions: [ createPageExtension({ - // Ommitting name since we are overriding a plugin index page + // Omitting name since we are overriding a plugin index page // It's up to you whether to use the original default path or not, but links that are hardcoded to the default path won't work if you change it defaultPath: '/api-docs', // Associating the page with a different route ref may result in the sidebar item or external plugin route pointing to an unreachable page @@ -370,7 +370,7 @@ export default createFrontendModule({ createEntityCardExtension({ // Name is necessary so the system knows that this extension will override the default 'has-apis' entity card extension provided by the 'api-docs' plugin name: 'has-apis', - // Returing a custom card component + // Returning a custom card component loader: () => import('./components').then(m => ), }), @@ -443,7 +443,7 @@ export default createFrontendModule({ createEntityCardExtension({ // Name is necessary so the system knows that this extension will override the default 'definition' entity card extension provided by the 'api-docs' plugin name: 'definition', - // Returing a custom card component + // Returning a custom card component loader: () => import('./components').then(m => ), }), @@ -516,7 +516,7 @@ export default createFrontendModule({ createEntityCardExtension({ // Name is necessary so the system knows that this extension will override the default 'provided-apis' entity card extension provided by the 'api-docs' plugin name: 'provided-apis', - // Returing a custom card component + // Returning a custom card component loader: () => import('./components').then(m => ), }), @@ -589,7 +589,7 @@ export default createFrontendModule({ createEntityCardExtension({ // Name is necessary so the system knows that this extension will override the default 'consumed-apis' entity card extension provided by the 'api-docs' plugin name: 'consumed-apis', - // Returing a custom card component + // Returning a custom card component loader: () => import('./components').then(m => ), }), @@ -662,7 +662,7 @@ export default createFrontendModule({ createEntityCardExtension({ // Name is necessary so the system knows that this extension will override the default 'providing-components' entity card extension provided by the 'api-docs' plugin name: 'providing-components', - // Returing a custom card component + // Returning a custom card component loader: () => import('./components').then(m => ( @@ -737,7 +737,7 @@ export default createFrontendModule({ createEntityCardExtension({ // Name is necessary so the system knows that this extension will override the default 'consuming-components' entity card extension provided by the 'api-docs' plugin name: 'consuming-components', - // Returing a custom card component + // Returning a custom card component loader: () => import('./components').then(m => ( @@ -906,7 +906,7 @@ export default createFrontendModule({ createEntityContentExtension({ // Name is necessary so the system knows that this extension will override the default 'apis' entity content extension provided by the 'api-docs' plugin name: 'apis', - // Returing a custom content component + // Returning a custom content component loader: () => import('./components').then(m => ), }), @@ -935,7 +935,7 @@ This is an example with a made-up renderer for SQL schemas: ```tsx import { createFrontendModule, - createApiExtenion, + createApiExtension, createApiFactory, } from '@backstage/frontend-plugin-api'; import { ApiEntity } from '@backstage/catalog-model'; @@ -949,7 +949,7 @@ import { SqlRenderer } from '...'; export default createFrontendModule({ pluginId: 'api-docs', extensions: [ - createApiExtenion({ + createApiExtension({ factory: createApiFactory({ api: apiDocsConfigRef, deps: {}, @@ -989,7 +989,7 @@ Override the config api to configure a [`requestInterceptor` for Swagger UI](htt ```tsx import { createFrontendModule, - createApiExtenion, + createApiExtension, createApiFactory, } from '@backstage/frontend-plugin-api'; import { @@ -1002,7 +1002,7 @@ import { ApiEntity } from '@backstage/catalog-model'; export default createFrontendModule({ pluginId: 'api-docs', extensions: [ - createApiExtenion({ + createApiExtension({ factory: createApiFactory({ api: apiDocsConfigRef, deps: {}, @@ -1051,7 +1051,7 @@ If you want to limit the HTTP methods available for the `Try It Out` feature of ```tsx import { createFrontendModule, - createApiExtenion, + createApiExtension, createApiFactory, } from '@backstage/frontend-plugin-api'; import { @@ -1064,7 +1064,7 @@ import { ApiEntity } from '@backstage/catalog-model'; export default createFrontendModule({ pluginId: 'api-docs', extensions: [ - createApiExtenion({ + createApiExtension({ factory: createApiFactory({ api: apiDocsConfigRef, deps: {}, diff --git a/plugins/api-docs/src/alpha.tsx b/plugins/api-docs/src/alpha.tsx index f85df511ec..ca7a7759e6 100644 --- a/plugins/api-docs/src/alpha.tsx +++ b/plugins/api-docs/src/alpha.tsx @@ -74,7 +74,7 @@ const apiDocsConfigApi = ApiBlueprint.make({ const apiDocsExplorerPage = PageBlueprint.makeWithOverrides({ config: { schema: { - // Ommiting columns and actions for now as their types are too complex to map to zod + // Omitting columns and actions for now as their types are too complex to map to zod initiallySelectedFilter: z => z.enum(['owned', 'starred', 'all']).optional(), }, @@ -98,7 +98,7 @@ const apiDocsExplorerPage = PageBlueprint.makeWithOverrides({ const apiDocsHasApisEntityCard = EntityCardBlueprint.make({ name: 'has-apis', params: { - // Ommiting configSchema for now + // Omitting configSchema for now // We are skipping variants and columns are too complex to map to zod // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 filter: entity => { @@ -132,7 +132,7 @@ const apiDocsDefinitionEntityCard = EntityCardBlueprint.make({ const apiDocsConsumedApisEntityCard = EntityCardBlueprint.make({ name: 'consumed-apis', params: { - // Ommiting configSchema for now + // Omitting configSchema for now // We are skipping variants and columns are too complex to map to zod // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 filter: 'kind:component', @@ -146,7 +146,7 @@ const apiDocsConsumedApisEntityCard = EntityCardBlueprint.make({ const apiDocsProvidedApisEntityCard = EntityCardBlueprint.make({ name: 'provided-apis', params: { - // Ommiting configSchema for now + // Omitting configSchema for now // We are skipping variants and columns are too complex to map to zod // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 filter: 'kind:component', @@ -160,7 +160,7 @@ const apiDocsProvidedApisEntityCard = EntityCardBlueprint.make({ const apiDocsConsumingComponentsEntityCard = EntityCardBlueprint.make({ name: 'consuming-components', params: { - // Ommiting configSchema for now + // Omitting configSchema for now // We are skipping variants // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 filter: 'kind:api', @@ -174,7 +174,7 @@ const apiDocsConsumingComponentsEntityCard = EntityCardBlueprint.make({ const apiDocsProvidingComponentsEntityCard = EntityCardBlueprint.make({ name: 'providing-components', params: { - // Ommiting configSchema for now + // Omitting configSchema for now // We are skipping variants // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 filter: 'kind:api', diff --git a/plugins/auth-backend/src/identity/TokenFactory.ts b/plugins/auth-backend/src/identity/TokenFactory.ts index a1643cdbe3..9c6f0c3793 100644 --- a/plugins/auth-backend/src/identity/TokenFactory.ts +++ b/plugins/auth-backend/src/identity/TokenFactory.ts @@ -52,7 +52,7 @@ export interface BackstageTokenPayload { sub: string; /** - * The entity refs that the user claims ownership througg + * The entity refs that the user claims ownership through */ ent: string[]; diff --git a/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.tsx b/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.tsx index a6860ad185..59c32c1f6c 100644 --- a/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.tsx +++ b/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.tsx @@ -57,7 +57,7 @@ export function useCookieAuthRefresh(options: { if (!response.ok) { // If we get a 404 from the cookie endpoint we assume that it does not // exist and cookie auth is not needed. For all active tabs we don't - // schedule another refresh for the forseeable future, but new tabs will + // schedule another refresh for the foreseeable future, but new tabs will // still check if cookie auth has been added to the deployment. // TODO(Rugvip): Once the legacy backend system is no longer supported we should remove this check if (response.status === 404) { diff --git a/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.ts b/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.ts index a5f5ce2f6c..de4473a1bc 100644 --- a/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.ts +++ b/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.ts @@ -49,10 +49,10 @@ export class AwsEKSClusterProcessor implements CatalogProcessor { clusterEntityTransformer?: EksClusterEntityTransformer; }, ): AwsEKSClusterProcessor { - const awsCredentaislManager = + const awsCredentialsManager = DefaultAwsCredentialsManager.fromConfig(configRoot); return new AwsEKSClusterProcessor({ - credentialsManager: awsCredentaislManager, + credentialsManager: awsCredentialsManager, ...options, }); } diff --git a/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts b/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts index bc2a39d5bf..4024cb1318 100644 --- a/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts +++ b/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts @@ -295,7 +295,7 @@ export class InternalOpenApiDocumentationProvider implements EntityProvider { // Overwrite baseConfig with options from config file. const mergedConfig = lodash.merge(baseConfig, configToMerge); - // Overwite mergedConfig with requiredConfig (i.e., spec.type and spec.definition) to avoid bad configuration. + // Overwrite mergedConfig with requiredConfig (i.e., spec.type and spec.definition) to avoid bad configuration. const documentationEntity = lodash.merge( mergedConfig, requiredConfig, diff --git a/plugins/catalog-backend-module-github-org/src/index.ts b/plugins/catalog-backend-module-github-org/src/index.ts index 03d22b1a79..3c548c4bdb 100644 --- a/plugins/catalog-backend-module-github-org/src/index.ts +++ b/plugins/catalog-backend-module-github-org/src/index.ts @@ -26,7 +26,7 @@ export { } from './module'; /** * - * TODO(djamaile): GithubMultiOrgEntityProvider should be mirgated over to this module. + * TODO(djamaile): GithubMultiOrgEntityProvider should be migrated over to this module. * Afterwards, mark it as deprecated in catalog-backend-module-github and export them there from this module. */ export { diff --git a/plugins/catalog-backend-module-gitlab/src/lib/types.ts b/plugins/catalog-backend-module-gitlab/src/lib/types.ts index 864eca7dfe..23b03ec366 100644 --- a/plugins/catalog-backend-module-gitlab/src/lib/types.ts +++ b/plugins/catalog-backend-module-gitlab/src/lib/types.ts @@ -256,7 +256,7 @@ export type GitlabProviderConfig = { membership?: boolean; /** - * Optional comma seperated list of topics to filter projects by, as specified in the GitLab API documentation: + * Optional comma separated list of topics to filter projects by, as specified in the GitLab API documentation: * https://docs.gitlab.com/api/projects/#list-projects */ topics?: string; diff --git a/plugins/catalog-backend-module-logs/catalog-info.yaml b/plugins/catalog-backend-module-logs/catalog-info.yaml index f2223174c3..f2dd90ae96 100644 --- a/plugins/catalog-backend-module-logs/catalog-info.yaml +++ b/plugins/catalog-backend-module-logs/catalog-info.yaml @@ -3,7 +3,7 @@ kind: Component metadata: name: backstage-plugin-catalog-backend-module-logs title: '@backstage/plugin-catalog-backend-module-logs' - description: A module that subscribes to catalog releated events and logs them. + description: A module that subscribes to catalog related events and logs them. spec: lifecycle: experimental type: backstage-backend-plugin-module diff --git a/plugins/catalog-backend-module-logs/package.json b/plugins/catalog-backend-module-logs/package.json index 536b459ebd..b473998613 100644 --- a/plugins/catalog-backend-module-logs/package.json +++ b/plugins/catalog-backend-module-logs/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-logs", "version": "0.1.10-next.0", - "description": "A module that subscribes to catalog releated events and logs them.", + "description": "A module that subscribes to catalog related events and logs them.", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.test.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.test.ts index 624b6e42b0..1f59ee9c93 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.test.ts @@ -326,7 +326,7 @@ describe('MicrosoftGraphClient', () => { it('should load organization', async () => { worker.use( - rest.get('https://example.com/organization/tentant-id', (_, res, ctx) => + rest.get('https://example.com/organization/tenant-id', (_, res, ctx) => res( ctx.status(200), ctx.json({ @@ -336,7 +336,7 @@ describe('MicrosoftGraphClient', () => { ), ); - const organization = await client.getOrganization('tentant-id'); + const organization = await client.getOrganization('tenant-id'); expect(organization).toEqual({ displayName: 'Example' }); }); diff --git a/plugins/catalog-graph/README-alpha.md b/plugins/catalog-graph/README-alpha.md index 06de696dcc..bab9a26fd1 100644 --- a/plugins/catalog-graph/README-alpha.md +++ b/plugins/catalog-graph/README-alpha.md @@ -184,7 +184,7 @@ app: extensions: # this is the extension id and it follows the naming pattern bellow: # /: - # example disbaling the graph card extension + # example disabling the graph card extension - entity-card:catalog-graph/relations: false ``` @@ -212,7 +212,7 @@ export default createFrontendModule({ configSchema: createSchemaFromZod(z => z.object({ filter: z.string().optional(), - // Ommitting the rest of default configs for simplicity in this example + // Omitting the rest of default configs for simplicity in this example }), ), loader: () => @@ -283,7 +283,7 @@ app: extensions: # this is the extension id and it follows the naming pattern bellow: # /: - # example disbaling the graph page extension + # example disabling the graph page extension - page:catalog-graph: false ``` @@ -305,12 +305,12 @@ export default createFrontendModule({ pluginId: 'catalog-graph', extensions: [ createPageExtension({ - // Ommiting name since it is an index page + // Omitting name since it is an index page defaultPath: '/catalog-graph', routeRef: convertLegacyRouteRef(catalogGraphRouteRef), createSchemaFromZod(z => z.object({ path: z.string().default('/catalog-graph') - // Ommitting the rest of default configs for simplicity in this example + // Omitting the rest of default configs for simplicity in this example })), loader: () => import('./components').then(m => ) }) diff --git a/plugins/home/README.md b/plugins/home/README.md index 1e5fd6b2d2..50b422a3dc 100644 --- a/plugins/home/README.md +++ b/plugins/home/README.md @@ -170,7 +170,7 @@ Available home page properties that are used for homepage widgets are: To define settings that the users can change for your component, you should define the `layout` and `settings` properties. The `settings.schema` object should follow [react-jsonschema-form](https://rjsf-team.github.io/react-jsonschema-form/docs/) definition and the type of the schema -must be `object`. As well, the `uiSchema` can be defined if a certain UI style needs to be applied fo any of the defined +must be `object`. As well, the `uiSchema` can be defined if a certain UI style needs to be applied for any of the defined properties. More documentation [here](https://rjsf-team.github.io/react-jsonschema-form/docs/api-reference/uiSchema). If you want to hide the card title, you can do it by setting a `name` and leaving the `title` empty. diff --git a/plugins/home/src/api/VisitsStorageApi.ts b/plugins/home/src/api/VisitsStorageApi.ts index ca19dcbdf1..da3adc285e 100644 --- a/plugins/home/src/api/VisitsStorageApi.ts +++ b/plugins/home/src/api/VisitsStorageApi.ts @@ -125,7 +125,7 @@ export class VisitsStorageApi implements VisitsApi { private async retrieveAll(): Promise> { const storageKey = await this.getStorageKey(); - // Handles for case when snapshot is and is not referenced per storaged type used + // Handles for case when snapshot is and is not referenced per storage type used const snapshot = this.storageApi.snapshot>(storageKey); if (snapshot?.presence !== 'unknown') { return snapshot?.value ?? []; diff --git a/plugins/home/src/api/VisitsWebStorageApi.test.ts b/plugins/home/src/api/VisitsWebStorageApi.test.ts index 8d8669e99e..bcc6543bd0 100644 --- a/plugins/home/src/api/VisitsWebStorageApi.test.ts +++ b/plugins/home/src/api/VisitsWebStorageApi.test.ts @@ -39,7 +39,7 @@ describe('VisitsWebStorageApi.create()', () => { jest.clearAllMocks(); }); - it('instantiates with only identitiyApi', () => { + it('instantiates with only identityApi', () => { const api = VisitsWebStorageApi.create({ identityApi: mockIdentityApi, errorApi: mockErrorApi, diff --git a/plugins/home/src/homePageComponents/Toolkit/Toolkit.stories.tsx b/plugins/home/src/homePageComponents/Toolkit/Toolkit.stories.tsx index 07c5c35758..6858982306 100644 --- a/plugins/home/src/homePageComponents/Toolkit/Toolkit.stories.tsx +++ b/plugins/home/src/homePageComponents/Toolkit/Toolkit.stories.tsx @@ -43,7 +43,7 @@ export const Default = () => { ); }; -export const InAccordian = () => { +export const InAccordion = () => { const ExpandedComponentAccordion = (props: any) => ( ); diff --git a/plugins/kubernetes-common/src/types.ts b/plugins/kubernetes-common/src/types.ts index 5ef91a6d63..8ce3d80409 100644 --- a/plugins/kubernetes-common/src/types.ts +++ b/plugins/kubernetes-common/src/types.ts @@ -72,7 +72,7 @@ export interface ClusterAttributes { */ name: string; /** - * Human-readable name for the cluster, to be dispayed in UIs. + * Human-readable name for the cluster, to be displayed in UIs. */ title?: string; /** diff --git a/plugins/kubernetes-node/src/types/types.ts b/plugins/kubernetes-node/src/types/types.ts index 4b4fabfceb..3789835d52 100644 --- a/plugins/kubernetes-node/src/types/types.ts +++ b/plugins/kubernetes-node/src/types/types.ts @@ -78,7 +78,7 @@ export interface ClusterDetails { */ name: string; /** - * Human-readable name for the cluster, to be dispayed in UIs. + * Human-readable name for the cluster, to be displayed in UIs. */ title?: string; url: string; diff --git a/plugins/kubernetes-react/src/kubernetes-auth-provider/KubernetesAuthProviders.test.ts b/plugins/kubernetes-react/src/kubernetes-auth-provider/KubernetesAuthProviders.test.ts index 5d03dcdae3..2fdd3d9d2c 100644 --- a/plugins/kubernetes-react/src/kubernetes-auth-provider/KubernetesAuthProviders.test.ts +++ b/plugins/kubernetes-react/src/kubernetes-auth-provider/KubernetesAuthProviders.test.ts @@ -85,7 +85,7 @@ describe('KubernetesAuthProviders tests', () => { ); }); - it('returns error for missconfigured oidc authProvider', async () => { + it('returns error for misconfigured oidc authProvider', async () => { await expect( kap.decorateRequestBodyForAuth('oidc.random', requestBody), ).rejects.toThrow( diff --git a/plugins/org/README-alpha.md b/plugins/org/README-alpha.md index 942192252b..30c4bce1c2 100644 --- a/plugins/org/README-alpha.md +++ b/plugins/org/README-alpha.md @@ -130,7 +130,7 @@ export default createFrontendModule({ name: 'group-profile', // By default, this card will show up only for groups filter: 'kind:group' - // Returing a custom card component + // Returning a custom card component loader: () => import('./components').then(m => ), }), @@ -182,7 +182,7 @@ export default createFrontendModule({ name: 'members-list', // By default, this card will show up only for groups filter: 'kind:group' - // Returing a custom card component + // Returning a custom card component loader: () => import('./components').then(m => ), }), @@ -234,7 +234,7 @@ export default createFrontendModule({ name: 'ownership', // By default, this card will show up only for groups or users filter: 'kind:group,user' - // Returing a custom card component + // Returning a custom card component loader: () => import('./components').then(m => ), }), @@ -286,7 +286,7 @@ export default createFrontendModule({ name: 'user-profile', // By default, this card will show up only for groups or users filter: 'kind:user' - // Returing a custom card component + // Returning a custom card component loader: () => import('./components').then(m => ), }), diff --git a/plugins/permission-common/src/PermissionClient.test.ts b/plugins/permission-common/src/PermissionClient.test.ts index 75eb2b1ff3..72707bf98a 100644 --- a/plugins/permission-common/src/PermissionClient.test.ts +++ b/plugins/permission-common/src/PermissionClient.test.ts @@ -310,7 +310,7 @@ describe('PermissionClient', () => { ).rejects.toThrow(/request failed with 401/i); }); - it('should handle reponses with rules with no params', async () => { + it('should handle responses with rules with no params', async () => { mockPolicyDecisionHandler.mockImplementationOnce( (req, res, { json }: RestContext) => { const responses = req.body.items.map( diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.examples.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.examples.ts index 63e4ccb212..56d19dcd8b 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.examples.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.examples.ts @@ -126,7 +126,7 @@ export const examples: TemplateExample[] = [ }, { description: - 'Initializes a Bitbucket Cloud repository with all proporties being set', + 'Initializes a Bitbucket Cloud repository with all properties being set', example: yaml.stringify({ steps: [ { diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.ts index c70089abc2..eba9c74a6a 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.ts @@ -82,7 +82,7 @@ const createPullRequest = async (opts: { data, ); } catch (e) { - throw new Error(`Unable to create pull-reqeusts, ${e}`); + throw new Error(`Unable to create pull-requests, ${e}`); } if (response.status !== 201) { diff --git a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.ts b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.ts index 5b00f15ac2..71c16beaa7 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.ts +++ b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.ts @@ -96,7 +96,7 @@ const createPullRequest = async (opts: { data, ); } catch (e) { - throw new Error(`Unable to create pull-reqeusts, ${e}`); + throw new Error(`Unable to create pull-requests, ${e}`); } if (response.status !== 201) { diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.examples.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.examples.ts index 12f9472398..6248c991f8 100644 --- a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.examples.ts +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.examples.ts @@ -178,7 +178,7 @@ export const examples: TemplateExample[] = [ }, { description: - 'Initializes a Bitbucket repository with all proporties being set', + 'Initializes a Bitbucket repository with all properties being set', example: yaml.stringify({ steps: [ { diff --git a/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.examples.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.examples.ts index 261465b3a0..d6afbcdfce 100644 --- a/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.examples.ts +++ b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.examples.ts @@ -136,7 +136,7 @@ export const examples: TemplateExample[] = [ }, { description: - 'Initializes a Gerrit repository with all proporties being set', + 'Initializes a Gerrit repository with all properties being set', example: yaml.stringify({ steps: [ { diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/github.examples.test.ts index 3c7f27f572..4f3cc9f03e 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.examples.test.ts @@ -121,7 +121,7 @@ describe('publish:github', () => { githubCredentialsProvider, }); - // restore real implmentation + // restore real implementation (entityRefToName as jest.Mock).mockImplementation( realFamiliarizeEntityName, ); diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.test.ts b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts index f2063bd410..f6a67fb398 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts @@ -125,7 +125,7 @@ describe('publish:github', () => { githubCredentialsProvider, }); - // restore real implmentation + // restore real implementation (entityRefToName as jest.Mock).mockImplementation( realFamiliarizeEntityName, ); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.examples.ts index adcc3fdb0b..ea2ff41ec5 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.examples.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.examples.ts @@ -69,7 +69,7 @@ export const examples: TemplateExample[] = [ }), }, { - description: 'Initializes a GitLab repository with aditional settings.', + description: 'Initializes a GitLab repository with additional settings.', example: yaml.stringify({ steps: [ { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts index ee5e81c6c6..c5161b199d 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts @@ -418,7 +418,7 @@ describe('createGitLabMergeRequest', () => { }); describe('createGitLabMergeRequestWithAssignee', () => { - it('assignee is set correcly when a valid assignee username is passed in options', async () => { + it('assignee is set correctly when a valid assignee username is passed in options', async () => { const input = { repoUrl: 'gitlab.com?repo=repo&owner=owner', title: 'Create my new MR', @@ -678,7 +678,7 @@ describe('createGitLabMergeRequest', () => { ); }); - it('reviewer is set correcly when a valid reviewer username is passed in options in combination with MR approval rules', async () => { + it('reviewer is set correctly when a valid reviewer username is passed in options in combination with MR approval rules', async () => { const input = { repoUrl: 'gitlab.com?repo=repo&owner=owner', title: 'Create my new MR', @@ -730,7 +730,7 @@ describe('createGitLabMergeRequest', () => { ); }); - it('reviewer is set correcly when a valid reviewer username is passed in options in combination with deactivated approval rules', async () => { + it('reviewer is set correctly when a valid reviewer username is passed in options in combination with deactivated approval rules', async () => { const input = { repoUrl: 'gitlab.com?repo=repo&owner=owner', title: 'Create my new MR', @@ -824,7 +824,7 @@ describe('createGitLabMergeRequest', () => { expect(mockGitlabClient.MergeRequests.edit).not.toHaveBeenCalled(); }); - it('reviewer is set correcly when a valid reviewer username is passed in options and MR rules are not included in the Gitlab license (404)', async () => { + it('reviewer is set correctly when a valid reviewer username is passed in options and MR rules are not included in the Gitlab license (404)', async () => { const input = { repoUrl: 'gitlab.com?repo=repo-without-approval-rule-license&owner=owner', @@ -875,7 +875,7 @@ describe('createGitLabMergeRequest', () => { expect(ctx.logger.warn).toHaveBeenCalledWith( 'Failed to retrieve approval rules for MR 6: Error: Not Found. Proceeding with MR creation without reviewers from approval rules.', ); - expect(ctx.output).toHaveBeenCalledWith('targetBranchName', 'main'); // This ensures that the MR scaffolder step finishes successfully and all errors are catched. + expect(ctx.output).toHaveBeenCalledWith('targetBranchName', 'main'); // This ensures that the MR scaffolder step finishes successfully and all errors are caught. }); it('assignee is not set when a valid assignee username is not passed in options', async () => { @@ -1144,7 +1144,7 @@ describe('createGitLabMergeRequest', () => { mockDir.setContent({ [workspacePath]: { source: { 'foo.txt': 'Hello there!', 'auto.txt': 'File exist' }, - irrevelant: {}, + irrelevant: {}, }, }); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts index 49b7b501d4..61a6c04889 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts @@ -317,7 +317,7 @@ which uses additional API calls in order to detect whether to 'create', 'update' } } - let reviewerIds: number[] | undefined = undefined; // Explicitly set to undefined. Strangely, passing an empty array to the API will result the other options being undefined also being explicity passed to the Gitlab API call (e.g. assigneeId) + let reviewerIds: number[] | undefined = undefined; // Explicitly set to undefined. Strangely, passing an empty array to the API will result the other options being undefined also being explicitly passed to the Gitlab API call (e.g. assigneeId) if (reviewers !== undefined) { reviewerIds = ( await Promise.all( diff --git a/plugins/scaffolder-backend-module-gitlab/src/util.test.ts b/plugins/scaffolder-backend-module-gitlab/src/util.test.ts index 940dd30a05..3fa92dbe66 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/util.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/util.test.ts @@ -351,7 +351,7 @@ describe('checkEpicScope', () => { }); describe('convertDate', () => { - it('should convert a valid input date with miliseconds to an ISO string', () => { + it('should convert a valid input date with milliseconds to an ISO string', () => { const inputDate = '1970-01-01T12:00:00.000Z'; const defaultDate = '1978-10-09T12:00:00Z'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts index 6d4f7de0d8..839ec50886 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts @@ -250,7 +250,7 @@ describe('catalog:register', () => { ); }); - it('should not return entityRef if there are no entites', async () => { + it('should not return entityRef if there are no entities', async () => { catalogClient.addLocation .mockResolvedValueOnce({ location: null as any, diff --git a/plugins/scaffolder-node/src/scm/git.test.ts b/plugins/scaffolder-node/src/scm/git.test.ts index dfbdd9cd16..b9490ca8ee 100644 --- a/plugins/scaffolder-node/src/scm/git.test.ts +++ b/plugins/scaffolder-node/src/scm/git.test.ts @@ -137,13 +137,13 @@ describe('Git', () => { it('should call isomorphic-git with the correct arguments', async () => { const git = Git.fromAuth({}); const dir = 'mockdirectory'; - const message = 'Inital Commit'; + const message = 'Initial Commit'; const author = { name: 'author', email: 'test@backstage.io', }; const committer = { - name: 'comitter', + name: 'committer', email: 'test@backstage.io', }; const signingKey = 'test-signing-key'; @@ -414,7 +414,7 @@ describe('Git', () => { email: 'test@backstage.io', }; const committer = { - name: 'comitter', + name: 'committer', email: 'test@backstage.io', }; const theirs = 'master'; diff --git a/plugins/scaffolder-react/src/hooks/useCustomFieldExtensions.ts b/plugins/scaffolder-react/src/hooks/useCustomFieldExtensions.ts index d44fc18e07..be24e4186d 100644 --- a/plugins/scaffolder-react/src/hooks/useCustomFieldExtensions.ts +++ b/plugins/scaffolder-react/src/hooks/useCustomFieldExtensions.ts @@ -51,7 +51,7 @@ export const useCustomFieldExtensions = < }), ); - // This should really be a different type moving foward, but we do this to keep type compatibility. + // This should really be a different type moving forward, but we do this to keep type compatibility. // should probably also move the defaults into the API eventually too, but that will come with the move // to the new frontend system. const blueprintsToLegacy: FieldExtensionOptions[] = blueprintFields?.map( diff --git a/plugins/scaffolder-react/src/next/components/Stepper/utils.test.ts b/plugins/scaffolder-react/src/next/components/Stepper/utils.test.ts index 24c62ebb35..a8eed2162f 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/utils.test.ts +++ b/plugins/scaffolder-react/src/next/components/Stepper/utils.test.ts @@ -65,7 +65,7 @@ describe('hasErrors', () => { otherThing: {}, someName: { __errors: [ - 'Accepts alphanumeric values along with _(underscore) and -(hypen) as special characters', + 'Accepts alphanumeric values along with _(underscore) and -(hyphen) as special characters', ], addError: jest.fn(), }, diff --git a/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.test.tsx b/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.test.tsx index feab3e30de..b014bfa21f 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.test.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.test.tsx @@ -44,7 +44,7 @@ describe('TemplateGroups', () => { }); it('should use the error api if there is an error with the retrieval of entitylist', async () => { - const mockError = new Error('tings went poop'); + const mockError = new Error('things went poop'); (useEntityList as jest.Mock).mockReturnValue({ error: mockError, }); diff --git a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx index 7e0a201e99..b3dc78dba5 100644 --- a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx +++ b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx @@ -108,7 +108,7 @@ describe('', () => { , ); - // Test template title is overriden + // Test template title is overridden expect(getByRole('heading', { level: 2 }).innerHTML).toBe( 'Different title than template', ); diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/CustomFieldPlaygroud.tsx b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/CustomFieldPlayground.tsx similarity index 99% rename from plugins/scaffolder/src/alpha/components/TemplateEditorPage/CustomFieldPlaygroud.tsx rename to plugins/scaffolder/src/alpha/components/TemplateEditorPage/CustomFieldPlayground.tsx index 0a5ffda3d4..88e192d510 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/CustomFieldPlaygroud.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/CustomFieldPlayground.tsx @@ -57,7 +57,7 @@ const useStyles = makeStyles( { name: 'ScaffolderCustomFieldExtensionsPlaygroud' }, ); -export const CustomFieldPlaygroud = ({ +export const CustomFieldPlayground = ({ fieldExtensions = [], }: { fieldExtensions?: FieldExtensionOptions[]; diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorToolbar.tsx b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorToolbar.tsx index 6a0d673088..1ceb6b6097 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorToolbar.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorToolbar.tsx @@ -37,7 +37,7 @@ import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react'; import { ActionPageContent } from '../../../components/ActionsPage/ActionsPage'; import { scaffolderTranslationRef } from '../../../translation'; -import { CustomFieldPlaygroud } from './CustomFieldPlaygroud'; +import { CustomFieldPlayground } from './CustomFieldPlayground'; import { TemplatingExtensionsPageContent } from '../../../components/TemplatingExtensionsPage/TemplatingExtensionsPage'; const useStyles = makeStyles( @@ -128,7 +128,7 @@ export function TemplateEditorToolbar(props: { open={showFieldsDrawer} onClose={() => setShowFieldsDrawer(false)} > - + alertApi.post({ - message: `Error loading exisiting templates: ${e.message}`, + message: `Error loading existing templates: ${e.message}`, severity: 'error', }), ), diff --git a/plugins/scaffolder/src/components/FileBrowser/FileBrowser.test.tsx b/plugins/scaffolder/src/components/FileBrowser/FileBrowser.test.tsx index 67d0034ba3..c87b8d1352 100644 --- a/plugins/scaffolder/src/components/FileBrowser/FileBrowser.test.tsx +++ b/plugins/scaffolder/src/components/FileBrowser/FileBrowser.test.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FileEntry, parseFileEntires } from './FileBrowser'; +import { FileEntry, parseFileEntries } from './FileBrowser'; function dir(path: string, ...children: FileEntry[]): FileEntry { return { @@ -33,62 +33,62 @@ function file(path: string): FileEntry { }; } -describe('parseFileEntires', () => { +describe('parseFileEntries', () => { it('parses an empty list', () => { - expect(parseFileEntires([])).toEqual([]); + expect(parseFileEntries([])).toEqual([]); }); it('parses a single file', () => { - expect(parseFileEntires(['a.txt'])).toEqual([file('a.txt')]); - expect(parseFileEntires(['a/b.txt'])).toEqual([dir('a', file('a/b.txt'))]); - expect(parseFileEntires(['a/b/c.txt'])).toEqual([ + expect(parseFileEntries(['a.txt'])).toEqual([file('a.txt')]); + expect(parseFileEntries(['a/b.txt'])).toEqual([dir('a', file('a/b.txt'))]); + expect(parseFileEntries(['a/b/c.txt'])).toEqual([ dir('a', dir('a/b', file('a/b/c.txt'))), ]); }); it('parses multiple files', () => { - expect(parseFileEntires(['a.txt', 'b.txt'])).toEqual([ + expect(parseFileEntries(['a.txt', 'b.txt'])).toEqual([ file('a.txt'), file('b.txt'), ]); - expect(parseFileEntires(['a.txt', 'a/b.txt'])).toEqual([ + expect(parseFileEntries(['a.txt', 'a/b.txt'])).toEqual([ dir('a', file('a/b.txt')), file('a.txt'), ]); - expect(parseFileEntires(['a.txt', 'a/b.txt', 'a/c.txt'])).toEqual([ + expect(parseFileEntries(['a.txt', 'a/b.txt', 'a/c.txt'])).toEqual([ dir('a', file('a/b.txt'), file('a/c.txt')), file('a.txt'), ]); - expect(parseFileEntires(['a.txt', 'a/b/c.txt', 'a/b/d.txt'])).toEqual([ + expect(parseFileEntries(['a.txt', 'a/b/c.txt', 'a/b/d.txt'])).toEqual([ dir('a', dir('a/b', file('a/b/c.txt'), file('a/b/d.txt'))), file('a.txt'), ]); }); it('throws an error on invalid filenames', () => { - expect(() => parseFileEntires([''])).toThrow(`Invalid path part: ''`); - expect(() => parseFileEntires(['/'])).toThrow(`Invalid path part: ''`); - expect(() => parseFileEntires(['a/'])).toThrow(`Invalid path part: ''`); - expect(() => parseFileEntires(['/a.txt'])).toThrow(`Invalid path part: ''`); - expect(() => parseFileEntires(['a//a.txt'])).toThrow( + expect(() => parseFileEntries([''])).toThrow(`Invalid path part: ''`); + expect(() => parseFileEntries(['/'])).toThrow(`Invalid path part: ''`); + expect(() => parseFileEntries(['a/'])).toThrow(`Invalid path part: ''`); + expect(() => parseFileEntries(['/a.txt'])).toThrow(`Invalid path part: ''`); + expect(() => parseFileEntries(['a//a.txt'])).toThrow( `Invalid path part: ''`, ); }); it('throws an error on conflicting directory and filenames', () => { - expect(() => parseFileEntires(['a', 'a'])).toThrow( + expect(() => parseFileEntries(['a', 'a'])).toThrow( `Duplicate filename at 'a'`, ); - expect(() => parseFileEntires(['a', 'a/b'])).toThrow( + expect(() => parseFileEntries(['a', 'a/b'])).toThrow( `Duplicate filename at 'a'`, ); - expect(() => parseFileEntires(['a/b', 'a'])).toThrow( + expect(() => parseFileEntries(['a/b', 'a'])).toThrow( `Duplicate filename at 'a'`, ); - expect(() => parseFileEntires(['a/b', 'a/b/c'])).toThrow( + expect(() => parseFileEntries(['a/b', 'a/b/c'])).toThrow( `Duplicate filename at 'a/b'`, ); - expect(() => parseFileEntires(['a/b/c', 'a/b/c'])).toThrow( + expect(() => parseFileEntries(['a/b/c', 'a/b/c'])).toThrow( `Duplicate filename at 'a/b/c'`, ); }); diff --git a/plugins/scaffolder/src/components/FileBrowser/FileBrowser.tsx b/plugins/scaffolder/src/components/FileBrowser/FileBrowser.tsx index a93d57f7b3..3df9d7397f 100644 --- a/plugins/scaffolder/src/components/FileBrowser/FileBrowser.tsx +++ b/plugins/scaffolder/src/components/FileBrowser/FileBrowser.tsx @@ -42,7 +42,7 @@ export type FileEntry = children: FileEntry[]; }; -export function parseFileEntires(paths: string[]): FileEntry[] { +export function parseFileEntries(paths: string[]): FileEntry[] { const root: FileEntry = { type: 'directory', name: '', @@ -122,7 +122,7 @@ export function FileBrowser(props: FileBrowserProps) { const classes = useStyles(); const fileTree = useMemo( - () => parseFileEntires(props.filePaths), + () => parseFileEntries(props.filePaths), [props.filePaths], ); diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx index d5f9e6af0c..eb80d47e78 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx @@ -188,7 +188,7 @@ describe('', () => { }); }); - it('search for entitities containing an specific key', async () => { + it('search for entities containing an specific key', async () => { const uiSchemaWithBoolean = { 'ui:options': { catalogFilter: [ diff --git a/plugins/scaffolder/src/components/fields/RepoBranchPicker/BitbucketRepoBranchPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoBranchPicker/BitbucketRepoBranchPicker.test.tsx index d52ac9dec6..2f2f186f6f 100644 --- a/plugins/scaffolder/src/components/fields/RepoBranchPicker/BitbucketRepoBranchPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoBranchPicker/BitbucketRepoBranchPicker.test.tsx @@ -113,7 +113,7 @@ describe('BitbucketRepoBranchPicker', () => { , ); - // Open the Autcomplete dropdown + // Open the Autocomplete dropdown const input = getByRole('textbox'); await userEvent.click(input); diff --git a/plugins/scaffolder/src/components/fields/RepoBranchPicker/GitHubRepoBranchPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoBranchPicker/GitHubRepoBranchPicker.test.tsx index 959e13bf9d..2ad5ed2e43 100644 --- a/plugins/scaffolder/src/components/fields/RepoBranchPicker/GitHubRepoBranchPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoBranchPicker/GitHubRepoBranchPicker.test.tsx @@ -113,7 +113,7 @@ describe('GitHubRepoBranchPicker', () => { , ); - // Open the Autcomplete dropdown + // Open the Autocomplete dropdown const input = getByRole('textbox'); await userEvent.click(input); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.test.tsx index 5a17cbf22e..c42a4404b9 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.test.tsx @@ -200,7 +200,7 @@ describe('BitbucketRepoPicker', () => { , ); - // Open the Autcomplete dropdown + // Open the Autocomplete dropdown const workspaceInput = getAllByRole('textbox')[0]; await userEvent.click(workspaceInput); @@ -230,7 +230,7 @@ describe('BitbucketRepoPicker', () => { , ); - // Open the Autcomplete dropdown + // Open the Autocomplete dropdown const projectInput = getAllByRole('textbox')[1]; await userEvent.click(projectInput); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.test.tsx index 886437288f..b990845c86 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.test.tsx @@ -132,7 +132,7 @@ describe('GithubRepoPicker', () => { , ); - // Open the Autcomplete dropdown + // Open the Autocomplete dropdown const ownerInput = getAllByRole('textbox')[0]; await userEvent.click(ownerInput); diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts index 8d950cf85f..e2c8a48cd6 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts @@ -518,7 +518,7 @@ export class ElasticSearchSearchEngine implements SearchEngine { const service = config.getOptionalString('service') ?? requestSigner.service; if (service !== 'es' && service !== 'aoss') - throw new Error(`Unrecognized serivce type: ${service}`); + throw new Error(`Unrecognized service type: ${service}`); return { provider: 'aws', node: config.getString('node'), diff --git a/plugins/search-backend-module-elasticsearch/src/module.ts b/plugins/search-backend-module-elasticsearch/src/module.ts index 1ed3c3f87c..c5c2a6fc1d 100644 --- a/plugins/search-backend-module-elasticsearch/src/module.ts +++ b/plugins/search-backend-module-elasticsearch/src/module.ts @@ -72,7 +72,7 @@ export default createBackendModule({ const baseConfig = config.getOptional(baseKey); if (!baseConfig) { logger.warn( - 'No configuration found under "search.elasticsearch" key. Skipping search engine inititalization.', + 'No configuration found under "search.elasticsearch" key. Skipping search engine initialization.', ); return; } diff --git a/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts b/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts index 2bdf1cd8b0..2eb6bf03f4 100644 --- a/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts +++ b/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts @@ -70,7 +70,7 @@ describe('CachedEntityLoader', () => { expect(catalog.getEntityByRef).not.toHaveBeenCalled(); }); - it('does not cache missing entites', async () => { + it('does not cache missing entities', async () => { const catalog = catalogServiceMock({ entities: [] }); cache.get.mockResolvedValue(undefined); diff --git a/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.ts b/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.ts index 850f51260f..076504308c 100644 --- a/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.ts +++ b/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.ts @@ -80,7 +80,7 @@ export class DockerContainerRunner implements TechDocsContainerRunner { } else if (!stream) { reject( new Error( - 'Unexpeected error: no stream returned from Docker while pulling image', + 'Unexpected error: no stream returned from Docker while pulling image', ), ); } else { diff --git a/plugins/techdocs-react/src/context.test.tsx b/plugins/techdocs-react/src/context.test.tsx index 719a067953..7c157d0699 100644 --- a/plugins/techdocs-react/src/context.test.tsx +++ b/plugins/techdocs-react/src/context.test.tsx @@ -53,7 +53,7 @@ const mockEntityMetadata: Entity = { }; const mockTechDocsMetadata: TechDocsMetadata = { - site_name: 'test-componnet', + site_name: 'test-component', site_description: 'this is a test component', }; diff --git a/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.test.tsx b/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.test.tsx index 8d87125c13..490e4470b1 100644 --- a/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.test.tsx +++ b/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.test.tsx @@ -77,7 +77,7 @@ describe('Entity List Docs Grid', () => { [starredEntitiesApiRef, new MockStarredEntitiesApi()], ); - it('should render all entitites without filtering', async () => { + it('should render all entities without filtering', async () => { await renderInTestApp( diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage/context.test.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage/context.test.tsx index e98b328cc9..a46264e329 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPage/context.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage/context.test.tsx @@ -44,7 +44,7 @@ const mockEntityMetadata: Entity = { }; const mockTechDocsMetadata: TechDocsMetadata = { - site_name: 'test-componnet', + site_name: 'test-component', site_description: 'this is a test component', }; From aaed3bde0f19fae5c276f160509fe169d544f13d Mon Sep 17 00:00:00 2001 From: Julius Sudds Date: Wed, 23 Apr 2025 11:08:17 -0500 Subject: [PATCH 12/17] moved Entity Ref to top of Card Signed-off-by: Julius Sudds --- .../Group/GroupProfile/GroupProfileCard.tsx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx index da94092445..35eb82334c 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx @@ -150,6 +150,17 @@ export const GroupProfileCard = (props: { + + + + + + + + {profile?.email && ( @@ -203,17 +214,6 @@ export const GroupProfileCard = (props: { secondary="Child Groups" /> - - - - - - - - {props?.showLinks && } From ae249fc1c3b859770c6bc898ea525c1fda914c11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 24 Apr 2025 15:02:19 +0200 Subject: [PATCH 13/17] leverage webhook secrets from github integrations too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/large-experts-sort.md | 5 +++ .../events-backend-module-github/package.json | 1 + .../createGithubSignatureValidator.test.ts | 28 ++++++++++++ .../http/createGithubSignatureValidator.ts | 44 +++++++++++++------ yarn.lock | 1 + 5 files changed, 65 insertions(+), 14 deletions(-) create mode 100644 .changeset/large-experts-sort.md diff --git a/.changeset/large-experts-sort.md b/.changeset/large-experts-sort.md new file mode 100644 index 0000000000..003be57430 --- /dev/null +++ b/.changeset/large-experts-sort.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-events-backend-module-github': patch +--- + +Support webhook validation based on `integrations.github.[].apps.[].webhookSecret` too diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json index 13ab583c0c..54bee4bfa0 100644 --- a/plugins/events-backend-module-github/package.json +++ b/plugins/events-backend-module-github/package.json @@ -45,6 +45,7 @@ "dependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", + "@backstage/integration": "workspace:^", "@backstage/plugin-events-node": "workspace:^", "@octokit/webhooks-methods": "^3.0.0" }, diff --git a/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.test.ts b/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.test.ts index bb604e88d8..c771e29d67 100644 --- a/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.test.ts +++ b/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.test.ts @@ -47,6 +47,24 @@ describe('createGithubSignatureValidator', () => { }, }, }); + const configWithAppSecret = new ConfigReader({ + integrations: { + github: [ + { + host: 'github.com', + apps: [ + { + appId: 1, + privateKey: 'a', + clientId: 'b', + clientSecret: 'c', + webhookSecret: secret, + }, + ], + }, + ], + }, + }); const payloadString = '{"test": "payload", "score": 5.0}'; const payload = JSON.parse(payloadString); const payloadBuffer = Buffer.from(payloadString); @@ -104,4 +122,14 @@ describe('createGithubSignatureValidator', () => { expect(context.details).toBeUndefined(); }); + + it('secret configured, accept request with valid signature defined in integrations', async () => { + const request = await requestWithSignature(await validSignature); + const context = new TestContext(); + + const validator = createGithubSignatureValidator(configWithAppSecret); + await validator!(request, context); + + expect(context.details).toBeUndefined(); + }); }); diff --git a/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.ts b/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.ts index f54c00b58a..137b2a08f3 100644 --- a/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.ts +++ b/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.ts @@ -15,6 +15,7 @@ */ import { Config } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; import { RequestDetails, RequestValidationContext, @@ -36,10 +37,25 @@ import { verify } from '@octokit/webhooks-methods'; export function createGithubSignatureValidator( config: Config, ): RequestValidator | undefined { - const secret = config.getOptionalString( + const webhookSecrets = new Set(); + + const integrations = ScmIntegrations.fromConfig(config); + for (const integration of integrations.github.list()) { + for (const app of integration.config.apps ?? []) { + if (app.webhookSecret) { + webhookSecrets.add(app.webhookSecret); + } + } + } + + const moduleSecret = config.getOptionalString( 'events.modules.github.webhookSecret', ); - if (!secret) { + if (moduleSecret) { + webhookSecrets.add(moduleSecret); + } + + if (webhookSecrets.size === 0) { return undefined; } @@ -51,18 +67,18 @@ export function createGithubSignatureValidator( | string | undefined; - if ( - !signature || - !(await verify( - secret, - request.raw.body.toString(request.raw.encoding), - signature, - )) - ) { - context.reject({ - status: 403, - payload: { message: 'invalid signature' }, - }); + if (signature) { + const body = request.raw.body.toString(request.raw.encoding); + for (const secret of webhookSecrets) { + if (await verify(secret, body, signature)) { + return; // OK + } + } } + + context.reject({ + status: 403, + payload: { message: 'invalid signature' }, + }); }; } diff --git a/yarn.lock b/yarn.lock index fac71e8789..7acee57c6d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6576,6 +6576,7 @@ __metadata: "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/integration": "workspace:^" "@backstage/plugin-events-backend-test-utils": "workspace:^" "@backstage/plugin-events-node": "workspace:^" "@octokit/webhooks-methods": "npm:^3.0.0" From 02dd364c62973053f93a97fab0bf9a10c5f2ff12 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 20:43:52 +0000 Subject: [PATCH 14/17] Pin dependencies Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/automate_issue_labels.yml | 2 +- .github/workflows/issue.yaml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/automate_issue_labels.yml b/.github/workflows/automate_issue_labels.yml index bbffa4f7b8..b4a5d0f655 100644 --- a/.github/workflows/automate_issue_labels.yml +++ b/.github/workflows/automate_issue_labels.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Remove needs:triage label - uses: actions-ecosystem/action-remove-labels@v1 + uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0 # v1 if: ${{ startsWith(github.event.label.name, 'priority:') || ( startsWith(github.event.label.name, 'needs:') && github.event.label.name != 'needs:triage' ) }} with: labels: needs:triage diff --git a/.github/workflows/issue.yaml b/.github/workflows/issue.yaml index 46ff8a6011..50d0e4ceb0 100644 --- a/.github/workflows/issue.yaml +++ b/.github/workflows/issue.yaml @@ -22,7 +22,7 @@ jobs: # We need to checkout the `.github/ISSUE_TEMPLATE` for the advanced labeler action to be able to read the templates # While at it we might as well checkout all of `.github` so that the labeling actions don't need to fetch their configs - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: sparse-checkout: .github @@ -38,12 +38,12 @@ jobs: # These two steps add labels based on user input in the issue form - name: Parse issue form - uses: stefanbuck/github-issue-parser@v3 + uses: stefanbuck/github-issue-parser@2ea9b35a8c584529ed00891a8f7e41dc46d0441e # v3 id: issue-parser with: template-path: .github/ISSUE_TEMPLATE/.common.yaml - name: Add advanced issue labels - uses: redhat-plumbers-in-action/advanced-issue-labeler@v2 + uses: redhat-plumbers-in-action/advanced-issue-labeler@9e55064634b67244f7deb4211452b4a7217b93de # v2 with: issue-form: ${{ steps.issue-parser.outputs.jsonString }} token: ${{ secrets.GITHUB_TOKEN }} From 3dd708ff4d14bf62b27f602c27faaacf2f4e4949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sat, 26 Apr 2025 11:49:55 +0200 Subject: [PATCH 15/17] properly resolve app IDs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/large-experts-sort.md | 6 +- .../events-backend-module-github/package.json | 6 +- .../report.api.md | 7 -- .../createGithubSignatureValidator.test.ts | 51 +++++++-- .../http/createGithubSignatureValidator.ts | 40 ++++--- .../events-backend-module-github/src/index.ts | 6 +- .../src/service/eventsModuleGithubWebhook.ts | 9 +- .../src/util/createAppIdResolver.ts | 66 +++++++++++ .../src/util/octokitProviderService.ts | 104 ++++++++++++++++++ yarn.lock | 11 ++ 10 files changed, 270 insertions(+), 36 deletions(-) create mode 100644 plugins/events-backend-module-github/src/util/createAppIdResolver.ts create mode 100644 plugins/events-backend-module-github/src/util/octokitProviderService.ts diff --git a/.changeset/large-experts-sort.md b/.changeset/large-experts-sort.md index 003be57430..6d58836db7 100644 --- a/.changeset/large-experts-sort.md +++ b/.changeset/large-experts-sort.md @@ -1,5 +1,7 @@ --- -'@backstage/plugin-events-backend-module-github': patch +'@backstage/plugin-events-backend-module-github': minor --- -Support webhook validation based on `integrations.github.[].apps.[].webhookSecret` too +**BREAKING**: Removed the `createGithubSignatureValidator` export. + +Added support webhook validation based on `integrations.github.[].apps.[].webhookSecret`. diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json index 54bee4bfa0..f03ba438fb 100644 --- a/plugins/events-backend-module-github/package.json +++ b/plugins/events-backend-module-github/package.json @@ -47,7 +47,11 @@ "@backstage/config": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-events-node": "workspace:^", - "@octokit/webhooks-methods": "^3.0.0" + "@backstage/types": "workspace:^", + "@octokit/auth-callback": "^5.0.0", + "@octokit/webhooks-methods": "^3.0.0", + "lodash": "^4.17.21", + "octokit": "^3.0.0" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/events-backend-module-github/report.api.md b/plugins/events-backend-module-github/report.api.md index 8d1f4d939f..afecb7590b 100644 --- a/plugins/events-backend-module-github/report.api.md +++ b/plugins/events-backend-module-github/report.api.md @@ -4,17 +4,10 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; import { EventParams } from '@backstage/plugin-events-node'; import { EventsService } from '@backstage/plugin-events-node'; -import { RequestValidator } from '@backstage/plugin-events-node'; import { SubTopicEventRouter } from '@backstage/plugin-events-node'; -// @public -export function createGithubSignatureValidator( - config: Config, -): RequestValidator | undefined; - // @public (undocumented) const _default: BackendFeature; export default _default; diff --git a/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.test.ts b/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.test.ts index c771e29d67..9cb2ed3c4c 100644 --- a/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.test.ts +++ b/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.test.ts @@ -22,6 +22,7 @@ import { } from '@backstage/plugin-events-node'; import { sign } from '@octokit/webhooks-methods'; import { createGithubSignatureValidator } from './createGithubSignatureValidator'; +import { OctokitProviderService } from '../util/octokitProviderService'; class TestContext implements RequestValidationContext { #details?: Partial; @@ -35,6 +36,10 @@ class TestContext implements RequestValidationContext { } } +const octokitProvider = { + getOctokit: jest.fn(), +} satisfies OctokitProviderService; + describe('createGithubSignatureValidator', () => { const secret = 'valid-secret'; const configWithoutSecret = new ConfigReader({}); @@ -54,7 +59,7 @@ describe('createGithubSignatureValidator', () => { host: 'github.com', apps: [ { - appId: 1, + appId: 7, privateKey: 'a', clientId: 'b', clientSecret: 'c', @@ -65,8 +70,13 @@ describe('createGithubSignatureValidator', () => { ], }, }); - const payloadString = '{"test": "payload", "score": 5.0}'; - const payload = JSON.parse(payloadString); + const payload = { + test: 'payload', + score: 5.0, + repository: { html_url: 'https://github.com/backstage/backstage' }, + installation: { id: 70 }, + }; + const payloadString = JSON.stringify(payload); const payloadBuffer = Buffer.from(payloadString); const validSignature = sign({ secret, algorithm: 'sha256' }, payloadString); @@ -84,16 +94,19 @@ describe('createGithubSignatureValidator', () => { }; it('should return undefined if no secret is configured', async () => { - expect(createGithubSignatureValidator(configWithoutSecret)).toEqual( - undefined, - ); + expect( + createGithubSignatureValidator(configWithoutSecret, octokitProvider), + ).toEqual(undefined); }); it('secret configured, reject request without signature', async () => { const request = await requestWithSignature(undefined); const context = new TestContext(); - const validator = createGithubSignatureValidator(configWithSecret); + const validator = createGithubSignatureValidator( + configWithSecret, + octokitProvider, + ); await validator!(request, context); expect(context.details).not.toBeUndefined(); @@ -105,7 +118,10 @@ describe('createGithubSignatureValidator', () => { const request = await requestWithSignature('invalid signature'); const context = new TestContext(); - const validator = createGithubSignatureValidator(configWithSecret); + const validator = createGithubSignatureValidator( + configWithSecret, + octokitProvider, + ); await validator!(request, context); expect(context.details).not.toBeUndefined(); @@ -117,7 +133,10 @@ describe('createGithubSignatureValidator', () => { const request = await requestWithSignature(await validSignature); const context = new TestContext(); - const validator = createGithubSignatureValidator(configWithSecret); + const validator = createGithubSignatureValidator( + configWithSecret, + octokitProvider, + ); await validator!(request, context); expect(context.details).toBeUndefined(); @@ -126,8 +145,20 @@ describe('createGithubSignatureValidator', () => { it('secret configured, accept request with valid signature defined in integrations', async () => { const request = await requestWithSignature(await validSignature); const context = new TestContext(); + octokitProvider.getOctokit.mockResolvedValue({ + rest: { + apps: { + getInstallation: async () => ({ + data: { app_id: 7 }, + }), + }, + }, + }); - const validator = createGithubSignatureValidator(configWithAppSecret); + const validator = createGithubSignatureValidator( + configWithAppSecret, + octokitProvider, + ); await validator!(request, context); expect(context.details).toBeUndefined(); diff --git a/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.ts b/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.ts index 137b2a08f3..6eb203b79b 100644 --- a/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.ts +++ b/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.ts @@ -22,6 +22,8 @@ import { RequestValidator, } from '@backstage/plugin-events-node'; import { verify } from '@octokit/webhooks-methods'; +import { createAppIdResolver } from '../util/createAppIdResolver'; +import { OctokitProviderService } from '../util/octokitProviderService'; /** * Validates that the request received is the expected GitHub request @@ -36,29 +38,31 @@ import { verify } from '@octokit/webhooks-methods'; */ export function createGithubSignatureValidator( config: Config, + octokitProvider: OctokitProviderService, ): RequestValidator | undefined { - const webhookSecrets = new Set(); - const integrations = ScmIntegrations.fromConfig(config); + + // GitHub App installation ID to secret + const githubAppSecrets = new Map(); for (const integration of integrations.github.list()) { - for (const app of integration.config.apps ?? []) { - if (app.webhookSecret) { - webhookSecrets.add(app.webhookSecret); + for (const { appId, webhookSecret } of integration.config.apps ?? []) { + if (appId && webhookSecret) { + githubAppSecrets.set(appId, webhookSecret); } } } - const moduleSecret = config.getOptionalString( + // A single optional secret for all GitHub events + const genericSecret = config.getOptionalString( 'events.modules.github.webhookSecret', ); - if (moduleSecret) { - webhookSecrets.add(moduleSecret); - } - if (webhookSecrets.size === 0) { + if (!genericSecret && githubAppSecrets.size === 0) { return undefined; } + const appIdResolver = createAppIdResolver(octokitProvider); + return async ( request: RequestDetails, context: RequestValidationContext, @@ -69,9 +73,19 @@ export function createGithubSignatureValidator( if (signature) { const body = request.raw.body.toString(request.raw.encoding); - for (const secret of webhookSecrets) { - if (await verify(secret, body, signature)) { - return; // OK + + if (githubAppSecrets.size) { + const appId = await appIdResolver(request); + if (appId && githubAppSecrets.has(appId)) { + if (await verify(githubAppSecrets.get(appId)!, body, signature)) { + return; + } + } + } + + if (genericSecret) { + if (await verify(genericSecret, body, signature)) { + return; } } } diff --git a/plugins/events-backend-module-github/src/index.ts b/plugins/events-backend-module-github/src/index.ts index 72b55f892b..285d74bae4 100644 --- a/plugins/events-backend-module-github/src/index.ts +++ b/plugins/events-backend-module-github/src/index.ts @@ -20,6 +20,7 @@ * * @packageDocumentation */ + import { createBackendFeatureLoader } from '@backstage/backend-plugin-api'; export default createBackendFeatureLoader({ @@ -31,5 +32,8 @@ export default createBackendFeatureLoader({ }, }); -export { createGithubSignatureValidator } from './http/createGithubSignatureValidator'; +// TODO(freben): This is not exported at the moment since it depends on the octokit provider. +// Until we have made that a core thing in integrations, we can't export it +// export { createGithubSignatureValidator } from './http/createGithubSignatureValidator'; + export { GithubEventRouter } from './router/GithubEventRouter'; diff --git a/plugins/events-backend-module-github/src/service/eventsModuleGithubWebhook.ts b/plugins/events-backend-module-github/src/service/eventsModuleGithubWebhook.ts index 5cb9c88679..78def1f30a 100644 --- a/plugins/events-backend-module-github/src/service/eventsModuleGithubWebhook.ts +++ b/plugins/events-backend-module-github/src/service/eventsModuleGithubWebhook.ts @@ -20,6 +20,7 @@ import { } from '@backstage/backend-plugin-api'; import { eventsExtensionPoint } from '@backstage/plugin-events-node/alpha'; import { createGithubSignatureValidator } from '../http/createGithubSignatureValidator'; +import { octokitProviderServiceRef } from '../util/octokitProviderService'; /** * Module for the events-backend plugin, @@ -36,9 +37,13 @@ export default createBackendModule({ deps: { config: coreServices.rootConfig, events: eventsExtensionPoint, + octokitProvider: octokitProviderServiceRef, }, - async init({ config, events }) { - const validator = createGithubSignatureValidator(config); + async init({ config, events, octokitProvider }) { + const validator = createGithubSignatureValidator( + config, + octokitProvider, + ); if (validator) { events.addHttpPostIngress({ topic: 'github', diff --git a/plugins/events-backend-module-github/src/util/createAppIdResolver.ts b/plugins/events-backend-module-github/src/util/createAppIdResolver.ts new file mode 100644 index 0000000000..d5347ffd29 --- /dev/null +++ b/plugins/events-backend-module-github/src/util/createAppIdResolver.ts @@ -0,0 +1,66 @@ +/* + * Copyright 2025 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 { RequestDetails } from '@backstage/plugin-events-node'; +import { OctokitProviderService } from './octokitProviderService'; +import lodash from 'lodash'; + +export type AppIdResolver = ( + request: RequestDetails, +) => Promise; + +/** + * Helps with resolving what app ID (if any) that sent a webhook event. + */ +export function createAppIdResolver( + octokitProvider: OctokitProviderService, +): AppIdResolver { + const installationIdToAppId = new Map>(); + + return async (request: RequestDetails) => { + const installationId = lodash.get( + request.body, + 'installation.id', + ) as unknown; + + if (!installationId || typeof installationId !== 'number') { + return undefined; + } + + let appIdPromsie = installationIdToAppId.get(installationId); + if (appIdPromsie) { + return await appIdPromsie; + } + + const repositoryUrl = lodash.get( + request.body, + 'repository.html_url', + ) as unknown; + + if (!repositoryUrl || typeof repositoryUrl !== 'string') { + return undefined; + } + + const octokit = await octokitProvider.getOctokit(repositoryUrl); + appIdPromsie = octokit.rest.apps + .getInstallation({ installation_id: installationId }) + .then(response => Number(response.data.app_id)) + .catch(() => undefined); + + installationIdToAppId.set(installationId, appIdPromsie); + return await appIdPromsie; + }; +} diff --git a/plugins/events-backend-module-github/src/util/octokitProviderService.ts b/plugins/events-backend-module-github/src/util/octokitProviderService.ts new file mode 100644 index 0000000000..9993b91000 --- /dev/null +++ b/plugins/events-backend-module-github/src/util/octokitProviderService.ts @@ -0,0 +1,104 @@ +/* + * Copyright 2025 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 { + coreServices, + createServiceFactory, + createServiceRef, + RootConfigService, +} from '@backstage/backend-plugin-api'; +import { + DefaultGithubCredentialsProvider, + GithubCredentialsProvider, + ScmIntegrationRegistry, + ScmIntegrations, +} from '@backstage/integration'; +import { durationToMilliseconds, HumanDuration } from '@backstage/types'; +import { Octokit } from 'octokit'; + +export interface OctokitProviderService { + getOctokit: (url: string) => Promise; +} + +class OctokitProviderImpl implements OctokitProviderService { + readonly #integrations: ScmIntegrationRegistry; + readonly #githubCredentials: GithubCredentialsProvider; + readonly #octokitCache: Map; + readonly #octokitCacheTtl: HumanDuration; + + constructor(config: RootConfigService) { + this.#integrations = ScmIntegrations.fromConfig(config); + this.#githubCredentials = DefaultGithubCredentialsProvider.fromIntegrations( + this.#integrations, + ); + this.#octokitCache = new Map(); + this.#octokitCacheTtl = { hours: 1 }; + } + + async getOctokit(url: string): Promise { + // TODO(freben): Be smart and cache these more granularly, e.g. by + // organization or even repo. + const integration = this.#integrations.github.byUrl(url); + if (!integration) { + throw new Error(`No integration found for url: ${url}`); + } + const key = integration.config.host; + + if (this.#octokitCache.has(key)) { + return this.#octokitCache.get(key)!; + } + + const { createCallbackAuth } = await import('@octokit/auth-callback'); + + const octokit = new Octokit({ + baseUrl: integration.config.apiBaseUrl, + authStrategy: createCallbackAuth, + auth: { + callback: async () => { + try { + const credentials = await this.#githubCredentials.getCredentials({ + url, + }); + return credentials.token; + } catch { + return undefined; + } + }, + }, + }); + + this.#octokitCache.set(key, octokit); + setTimeout(() => { + this.#octokitCache.delete(key); + }, durationToMilliseconds(this.#octokitCacheTtl)); + + return octokit; + } +} + +export const octokitProviderServiceRef = + createServiceRef({ + id: 'octokitProvider', + scope: 'root', + defaultFactory: async service => + createServiceFactory({ + service, + deps: { config: coreServices.rootConfig }, + async factory({ config }) { + return new OctokitProviderImpl(config); + }, + }), + }); diff --git a/yarn.lock b/yarn.lock index 7acee57c6d..876222dd6e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6579,7 +6579,11 @@ __metadata: "@backstage/integration": "workspace:^" "@backstage/plugin-events-backend-test-utils": "workspace:^" "@backstage/plugin-events-node": "workspace:^" + "@backstage/types": "workspace:^" + "@octokit/auth-callback": "npm:^5.0.0" "@octokit/webhooks-methods": "npm:^3.0.0" + lodash: "npm:^4.17.21" + octokit: "npm:^3.0.0" languageName: unknown linkType: soft @@ -12850,6 +12854,13 @@ __metadata: languageName: node linkType: hard +"@octokit/auth-callback@npm:^5.0.0": + version: 5.0.1 + resolution: "@octokit/auth-callback@npm:5.0.1" + checksum: 10/afa2fd71e1cc238c4fc09a1a8cc3b5c8d2f231aecdb8f8be2384857cde893aa4ac697f7c6aa0b61e4d5fa1f00d41fb349ceffb938977c02ee317d5432247f6ef + languageName: node + linkType: hard + "@octokit/auth-oauth-app@npm:^5.0.0": version: 5.0.1 resolution: "@octokit/auth-oauth-app@npm:5.0.1" From 296d8a122b1044311a0a5e97d7f186ae6ee659da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 28 Apr 2025 12:15:51 +0200 Subject: [PATCH 16/17] mark BEP-0007 as implemented MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- beps/0007-auth-external-services/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beps/0007-auth-external-services/README.md b/beps/0007-auth-external-services/README.md index d49cd7992f..1d1f3e74b6 100644 --- a/beps/0007-auth-external-services/README.md +++ b/beps/0007-auth-external-services/README.md @@ -1,6 +1,6 @@ --- title: Authentication of External Services -status: provisional +status: implemented authors: - '@mareklibra' owners: From e802ee2b819229f32c23d13c80bc05bb34e62874 Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Mon, 28 Apr 2025 15:05:40 +0100 Subject: [PATCH 17/17] Update .changeset/sharp-ligers-beg.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: Brian Fletcher --- .changeset/sharp-ligers-beg.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/sharp-ligers-beg.md b/.changeset/sharp-ligers-beg.md index a89ce8ee05..0582ed72d2 100644 --- a/.changeset/sharp-ligers-beg.md +++ b/.changeset/sharp-ligers-beg.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-backend': minor --- -Adds the ability to disable the default entity processors using a new boolean app config item `catalog.disableCatalogProcessors`. +Adds the ability to disable the default entity processors using a new boolean app config item `catalog.disableDefaultProcessors`.