diff --git a/.changeset/twenty-humans-visit.md b/.changeset/twenty-humans-visit.md new file mode 100644 index 0000000000..e543d30145 --- /dev/null +++ b/.changeset/twenty-humans-visit.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-techdocs-react': patch +--- + +Add `toLowerEntityRefMaybe()` helper function for handling `techdocs.legacyUseCaseSensitiveTripletPaths` flag. +Pass modified `entityRef` to `TechDocsReaderPageContext` to handle the `techdocs.legacyUseCaseSensitiveTripletPaths` flag. diff --git a/microsite/data/plugins/okta-entity-providers.yaml b/microsite/data/plugins/okta-entity-providers.yaml index 714a1e4b68..93e4473fde 100644 --- a/microsite/data/plugins/okta-entity-providers.yaml +++ b/microsite/data/plugins/okta-entity-providers.yaml @@ -5,5 +5,5 @@ authorUrl: https://roadie.io/?utm_source=backstage.io&utm_medium=marketplace&utm category: Identity description: Load users and groups from Okta into the Backstage catalog. documentation: https://github.com/RoadieHQ/roadie-backstage-plugins/tree/main/plugins/backend/catalog-backend-module-okta -iconUrl: https://roadie.io/images/logos/github.png +iconUrl: https://roadie.io/images/logos/okta.png npmPackageName: '@roadiehq/catalog-backend-module-okta' diff --git a/plugins/techdocs-react/api-report.md b/plugins/techdocs-react/api-report.md index 336481e922..d04b852d07 100644 --- a/plugins/techdocs-react/api-report.md +++ b/plugins/techdocs-react/api-report.md @@ -7,6 +7,7 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { AsyncState } from 'react-use/lib/useAsync'; import { ComponentType } from 'react'; import { CompoundEntityRef } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; import { Dispatch } from 'react'; import { Entity } from '@backstage/catalog-model'; import { Extension } from '@backstage/core-plugin-api'; @@ -152,6 +153,12 @@ export interface TechDocsStorageApi { // @public export const techdocsStorageApiRef: ApiRef; +// @public +export function toLowercaseEntityRefMaybe( + entityRef: CompoundEntityRef, + config: Config, +): CompoundEntityRef; + // @public export const useShadowDomStylesLoading: (element: Element | null) => boolean; diff --git a/plugins/techdocs-react/package.json b/plugins/techdocs-react/package.json index b844270e2f..75821eac88 100644 --- a/plugins/techdocs-react/package.json +++ b/plugins/techdocs-react/package.json @@ -36,6 +36,7 @@ }, "dependencies": { "@backstage/catalog-model": "^1.1.0", + "@backstage/config": "^1.0.1", "@backstage/core-components": "^0.10.1-next.0", "@backstage/core-plugin-api": "^1.0.5-next.0", "@backstage/version-bridge": "^1.0.1", diff --git a/plugins/techdocs-react/src/context.test.tsx b/plugins/techdocs-react/src/context.test.tsx index 14669dc336..6cc5537d9e 100644 --- a/plugins/techdocs-react/src/context.test.tsx +++ b/plugins/techdocs-react/src/context.test.tsx @@ -21,6 +21,7 @@ import { ThemeProvider } from '@material-ui/core'; import { lightTheme } from '@backstage/theme'; import { TestApiProvider } from '@backstage/test-utils'; import { Entity, CompoundEntityRef } from '@backstage/catalog-model'; +import { configApiRef } from '@backstage/core-plugin-api'; import { techdocsApiRef } from './api'; import { useTechDocsReaderPage, TechDocsReaderPageProvider } from './context'; @@ -55,6 +56,10 @@ const techdocsApiMock = { getTechDocsMetadata: jest.fn().mockResolvedValue(mockTechDocsMetadata), }; +const configApiMock = { + getOptionalBoolean: jest.fn().mockReturnValue(undefined), +}; + const wrapper = ({ entityRef = { kind: mockEntityMetadata.kind, @@ -67,7 +72,12 @@ const wrapper = ({ children: React.ReactNode; }) => ( - + {children} @@ -123,4 +133,28 @@ describe('useTechDocsReaderPage', () => { '

Shadow DOM Mock

', ); }); + + it('should set entityRef as lowercase when legacyUseCaseSensitiveTripletPaths is false', async () => { + const lowercaseEntityRef = { + kind: mockEntityMetadata.kind.toLocaleLowerCase(), + name: mockEntityMetadata.metadata.name.toLocaleLowerCase(), + namespace: mockEntityMetadata.metadata.namespace?.toLocaleLowerCase(), + }; + const { result } = renderHook(() => useTechDocsReaderPage(), { wrapper }); + + expect(result.current.entityRef).toStrictEqual(lowercaseEntityRef); + }); + + it('entityRef is not modified when legacyUseCaseSensitiveTripletPaths is true', async () => { + configApiMock.getOptionalBoolean.mockReturnValueOnce(true); + const caseSensitiveEntityRef = { + kind: mockEntityMetadata.kind, + name: mockEntityMetadata.metadata.name, + namespace: mockEntityMetadata.metadata.namespace!!, + }; + + const { result } = renderHook(() => useTechDocsReaderPage(), { wrapper }); + + expect(result.current.entityRef).toStrictEqual(caseSensitiveEntityRef); + }); }); diff --git a/plugins/techdocs-react/src/context.tsx b/plugins/techdocs-react/src/context.tsx index 4f2fa26164..66faf858fa 100644 --- a/plugins/techdocs-react/src/context.tsx +++ b/plugins/techdocs-react/src/context.tsx @@ -33,11 +33,13 @@ import { createVersionedValueMap, } from '@backstage/version-bridge'; -import { useApi } from '@backstage/core-plugin-api'; +import { configApiRef, useApi } from '@backstage/core-plugin-api'; import { techdocsApiRef } from './api'; import { TechDocsEntityMetadata, TechDocsMetadata } from './types'; +import { toLowercaseEntityRefMaybe } from './helpers'; + const areEntityRefsEqual = ( prevEntityRef: CompoundEntityRef, nextEntityRef: CompoundEntityRef, @@ -107,6 +109,7 @@ export type TechDocsReaderPageProviderProps = { export const TechDocsReaderPageProvider = memo( ({ entityRef, children }: TechDocsReaderPageProviderProps) => { const techdocsApi = useApi(techdocsApiRef); + const config = useApi(configApiRef); const metadata = useAsync(async () => { return techdocsApi.getTechDocsMetadata(entityRef); @@ -126,7 +129,7 @@ export const TechDocsReaderPageProvider = memo( const value = { metadata, - entityRef, + entityRef: toLowercaseEntityRefMaybe(entityRef, config), entityMetadata, shadowRoot, setShadowRoot, diff --git a/plugins/techdocs-react/src/helpers.ts b/plugins/techdocs-react/src/helpers.ts new file mode 100644 index 0000000000..3f10fff2ba --- /dev/null +++ b/plugins/techdocs-react/src/helpers.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config } from '@backstage/config'; +import { CompoundEntityRef } from '@backstage/catalog-model'; + +/** + * Lower-case entity triplets by default, but allow override. + * + * @public + */ +export function toLowercaseEntityRefMaybe( + entityRef: CompoundEntityRef, + config: Config, +): CompoundEntityRef { + if (config.getOptionalBoolean('techdocs.legacyUseCaseSensitiveTripletPaths')) + return entityRef; + + entityRef.kind = entityRef.kind.toLocaleLowerCase(); + entityRef.name = entityRef.name.toLocaleLowerCase(); + entityRef.namespace = entityRef.namespace.toLocaleLowerCase(); + + return entityRef; +} diff --git a/plugins/techdocs-react/src/index.ts b/plugins/techdocs-react/src/index.ts index bd2edda763..00ba620651 100644 --- a/plugins/techdocs-react/src/index.ts +++ b/plugins/techdocs-react/src/index.ts @@ -51,3 +51,4 @@ export { useShadowRootElements, useShadowRootSelection, } from './hooks'; +export { toLowercaseEntityRefMaybe } from './helpers'; diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage/context.test.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage/context.test.tsx index 148b740205..2380724960 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPage/context.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage/context.test.tsx @@ -22,6 +22,7 @@ import { ThemeProvider } from '@material-ui/core'; import { lightTheme } from '@backstage/theme'; import { TestApiProvider } from '@backstage/test-utils'; import { Entity, CompoundEntityRef } from '@backstage/catalog-model'; +import { configApiRef } from '@backstage/core-plugin-api'; import { techdocsApiRef, TechDocsMetadata, @@ -52,6 +53,10 @@ const techdocsApiMock = { getTechDocsMetadata: jest.fn().mockResolvedValue(mockTechDocsMetadata), }; +const configApiMock = { + getOptionalBoolean: jest.fn().mockReturnValue(undefined), +}; + const wrapper = ({ entityRef = { kind: mockEntityMetadata.kind, @@ -64,7 +69,12 @@ const wrapper = ({ children: React.ReactNode; }) => ( - + {children}