diff --git a/.changeset/beige-apples-press.md b/.changeset/beige-apples-press.md new file mode 100644 index 0000000000..56c46db830 --- /dev/null +++ b/.changeset/beige-apples-press.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-azure-devops': patch +--- + +Added alpha support for the New Frontend System (Declarative Integration) diff --git a/.changeset/calm-onions-exercise.md b/.changeset/calm-onions-exercise.md new file mode 100644 index 0000000000..f1c58a173f --- /dev/null +++ b/.changeset/calm-onions-exercise.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Resolved an issue with generate-catalog-info where it was replacing upper case characters with -. diff --git a/.changeset/fast-onions-own.md b/.changeset/fast-onions-own.md new file mode 100644 index 0000000000..6977b035bc --- /dev/null +++ b/.changeset/fast-onions-own.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +`FetchUrlReader#readUrl()` now supports passing an optional `token` to authenticate requests. diff --git a/.changeset/shiny-clocks-greet.md b/.changeset/shiny-clocks-greet.md new file mode 100644 index 0000000000..0c112ff81f --- /dev/null +++ b/.changeset/shiny-clocks-greet.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-user-settings': patch +--- + +conditionally rendering the user name and email in user settings page diff --git a/.changeset/strong-news-develop.md b/.changeset/strong-news-develop.md new file mode 100644 index 0000000000..1cbe4dda07 --- /dev/null +++ b/.changeset/strong-news-develop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-explore': patch +--- + +Use the EntityDisplayName component for rendering Group nodes diff --git a/.changeset/tall-frogs-clap.md b/.changeset/tall-frogs-clap.md new file mode 100644 index 0000000000..1e87ca316c --- /dev/null +++ b/.changeset/tall-frogs-clap.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-azure-sites-backend': patch +--- + +Added `permissionIntegrationRouter` for azure-sites-backend routes diff --git a/.changeset/wise-papayas-cough.md b/.changeset/wise-papayas-cough.md new file mode 100644 index 0000000000..955e7fefcc --- /dev/null +++ b/.changeset/wise-papayas-cough.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Add a config declaration for `workingDirectory` diff --git a/.husky/pre-commit b/.husky/pre-commit index d2ae35e84b..3723623171 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - yarn lint-staged diff --git a/microsite/data/plugins/umami.yaml b/microsite/data/plugins/umami.yaml new file mode 100644 index 0000000000..5f1fc208d4 --- /dev/null +++ b/microsite/data/plugins/umami.yaml @@ -0,0 +1,10 @@ +--- +title: Umami Analytics +author: AxisCommunications +authorUrl: https://github.com/AxisCommunications +category: Monitoring +description: Track usage of your Backstage instance using Umami Analytics. +documentation: https://github.com/AxisCommunications/backstage-plugins/blob/main/plugins/analytics-module-umami/README.md +iconUrl: /img/umami-logo.svg +npmPackageName: '@axis-backstage/plugin-analytics-module-umami' +addedDate: '2024-02-05' diff --git a/microsite/static/img/umami-logo.svg b/microsite/static/img/umami-logo.svg new file mode 100644 index 0000000000..b139531324 --- /dev/null +++ b/microsite/static/img/umami-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/package.json b/package.json index 6deae7611e..fbe47ec8b4 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,8 @@ "build-storybook": "yarn ./storybook run build-storybook", "techdocs-cli": "node scripts/techdocs-cli.js", "techdocs-cli:dev": "cross-env TECHDOCS_CLI_DEV_MODE=true node scripts/techdocs-cli.js", - "prepare": "husky install", - "postinstall": "husky install || true" + "prepare": "husky", + "postinstall": "husky || true" }, "workspaces": { "packages": [ @@ -85,7 +85,7 @@ "eslint-plugin-react": "^7.28.0", "eslint-plugin-testing-library": "^6.0.0", "fs-extra": "10.1.0", - "husky": "^8.0.0", + "husky": "^9.0.0", "lint-staged": "^15.0.0", "minimist": "^1.2.5", "node-gyp": "^10.0.0", diff --git a/packages/app-next/app-config.yaml b/packages/app-next/app-config.yaml index 2007aaba58..0dd2187418 100644 --- a/packages/app-next/app-config.yaml +++ b/packages/app-next/app-config.yaml @@ -19,8 +19,12 @@ app: - entity-card:catalog-graph/relations: config: height: 300 + - entity-card:azure-devops/readme # Entity page content - entity-content:techdocs + - entity-content:azure-devops/pipelines + - entity-content:azure-devops/pull-requests + - entity-content:azure-devops/git-tags # scmAuthExtension: >- # createScmAuthExtension({ diff --git a/packages/backend-common/config.d.ts b/packages/backend-common/config.d.ts index 8bcc5465d5..700d9a6c6c 100644 --- a/packages/backend-common/config.d.ts +++ b/packages/backend-common/config.d.ts @@ -67,6 +67,22 @@ export interface Config { }; }; + /** + * An absolute path to a directory that can be used as a working dir, for + * example as scratch space for large operations. + * + * @remarks + * + * Note that this must be an absolute path. + * + * If not set, the operating system's designated temporary directory is + * commonly used, but that is implementation defined per plugin. + * + * Plugins are encouraged to heed this config setting if present, to allow + * deployment in severely locked-down or limited environments. + */ + workingDirectory?: string; + /** Database connection configuration, select base database type using the `client` field */ database: { /** Default database client to use */ diff --git a/packages/backend-common/src/reading/FetchUrlReader.test.ts b/packages/backend-common/src/reading/FetchUrlReader.test.ts index ad834c64ef..7b5e4ebc05 100644 --- a/packages/backend-common/src/reading/FetchUrlReader.test.ts +++ b/packages/backend-common/src/reading/FetchUrlReader.test.ts @@ -223,6 +223,27 @@ describe('FetchUrlReader', () => { ).rejects.toThrow(NotModifiedError); }); + it('should send Authorization header if token is provided', async () => { + expect.assertions(1); + + worker.use( + rest.get( + 'https://backstage.io/requires-authentication', + (req, res, ctx) => { + expect(req.headers.get('authorization')).toBe('Bearer mytoken'); + return res(ctx.status(200)); + }, + ), + ); + + await fetchUrlReader.readUrl( + 'https://backstage.io/requires-authentication', + { + token: 'mytoken', + }, + ); + }); + it('should return etag from the response', async () => { const response = await fetchUrlReader.readUrl( 'https://backstage.io/some-resource', diff --git a/packages/backend-common/src/reading/FetchUrlReader.ts b/packages/backend-common/src/reading/FetchUrlReader.ts index 90c1cf6c26..475a98ffdc 100644 --- a/packages/backend-common/src/reading/FetchUrlReader.ts +++ b/packages/backend-common/src/reading/FetchUrlReader.ts @@ -131,6 +131,7 @@ export class FetchUrlReader implements UrlReader { ...(options?.lastModifiedAfter && { 'If-Modified-Since': options.lastModifiedAfter.toUTCString(), }), + ...(options?.token && { Authorization: `Bearer ${options.token}` }), }, // TODO(freben): The signal cast is there because pre-3.x versions of // node-fetch have a very slightly deviating AbortSignal type signature. diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index dff05e9774..51d4bcdf4c 100644 --- a/packages/core-compat-api/package.json +++ b/packages/core-compat-api/package.json @@ -12,6 +12,11 @@ "backstage": { "role": "web-library" }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/core-compat-api" + }, "sideEffects": false, "scripts": { "start": "backstage-cli package start", diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index 7d8009f338..7d5b99d504 100644 --- a/packages/frontend-plugin-api/package.json +++ b/packages/frontend-plugin-api/package.json @@ -12,6 +12,11 @@ "backstage": { "role": "web-library" }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/frontend-plugin-api" + }, "sideEffects": false, "scripts": { "start": "backstage-cli package start", diff --git a/packages/repo-tools/src/commands/generate-catalog-info/generate-catalog-info.ts b/packages/repo-tools/src/commands/generate-catalog-info/generate-catalog-info.ts index b8a473c67b..1893bd25c8 100644 --- a/packages/repo-tools/src/commands/generate-catalog-info/generate-catalog-info.ts +++ b/packages/repo-tools/src/commands/generate-catalog-info/generate-catalog-info.ts @@ -33,6 +33,7 @@ import { isFulfilled, readFile, writeFile, + safeEntityName, } from './utils'; import { CodeOwnersEntry } from 'codeowners-utils'; @@ -164,9 +165,7 @@ async function fixCatalogInfoYaml(options: FixOptions) { codeowners, relativePath('.', yamlPath), ); - const safeName = packageJson.name - .replace(/[^a-z0-9_\-\.]+/g, '-') - .replace(/^[^a-z0-9]|[^a-z0-9]$/g, ''); + const safeName = safeEntityName(packageJson.name); let yamlJson: BackstagePackageEntity; try { @@ -240,9 +239,7 @@ function createOrMergeEntity( owner: string, existingEntity: BackstagePackageEntity | Record = {}, ): BackstagePackageEntity { - const safeEntityName = packageJson.name - .replace(/[^a-z0-9_\-\.]+/g, '-') - .replace(/^[^a-z0-9]|[^a-z0-9]$/g, ''); + const entityName = safeEntityName(packageJson.name); return { ...existingEntity, @@ -251,7 +248,7 @@ function createOrMergeEntity( metadata: { ...existingEntity.metadata, // Provide default name/title/description values. - name: safeEntityName, + name: entityName, title: packageJson.name, ...(packageJson.description && !existingEntity.metadata?.description ? { description: packageJson.description } diff --git a/packages/repo-tools/src/commands/generate-catalog-info/utils.test.ts b/packages/repo-tools/src/commands/generate-catalog-info/utils.test.ts new file mode 100644 index 0000000000..40c1bb1e19 --- /dev/null +++ b/packages/repo-tools/src/commands/generate-catalog-info/utils.test.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2024 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 { safeEntityName } from './utils'; + +describe('utils', () => { + describe('safeEntityName', () => { + it('should remove non-alphanumeric characters at the start and end', () => { + const result = safeEntityName('%entityname$'); + expect(result).toBe('entityname'); + }); + + it('should replace non-alphanumeric characters, except - and _, with -', () => { + const result = safeEntityName('entity@#name$'); + expect(result).toBe('entity-name'); + }); + + it('should replace capital letters with - followed by the same letter in lowercase', () => { + const result = safeEntityName('EntityName'); + expect(result).toBe('entity-name'); + }); + }); +}); diff --git a/packages/repo-tools/src/commands/generate-catalog-info/utils.ts b/packages/repo-tools/src/commands/generate-catalog-info/utils.ts index b00fbd57a8..3f23b35112 100644 --- a/packages/repo-tools/src/commands/generate-catalog-info/utils.ts +++ b/packages/repo-tools/src/commands/generate-catalog-info/utils.ts @@ -48,3 +48,20 @@ export const isRejected = ( export const isFulfilled = ( input: PromiseSettledResult, ): input is PromiseFulfilledResult => input.status === 'fulfilled'; + +/** + * Generates a suitable entity name from a package name by slugifying the given package name. + * + * @param packageName - The package name to generate an entity name from. + * @returns The generated entity name, a slugified version of the package name. + */ +export const safeEntityName = (packageName: string): string => { + return packageName + .replace(/^[^\w\s]|[^a-z0-9]$/g, '') + .replace(/[^A-Za-z0-9_\-.]+/g, '-') + .replace( + /([a-z])([A-Z])/g, + (_, a, b) => `${a}-${b.toLocaleLowerCase('en-US')}`, + ) + .replace(/^(.)/, (_, a) => a.toLocaleLowerCase('en-US')); +}; diff --git a/plugins/azure-devops/api-report-alpha.md b/plugins/azure-devops/api-report-alpha.md new file mode 100644 index 0000000000..8040c0235f --- /dev/null +++ b/plugins/azure-devops/api-report-alpha.md @@ -0,0 +1,13 @@ +## API Report File for "@backstage/plugin-azure-devops" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BackstagePlugin } from '@backstage/frontend-plugin-api'; + +// @alpha (undocumented) +const _default: BackstagePlugin<{}, {}>; +export default _default; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json index e0cf441155..84024646dd 100644 --- a/plugins/azure-devops/package.json +++ b/plugins/azure-devops/package.json @@ -5,9 +5,22 @@ "types": "src/index.ts", "license": "Apache-2.0", "publishConfig": { - "access": "public", - "main": "dist/index.esm.js", - "types": "dist/index.d.ts" + "access": "public" + }, + "exports": { + ".": "./src/index.ts", + "./alpha": "./src/alpha.ts", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "alpha": [ + "src/alpha.ts" + ], + "package.json": [ + "package.json" + ] + } }, "backstage": { "role": "frontend-plugin" @@ -30,9 +43,11 @@ }, "dependencies": { "@backstage/catalog-model": "workspace:^", + "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", "@backstage/plugin-azure-devops-common": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@material-ui/core": "^4.12.2", diff --git a/plugins/azure-devops/src/alpha.ts b/plugins/azure-devops/src/alpha.ts new file mode 100644 index 0000000000..e80f131817 --- /dev/null +++ b/plugins/azure-devops/src/alpha.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2023 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. + */ + +export * from './alpha/index'; +export { default } from './alpha/index'; diff --git a/plugins/azure-devops/src/alpha/index.ts b/plugins/azure-devops/src/alpha/index.ts new file mode 100644 index 0000000000..2f137f09ee --- /dev/null +++ b/plugins/azure-devops/src/alpha/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2023 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. + */ + +export { default } from './plugin'; diff --git a/plugins/azure-devops/src/alpha/plugin.tsx b/plugins/azure-devops/src/alpha/plugin.tsx new file mode 100644 index 0000000000..40305c1714 --- /dev/null +++ b/plugins/azure-devops/src/alpha/plugin.tsx @@ -0,0 +1,111 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { + createApiExtension, + createApiFactory, + createPageExtension, + createPlugin, + discoveryApiRef, + identityApiRef, +} from '@backstage/frontend-plugin-api'; +import { azureDevOpsApiRef, AzureDevOpsClient } from '../api'; +import { + compatWrapper, + convertLegacyRouteRef, +} from '@backstage/core-compat-api'; +import { + createEntityCardExtension, + createEntityContentExtension, +} from '@backstage/plugin-catalog-react/alpha'; +import { azurePullRequestDashboardRouteRef } from '../routes'; + +/** @alpha */ +export const azureDevOpsApi = createApiExtension({ + factory: createApiFactory({ + api: azureDevOpsApiRef, + deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef }, + factory: ({ discoveryApi, identityApi }) => + new AzureDevOpsClient({ discoveryApi, identityApi }), + }), +}); + +/** @alpha */ +export const azureDevOpsPullRequestPage = createPageExtension({ + defaultPath: '/azure-pull-requests', + routeRef: convertLegacyRouteRef(azurePullRequestDashboardRouteRef), + loader: () => + import('../components/PullRequestsPage').then(m => + compatWrapper(), + ), +}); + +/** @alpha */ +export const azureDevOpsPipelinesEntityContent = createEntityContentExtension({ + name: 'pipelines', + defaultPath: '/pipelines', + defaultTitle: 'Pipelines', + loader: () => + import('../components/EntityPageAzurePipelines').then(m => + compatWrapper(), + ), +}); + +/** @alpha */ +export const azureDevOpsGitTagsEntityContent = createEntityContentExtension({ + name: 'git-tags', + defaultPath: '/git-tags', + defaultTitle: 'Git Tags', + loader: () => + import('../components/EntityPageAzureGitTags').then(m => + compatWrapper(), + ), +}); + +/** @alpha */ +export const azureDevOpsPullRequestsEntityContent = + createEntityContentExtension({ + name: 'pull-requests', + defaultPath: '/pull-requests', + defaultTitle: 'Pull Requests', + loader: () => + import('../components/EntityPageAzurePullRequests').then(m => + compatWrapper(), + ), + }); + +/** @alpha */ +export const azureDevOpsReadmeEntityCard = createEntityCardExtension({ + name: 'readme', + loader: async () => + import('../components/ReadmeCard').then(m => + compatWrapper(), + ), +}); + +/** @alpha */ +export default createPlugin({ + id: 'azure-devops', + extensions: [ + azureDevOpsApi, + azureDevOpsReadmeEntityCard, + azureDevOpsPipelinesEntityContent, + azureDevOpsGitTagsEntityContent, + azureDevOpsPullRequestsEntityContent, + azureDevOpsPullRequestPage, + ], +}); diff --git a/plugins/azure-sites-backend/src/service/router.ts b/plugins/azure-sites-backend/src/service/router.ts index 9ed198cc20..bd5d20c05e 100644 --- a/plugins/azure-sites-backend/src/service/router.ts +++ b/plugins/azure-sites-backend/src/service/router.ts @@ -27,8 +27,10 @@ import { } from '@backstage/plugin-permission-common'; import { azureSitesActionPermission, + azureSitesPermissions, AZURE_WEB_SITE_NAME_ANNOTATION, } from '@backstage/plugin-azure-sites-common'; +import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; import { CatalogApi } from '@backstage/catalog-client'; import { AzureSitesApi } from '../api'; @@ -47,8 +49,13 @@ export async function createRouter( ): Promise { const { logger, azureSitesApi, permissions, catalogApi } = options; + const permissionIntegrationRouter = createPermissionIntegrationRouter({ + permissions: azureSitesPermissions, + }); + const router = Router(); router.use(express.json()); + router.use(permissionIntegrationRouter); router.get('/health', (_, response) => { logger.info('PONG!'); diff --git a/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.test.tsx b/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.test.tsx index 7c57de84bc..1329d3c522 100644 --- a/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.test.tsx +++ b/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.test.tsx @@ -21,6 +21,7 @@ import { } from '@backstage/plugin-catalog-react'; import { Entity } from '@backstage/catalog-model'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { screen } from '@testing-library/react'; import React from 'react'; import { GroupsDiagram } from './GroupsDiagram'; @@ -55,7 +56,7 @@ describe('', () => { }), }; - const { getByText } = await renderInTestApp( + await renderInTestApp( , @@ -65,6 +66,9 @@ describe('', () => { }, }, ); - expect(getByText('Group A', { selector: 'div' })).toBeInTheDocument(); + + expect( + screen.getByRole('link', { name: 'group:my-namespace/group-a' }), + ).toBeInTheDocument(); }); }); diff --git a/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.tsx b/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.tsx index f7f0eab0bd..dd0705908a 100644 --- a/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.tsx +++ b/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.tsx @@ -15,7 +15,6 @@ */ import { - GroupEntity, parseEntityRef, RELATION_CHILD_OF, stringifyEntityRef, @@ -31,8 +30,8 @@ import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api'; import { catalogApiRef, entityRouteRef, - humanizeEntityRef, getEntityRelations, + EntityDisplayName, } from '@backstage/plugin-catalog-react'; import { makeStyles, Typography, useTheme } from '@material-ui/core'; import ZoomOutMap from '@material-ui/icons/ZoomOutMap'; @@ -140,7 +139,9 @@ function RenderNode(props: DependencyGraphTypes.RenderNodeProps) { rx={theme.shape.borderRadius} className={classes.groupNode} /> - {props.node.name} + + <EntityDisplayName entityRef={props.node.id} hideIcon disableTooltip /> + ) {
- {props.node.name} +
@@ -210,9 +211,7 @@ export function GroupsDiagram(props: { nodes.push({ id: stringifyEntityRef(catalogItem), kind: catalogItem.kind, - name: - (catalogItem as GroupEntity).spec?.profile?.displayName || - humanizeEntityRef(catalogItem, { defaultKind: 'Group' }), + name: '', }); // Edge to parent diff --git a/plugins/explore/src/components/GroupsExplorerContent/GroupsExplorerContent.test.tsx b/plugins/explore/src/components/GroupsExplorerContent/GroupsExplorerContent.test.tsx index 9b461fba7d..00a6164aa9 100644 --- a/plugins/explore/src/components/GroupsExplorerContent/GroupsExplorerContent.test.tsx +++ b/plugins/explore/src/components/GroupsExplorerContent/GroupsExplorerContent.test.tsx @@ -17,7 +17,7 @@ import { Entity } from '@backstage/catalog-model'; import { catalogApiRef, entityRouteRef } from '@backstage/plugin-catalog-react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; -import { waitFor } from '@testing-library/react'; +import { waitFor, screen } from '@testing-library/react'; import React from 'react'; import { GroupsExplorerContent } from '../GroupsExplorerContent'; @@ -73,24 +73,24 @@ describe('', () => { ]; catalogApi.getEntities.mockResolvedValue({ items: entities }); - const { getByText } = await renderInTestApp( + await renderInTestApp( , mountedRoutes, ); - await waitFor(() => { + await waitFor(() => expect( - getByText('my-namespace/group-a', { selector: 'div' }), - ).toBeInTheDocument(); - }); + screen.getByRole('link', { name: 'group:my-namespace/group-a' }), + ).toBeInTheDocument(), + ); }); it('renders a custom title', async () => { catalogApi.getEntities.mockResolvedValue({ items: [] }); - const { getByText } = await renderInTestApp( + await renderInTestApp( , @@ -98,7 +98,9 @@ describe('', () => { ); await waitFor(() => - expect(getByText('Our Teams', { selector: 'h2' })).toBeInTheDocument(), + expect( + screen.getByText('Our Teams', { selector: 'h2' }), + ).toBeInTheDocument(), ); }); @@ -106,7 +108,7 @@ describe('', () => { const catalogError = new Error('Network timeout'); catalogApi.getEntities.mockRejectedValueOnce(catalogError); - const { getAllByText } = await renderInTestApp( + await renderInTestApp( , @@ -114,7 +116,7 @@ describe('', () => { ); await waitFor(() => - expect(getAllByText(/Error: Network timeout/).length).not.toBe(0), + expect(screen.getAllByText(/Error: Network timeout/).length).not.toBe(0), ); }); }); diff --git a/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.test.tsx b/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.test.tsx index 9ff1dd1b17..ec4e4e1b0a 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.test.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.test.tsx @@ -358,12 +358,11 @@ describe('TemplateCard', () => { }, ); - expect( - getByRole('link', { name: 'group:default/my-test-user' }), - ).toBeInTheDocument(); - expect( - getByRole('link', { name: 'group:default/my-test-user' }), - ).toHaveAttribute('href', '/catalog/group/default/my-test-user'); + expect(getByRole('link', { name: /.*my-test-user$/ })).toBeInTheDocument(); + expect(getByRole('link', { name: /.*my-test-user$/ })).toHaveAttribute( + 'href', + '/catalog/group/default/my-test-user', + ); }); it('should call the onSelected handler when clicking the choose button', async () => { diff --git a/plugins/user-settings/src/components/AuthProviders/ProviderSettingsItem.tsx b/plugins/user-settings/src/components/AuthProviders/ProviderSettingsItem.tsx index e3fb597a15..e9a710036f 100644 --- a/plugins/user-settings/src/components/AuthProviders/ProviderSettingsItem.tsx +++ b/plugins/user-settings/src/components/AuthProviders/ProviderSettingsItem.tsx @@ -101,16 +101,20 @@ export const ProviderSettingsItem = (props: { - - {profile.displayName} - - - {profile.email} - + {profile.displayName && ( + + {profile.displayName} + + )} + {profile.email && ( + + {profile.email} + + )} {description} diff --git a/yarn.lock b/yarn.lock index 5cd5a0cbf5..df56bc2fbd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5052,10 +5052,12 @@ __metadata: dependencies: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" + "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" "@backstage/errors": "workspace:^" + "@backstage/frontend-plugin-api": "workspace:^" "@backstage/plugin-azure-devops-common": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" "@backstage/test-utils": "workspace:^" @@ -29841,12 +29843,12 @@ __metadata: languageName: node linkType: hard -"husky@npm:^8.0.0": - version: 8.0.3 - resolution: "husky@npm:8.0.3" +"husky@npm:^9.0.0": + version: 9.0.10 + resolution: "husky@npm:9.0.10" bin: - husky: lib/bin.js - checksum: 837bc7e4413e58c1f2946d38fb050f5d7324c6f16b0fd66411ffce5703b294bd21429e8ba58711cd331951ee86ed529c5be4f76805959ff668a337dbfa82a1b0 + husky: bin.mjs + checksum: 55f4b6db6706ff0bc181607d6a64f55310cbb18b4d7db2a5b85608c87a80abafc14ac3a8c4d0b44d6272f4a62d4c2d3d5491d6a13e2cc7ac4895309e5e5f0ec2 languageName: node linkType: hard @@ -40405,7 +40407,7 @@ __metadata: eslint-plugin-react: ^7.28.0 eslint-plugin-testing-library: ^6.0.0 fs-extra: 10.1.0 - husky: ^8.0.0 + husky: ^9.0.0 lint-staged: ^15.0.0 minimist: ^1.2.5 node-gyp: ^10.0.0