From 51fbedc4451980532ae8b8543b4e4a3bd7efd506 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 4 Jan 2022 23:08:05 +0100 Subject: [PATCH] plugins: migrate usage of deprecated IdentityApi methods Signed-off-by: Patrik Oldsberg --- .changeset/new-mice-brush.md | 25 ++++++ .../tutorials/authenticate-api-requests.md | 2 +- .../azure-devops/src/api/AzureDevOpsClient.ts | 2 +- .../azure-devops/src/hooks/useUserEmail.ts | 4 +- plugins/badges/src/api/BadgesClient.ts | 2 +- plugins/bazaar/src/api.ts | 5 +- .../src/api/CatalogImportClient.test.ts | 11 +-- .../src/api/CatalogImportClient.ts | 4 +- .../src/hooks/useEntityListProvider.test.tsx | 8 +- plugins/catalog-react/src/hooks/useOwnUser.ts | 26 +++--- .../catalog/src/CatalogClientWrapper.test.ts | 30 +------ plugins/catalog/src/CatalogClientWrapper.ts | 79 ++++++++++++------- .../CatalogPage/DefaultCatalogPage.test.tsx | 10 ++- plugins/cost-insights/package.json | 1 + .../CostInsightsHeader.test.tsx | 2 +- .../CostInsightsHeader/CostInsightsHeader.tsx | 32 ++++---- plugins/cost-insights/src/hooks/useGroups.tsx | 13 ++- plugins/fossa/src/api/FossaClient.test.ts | 4 +- plugins/fossa/src/api/FossaClient.ts | 2 +- .../Incident/IncidentActionsMenu.tsx | 16 +++- .../components/Incident/IncidentNewModal.tsx | 10 ++- plugins/kafka/src/api/KafkaBackendClient.ts | 2 +- .../src/api/KubernetesBackendClient.ts | 4 +- .../components/TriggerButton/index.test.tsx | 11 +-- .../TriggerDialog/TriggerDialog.test.tsx | 8 +- .../TriggerDialog/TriggerDialog.tsx | 19 +++-- .../src/apis/IdentityPermissionApi.ts | 7 +- plugins/rollbar/src/api/RollbarClient.ts | 2 +- plugins/scaffolder/src/api.ts | 8 +- plugins/search/src/apis.ts | 2 +- plugins/sentry/src/api/production-api.ts | 2 +- .../sonarqube/src/api/SonarQubeClient.test.ts | 30 +------ plugins/sonarqube/src/api/SonarQubeClient.ts | 2 +- .../components/Incident/Incidents.test.tsx | 11 +-- .../src/api/TechInsightsClient.ts | 4 +- plugins/techdocs/src/client.test.ts | 6 +- plugins/techdocs/src/client.ts | 8 +- plugins/todo/src/api/TodoClient.ts | 2 +- 38 files changed, 221 insertions(+), 195 deletions(-) create mode 100644 .changeset/new-mice-brush.md diff --git a/.changeset/new-mice-brush.md b/.changeset/new-mice-brush.md new file mode 100644 index 0000000000..b06050c88b --- /dev/null +++ b/.changeset/new-mice-brush.md @@ -0,0 +1,25 @@ +--- +'@backstage/plugin-azure-devops': patch +'@backstage/plugin-badges': patch +'@backstage/plugin-bazaar': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-cost-insights': patch +'@backstage/plugin-fossa': patch +'@backstage/plugin-ilert': patch +'@backstage/plugin-kafka': patch +'@backstage/plugin-kubernetes': patch +'@backstage/plugin-pagerduty': patch +'@backstage/plugin-permission-react': patch +'@backstage/plugin-rollbar': patch +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-search': patch +'@backstage/plugin-sentry': patch +'@backstage/plugin-sonarqube': patch +'@backstage/plugin-tech-insights': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-todo': patch +--- + +Migrated usage of deprecated `IdentityApi` methods. diff --git a/contrib/docs/tutorials/authenticate-api-requests.md b/contrib/docs/tutorials/authenticate-api-requests.md index 803a6babc7..25dd08e054 100644 --- a/contrib/docs/tutorials/authenticate-api-requests.md +++ b/contrib/docs/tutorials/authenticate-api-requests.md @@ -218,7 +218,7 @@ export class MyApi implements MyInterface { async getMyData() { const backendUrl = this.configApi.getString('backend.baseUrl'); -+ const token = await this.identityApi.getIdToken(); ++ const { token } = await this.identityApi.getCredentials(); const requestUrl = `${backendUrl}/api/data/`; - const response = await fetch(requestUrl); + const response = await fetch( diff --git a/plugins/azure-devops/src/api/AzureDevOpsClient.ts b/plugins/azure-devops/src/api/AzureDevOpsClient.ts index 42c5ac3f6c..84ade8800d 100644 --- a/plugins/azure-devops/src/api/AzureDevOpsClient.ts +++ b/plugins/azure-devops/src/api/AzureDevOpsClient.ts @@ -96,7 +96,7 @@ export class AzureDevOpsClient implements AzureDevOpsApi { const baseUrl = `${await this.discoveryApi.getBaseUrl('azure-devops')}/`; const url = new URL(path, baseUrl); - const idToken = await this.identityApi.getIdToken(); + const { token: idToken } = await this.identityApi.getCredentials(); const response = await fetch(url.toString(), { headers: idToken ? { Authorization: `Bearer ${idToken}` } : {}, }); diff --git a/plugins/azure-devops/src/hooks/useUserEmail.ts b/plugins/azure-devops/src/hooks/useUserEmail.ts index 4655815297..34f39f4db9 100644 --- a/plugins/azure-devops/src/hooks/useUserEmail.ts +++ b/plugins/azure-devops/src/hooks/useUserEmail.ts @@ -15,8 +15,10 @@ */ import { identityApiRef, useApi } from '@backstage/core-plugin-api'; +import useAsync from 'react-use/lib/useAsync'; export function useUserEmail(): string | undefined { const identityApi = useApi(identityApiRef); - return identityApi.getProfile().email; + const state = useAsync(() => identityApi.getProfileInfo(), [identityApi]); + return state.value?.email; } diff --git a/plugins/badges/src/api/BadgesClient.ts b/plugins/badges/src/api/BadgesClient.ts index 8ad5d313e4..68714b2fa4 100644 --- a/plugins/badges/src/api/BadgesClient.ts +++ b/plugins/badges/src/api/BadgesClient.ts @@ -34,7 +34,7 @@ export class BadgesClient implements BadgesApi { public async getEntityBadgeSpecs(entity: Entity): Promise { const entityBadgeSpecsUrl = await this.getEntityBadgeSpecsUrl(entity); - const token = await this.identityApi.getIdToken(); + const { token } = await this.identityApi.getCredentials(); const response = await fetch(entityBadgeSpecsUrl, { headers: token ? { diff --git a/plugins/bazaar/src/api.ts b/plugins/bazaar/src/api.ts index cae890ea4a..2d661506bc 100644 --- a/plugins/bazaar/src/api.ts +++ b/plugins/bazaar/src/api.ts @@ -121,6 +121,7 @@ export class BazaarClient implements BazaarApi { async addMember(id: number, userId: string): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('bazaar'); + const { picture } = await this.identityApi.getProfileInfo(); await fetch( `${baseUrl}/projects/${encodeURIComponent( @@ -132,9 +133,7 @@ export class BazaarClient implements BazaarApi { Accept: 'application/json', 'Content-Type': 'application/json', }, - body: JSON.stringify({ - picture: (await this.identityApi.getProfileInfo()).picture, - }), + body: JSON.stringify({ picture }), }, ); } diff --git a/plugins/catalog-import/src/api/CatalogImportClient.test.ts b/plugins/catalog-import/src/api/CatalogImportClient.test.ts index 6154eec757..202a7aba85 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.test.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.test.ts @@ -67,21 +67,12 @@ describe('CatalogImportClient', () => { getCredentials: jest.fn().mockResolvedValue({ token: 'token' }), }; const identityApi = { - getUserId: () => { - return 'user'; - }, - getProfile: () => { - return {}; - }, - getIdToken: () => { - return Promise.resolve('token'); - }, signOut: () => { return Promise.resolve(); }, getProfileInfo: jest.fn(), getBackstageIdentity: jest.fn(), - getCredentials: jest.fn(), + getCredentials: jest.fn().mockResolvedValue({ token: 'token' }), }; const scmIntegrationsApi = ScmIntegrations.fromConfig( diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index 752899e6e8..bc59657cad 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -173,13 +173,13 @@ the component will become available.\n\nFor more information, read an \ }: { repo: string; }): Promise { - const idToken = await this.identityApi.getIdToken(); + const { token } = await this.identityApi.getCredentials(); const response = await fetch( `${await this.discoveryApi.getBaseUrl('catalog')}/analyze-location`, { headers: { 'Content-Type': 'application/json', - ...(idToken && { Authorization: `Bearer ${idToken}` }), + ...(token && { Authorization: `Bearer ${token}` }), }, method: 'POST', body: JSON.stringify({ diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx index fd5cf3b9dd..a1e722bc6d 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx @@ -68,8 +68,12 @@ const mockConfigApi = { getOptionalString: () => '', } as Partial; const mockIdentityApi: Partial = { - getUserId: () => 'guest', - getIdToken: async () => undefined, + getBackstageIdentity: async () => ({ + type: 'user', + userEntityRef: 'user:default/guest', + ownershipEntityRefs: [], + }), + getCredentials: async () => ({ token: undefined }), }; const mockCatalogApi: Partial = { getEntities: jest.fn().mockImplementation(async () => ({ items: entities })), diff --git a/plugins/catalog-react/src/hooks/useOwnUser.ts b/plugins/catalog-react/src/hooks/useOwnUser.ts index 3df51f885f..f49eb768d0 100644 --- a/plugins/catalog-react/src/hooks/useOwnUser.ts +++ b/plugins/catalog-react/src/hooks/useOwnUser.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { UserEntity } from '@backstage/catalog-model'; +import { + ENTITY_DEFAULT_NAMESPACE, + parseEntityRef, + UserEntity, +} from '@backstage/catalog-model'; import useAsync, { AsyncState } from 'react-use/lib/useAsync'; import { catalogApiRef } from '../api'; import { identityApiRef, useApi } from '@backstage/core-plugin-api'; @@ -26,15 +30,13 @@ export function useOwnUser(): AsyncState { const catalogApi = useApi(catalogApiRef); const identityApi = useApi(identityApiRef); - // TODO: get the full entity (or at least the full entity name) from the - // identityApi - return useAsync( - () => - catalogApi.getEntityByName({ - kind: 'User', - namespace: 'default', - name: identityApi.getUserId(), - }) as Promise, - [catalogApi, identityApi], - ); + return useAsync(async () => { + const identity = await identityApi.getBackstageIdentity(); + return catalogApi.getEntityByName( + parseEntityRef(identity.userEntityRef, { + defaultKind: 'User', + defaultNamespace: ENTITY_DEFAULT_NAMESPACE, + }), + ) as Promise; + }, [catalogApi, identityApi]); } diff --git a/plugins/catalog/src/CatalogClientWrapper.test.ts b/plugins/catalog/src/CatalogClientWrapper.test.ts index 6917b5abed..e1bba9e60f 100644 --- a/plugins/catalog/src/CatalogClientWrapper.test.ts +++ b/plugins/catalog/src/CatalogClientWrapper.test.ts @@ -28,38 +28,16 @@ const discoveryApi: DiscoveryApi = { }, }; const identityApi: IdentityApi = { - getUserId() { - return 'jane-fonda'; - }, - getProfile() { - return { email: 'jane-fonda@spotify.com' }; - }, - async getIdToken() { - return Promise.resolve('fake-id-token'); - }, - async signOut() { - return Promise.resolve(); - }, + signOut: jest.fn(), getProfileInfo: jest.fn(), getBackstageIdentity: jest.fn(), - getCredentials: jest.fn(), + getCredentials: jest.fn().mockResolvedValue({ token: 'fake-id-token' }), }; const guestIdentityApi: IdentityApi = { - getUserId() { - return 'guest'; - }, - getProfile() { - return {}; - }, - async getIdToken() { - return Promise.resolve(undefined); - }, - async signOut() { - return Promise.resolve(); - }, + signOut: jest.fn(), getProfileInfo: jest.fn(), getBackstageIdentity: jest.fn(), - getCredentials: jest.fn(), + getCredentials: jest.fn().mockResolvedValue({ token: undefined }), }; describe('CatalogClientWrapper', () => { diff --git a/plugins/catalog/src/CatalogClientWrapper.ts b/plugins/catalog/src/CatalogClientWrapper.ts index c5f072e96c..939c4f095b 100644 --- a/plugins/catalog/src/CatalogClientWrapper.ts +++ b/plugins/catalog/src/CatalogClientWrapper.ts @@ -51,89 +51,108 @@ export class CatalogClientWrapper implements CatalogApi { id: string, options?: CatalogRequestOptions, ): Promise { - return await this.client.getLocationById(id, { - token: options?.token ?? (await this.identityApi.getIdToken()), - }); + return await this.client.getLocationById( + id, + await this.getCredentials(options), + ); } async getEntities( request?: CatalogEntitiesRequest, options?: CatalogRequestOptions, ): Promise> { - return await this.client.getEntities(request, { - token: options?.token ?? (await this.identityApi.getIdToken()), - }); + return await this.client.getEntities( + request, + await this.getCredentials(options), + ); } async getEntityByName( compoundName: EntityName, options?: CatalogRequestOptions, ): Promise { - return await this.client.getEntityByName(compoundName, { - token: options?.token ?? (await this.identityApi.getIdToken()), - }); + return await this.client.getEntityByName( + compoundName, + await this.getCredentials(options), + ); } async addLocation( request: AddLocationRequest, options?: CatalogRequestOptions, ): Promise { - return await this.client.addLocation(request, { - token: options?.token ?? (await this.identityApi.getIdToken()), - }); + return await this.client.addLocation( + request, + await this.getCredentials(options), + ); } async getOriginLocationByEntity( entity: Entity, options?: CatalogRequestOptions, ): Promise { - return await this.client.getOriginLocationByEntity(entity, { - token: options?.token ?? (await this.identityApi.getIdToken()), - }); + return await this.client.getOriginLocationByEntity( + entity, + await this.getCredentials(options), + ); } async getLocationByEntity( entity: Entity, options?: CatalogRequestOptions, ): Promise { - return await this.client.getLocationByEntity(entity, { - token: options?.token ?? (await this.identityApi.getIdToken()), - }); + return await this.client.getLocationByEntity( + entity, + await this.getCredentials(options), + ); } async removeLocationById( id: string, options?: CatalogRequestOptions, ): Promise { - return await this.client.removeLocationById(id, { - token: options?.token ?? (await this.identityApi.getIdToken()), - }); + return await this.client.removeLocationById( + id, + await this.getCredentials(options), + ); } async removeEntityByUid( uid: string, options?: CatalogRequestOptions, ): Promise { - return await this.client.removeEntityByUid(uid, { - token: options?.token ?? (await this.identityApi.getIdToken()), - }); + return await this.client.removeEntityByUid( + uid, + await this.getCredentials(options), + ); } async refreshEntity( entityRef: string, options?: CatalogRequestOptions, ): Promise { - return await this.client.refreshEntity(entityRef, { - token: options?.token ?? (await this.identityApi.getIdToken()), - }); + return await this.client.refreshEntity( + entityRef, + await this.getCredentials(options), + ); } async getEntityAncestors( request: CatalogEntityAncestorsRequest, options?: CatalogRequestOptions, ): Promise { - return await this.client.getEntityAncestors(request, { - token: options?.token ?? (await this.identityApi.getIdToken()), - }); + return await this.client.getEntityAncestors( + request, + await this.getCredentials(options), + ); + } + + private async getCredentials( + options?: CatalogRequestOptions, + ): Promise<{ token?: string }> { + if (options?.token) { + return { token: options?.token }; + } + return this.identityApi.getCredentials(); } } diff --git a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.test.tsx index 5f21ba364b..135a2a590d 100644 --- a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.test.tsx @@ -120,9 +120,13 @@ describe('DefaultCatalogPage', () => { displayName: 'Display Name', }; const identityApi: Partial = { - getUserId: () => 'tools', - getIdToken: async () => undefined, - getProfile: () => testProfile, + getBackstageIdentity: async () => ({ + type: 'user', + userEntityRef: 'user:default/guest', + ownershipEntityRefs: ['user:default/guest', 'group:default/tools'], + }), + getCredentials: async () => ({ token: undefined }), + getProfileInfo: async () => testProfile, }; const storageApi = MockStorageApi.create(); diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index 2e7ec2bad6..e24f45fd06 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -31,6 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { + "@backstage/catalog-model": "^0.9.9", "@backstage/config": "^0.1.12", "@backstage/core-components": "^0.8.4", "@backstage/core-plugin-api": "^0.5.0", diff --git a/plugins/cost-insights/src/components/CostInsightsHeader/CostInsightsHeader.test.tsx b/plugins/cost-insights/src/components/CostInsightsHeader/CostInsightsHeader.test.tsx index e29bb78f2f..cccf8b60cd 100644 --- a/plugins/cost-insights/src/components/CostInsightsHeader/CostInsightsHeader.test.tsx +++ b/plugins/cost-insights/src/components/CostInsightsHeader/CostInsightsHeader.test.tsx @@ -23,7 +23,7 @@ import { IdentityApi, identityApiRef } from '@backstage/core-plugin-api'; describe('', () => { const identityApi: Partial = { - getProfile: () => ({ + getProfileInfo: async () => ({ email: 'test-email@example.com', displayName: 'User 1', }), diff --git a/plugins/cost-insights/src/components/CostInsightsHeader/CostInsightsHeader.tsx b/plugins/cost-insights/src/components/CostInsightsHeader/CostInsightsHeader.tsx index 3d94ec19a2..cc68f79db1 100644 --- a/plugins/cost-insights/src/components/CostInsightsHeader/CostInsightsHeader.tsx +++ b/plugins/cost-insights/src/components/CostInsightsHeader/CostInsightsHeader.tsx @@ -16,16 +16,15 @@ import React from 'react'; import { Typography } from '@material-ui/core'; +import useAsync from 'react-use/lib/useAsync'; import { useCostInsightsStyles } from '../../utils/styles'; import { Group } from '../../types'; -import { - identityApiRef, - ProfileInfo, - useApi, -} from '@backstage/core-plugin-api'; +import { identityApiRef, useApi } from '@backstage/core-plugin-api'; -function name(profile: ProfileInfo | undefined): string { - return profile?.displayName || 'Mysterious Stranger'; +function useDisplayName(): string { + const identityApi = useApi(identityApiRef); + const state = useAsync(() => identityApi.getProfileInfo(), [identityApi]); + return state.loading ? '' : state.value?.displayName || 'Mysterious Stranger'; } type CostInsightsHeaderProps = { @@ -39,7 +38,7 @@ const CostInsightsHeaderNoData = ({ owner, groups, }: CostInsightsHeaderProps) => { - const profile = useApi(identityApiRef).getProfile(); + const displayName = useDisplayName(); const classes = useCostInsightsStyles(); const hasMultipleGroups = groups.length > 1; @@ -52,8 +51,8 @@ const CostInsightsHeaderNoData = ({ Well this is awkward - Hey, {name(profile)}! {owner} doesn't seem to have any - cloud costs. + Hey, {displayName}! {owner} doesn't seem to have any cloud + costs. {hasMultipleGroups && ( @@ -68,7 +67,7 @@ const CostInsightsHeaderAlerts = ({ owner, alerts, }: CostInsightsHeaderProps) => { - const profile = useApi(identityApiRef).getProfile(); + const displayName = useDisplayName(); const classes = useCostInsightsStyles(); return ( @@ -80,7 +79,7 @@ const CostInsightsHeaderAlerts = ({ You have {alerts} thing{alerts > 1 && 's'} to look into - Hey, {name(profile)}! We've identified{' '} + Hey, {displayName}! We've identified{' '} {alerts > 1 ? 'a few things ' : 'one thing '} {owner} should look into next. @@ -89,7 +88,7 @@ const CostInsightsHeaderAlerts = ({ }; const CostInsightsHeaderNoAlerts = ({ owner }: CostInsightsHeaderProps) => { - const profile = useApi(identityApiRef).getProfile(); + const displayName = useDisplayName(); const classes = useCostInsightsStyles(); return ( @@ -101,7 +100,7 @@ const CostInsightsHeaderNoAlerts = ({ owner }: CostInsightsHeaderProps) => { Your team is doing great - Hey, {name(profile)}! {owner} is doing well. No major + Hey, {displayName}! {owner} is doing well. No major changes this month. @@ -109,7 +108,7 @@ const CostInsightsHeaderNoAlerts = ({ owner }: CostInsightsHeaderProps) => { }; export const CostInsightsHeaderNoGroups = () => { - const profile = useApi(identityApiRef).getProfile(); + const displayName = useDisplayName(); const classes = useCostInsightsStyles(); return ( <> @@ -120,8 +119,7 @@ export const CostInsightsHeaderNoGroups = () => { Well this is awkward - Hey, {name(profile)}! It doesn't look like you belong to any - teams. + Hey, {displayName}! It doesn't look like you belong to any teams. ); diff --git a/plugins/cost-insights/src/hooks/useGroups.tsx b/plugins/cost-insights/src/hooks/useGroups.tsx index 6cbd0f7cd1..6d20f435b3 100644 --- a/plugins/cost-insights/src/hooks/useGroups.tsx +++ b/plugins/cost-insights/src/hooks/useGroups.tsx @@ -26,6 +26,10 @@ import { MapLoadingToProps, useLoading } from './useLoading'; import { Group, Maybe } from '../types'; import { DefaultLoadingAction } from '../utils/loading'; import { useApi, identityApiRef } from '@backstage/core-plugin-api'; +import { + ENTITY_DEFAULT_NAMESPACE, + parseEntityRef, +} from '@backstage/catalog-model'; type GroupsProviderLoadingProps = { dispatchLoadingGroups: (isLoading: boolean) => void; @@ -47,7 +51,7 @@ export const GroupsContext = React.createContext< >(undefined); export const GroupsProvider = ({ children }: PropsWithChildren<{}>) => { - const userId = useApi(identityApiRef).getUserId(); + const identityApi = useApi(identityApiRef); const client = useApi(costInsightsApiRef); const [error, setError] = useState>(null); const { dispatchLoadingGroups } = useLoading(mapLoadingToProps); @@ -59,6 +63,11 @@ export const GroupsProvider = ({ children }: PropsWithChildren<{}>) => { async function getUserGroups() { try { + const { userEntityRef } = await identityApi.getBackstageIdentity(); + const { name: userId } = parseEntityRef(userEntityRef, { + defaultKind: 'User', + defaultNamespace: ENTITY_DEFAULT_NAMESPACE, + }); const g = await client.getUserGroups(userId); setGroups(g); } catch (e) { @@ -69,7 +78,7 @@ export const GroupsProvider = ({ children }: PropsWithChildren<{}>) => { } getUserGroups(); - }, [userId, client]); // eslint-disable-line react-hooks/exhaustive-deps + }, [client]); // eslint-disable-line react-hooks/exhaustive-deps if (error) { return {error.message}; diff --git a/plugins/fossa/src/api/FossaClient.test.ts b/plugins/fossa/src/api/FossaClient.test.ts index e723a4b00a..65b0fc1d53 100644 --- a/plugins/fossa/src/api/FossaClient.test.ts +++ b/plugins/fossa/src/api/FossaClient.test.ts @@ -25,8 +25,8 @@ import { UrlPatternDiscovery } from '@backstage/core-app-api'; const server = setupServer(); const identityApi = { - async getIdToken() { - return Promise.resolve('fake-id-token'); + async getCredentials() { + return { token: 'fake-id-token' }; }, } as IdentityApi; diff --git a/plugins/fossa/src/api/FossaClient.ts b/plugins/fossa/src/api/FossaClient.ts index e2a21f3ef6..66b44de21e 100644 --- a/plugins/fossa/src/api/FossaClient.ts +++ b/plugins/fossa/src/api/FossaClient.ts @@ -57,7 +57,7 @@ export class FossaClient implements FossaApi { query: Record, ): Promise { const apiUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/fossa`; - const idToken = await this.identityApi.getIdToken(); + const { token: idToken } = await this.identityApi.getCredentials(); const response = await fetch( `${apiUrl}/${path}?${new URLSearchParams(query).toString()}`, { diff --git a/plugins/ilert/src/components/Incident/IncidentActionsMenu.tsx b/plugins/ilert/src/components/Incident/IncidentActionsMenu.tsx index 46ebc03ba3..d58fa91108 100644 --- a/plugins/ilert/src/components/Incident/IncidentActionsMenu.tsx +++ b/plugins/ilert/src/components/Incident/IncidentActionsMenu.tsx @@ -27,6 +27,10 @@ import { identityApiRef, } from '@backstage/core-plugin-api'; import { Progress, Link } from '@backstage/core-components'; +import { + ENTITY_DEFAULT_NAMESPACE, + parseEntityRef, +} from '@backstage/catalog-model'; export const IncidentActionsMenu = ({ incident, @@ -40,7 +44,6 @@ export const IncidentActionsMenu = ({ const ilertApi = useApi(ilertApiRef); const alertApi = useApi(alertApiRef); const identityApi = useApi(identityApiRef); - const userName = identityApi.getUserId(); const [anchorEl, setAnchorEl] = React.useState(null); const callback = onIncidentChanged || ((_: Incident): void => {}); const setProcessing = setIsLoading || ((_: boolean): void => {}); @@ -64,6 +67,12 @@ export const IncidentActionsMenu = ({ try { handleCloseMenu(); setProcessing(true); + + const { userEntityRef } = await identityApi.getBackstageIdentity(); + const { name: userName } = parseEntityRef(userEntityRef, { + defaultKind: 'User', + defaultNamespace: ENTITY_DEFAULT_NAMESPACE, + }); const newIncident = await ilertApi.acceptIncident(incident, userName); alertApi.post({ message: 'Incident accepted.' }); @@ -79,6 +88,11 @@ export const IncidentActionsMenu = ({ try { handleCloseMenu(); setProcessing(true); + const { userEntityRef } = await identityApi.getBackstageIdentity(); + const { name: userName } = parseEntityRef(userEntityRef, { + defaultKind: 'User', + defaultNamespace: ENTITY_DEFAULT_NAMESPACE, + }); const newIncident = await ilertApi.resolveIncident(incident, userName); alertApi.post({ message: 'Incident resolved.' }); diff --git a/plugins/ilert/src/components/Incident/IncidentNewModal.tsx b/plugins/ilert/src/components/Incident/IncidentNewModal.tsx index 006abec1e4..c8f63c6073 100644 --- a/plugins/ilert/src/components/Incident/IncidentNewModal.tsx +++ b/plugins/ilert/src/components/Incident/IncidentNewModal.tsx @@ -14,6 +14,10 @@ * limitations under the License. */ import React from 'react'; +import { + ENTITY_DEFAULT_NAMESPACE, + parseEntityRef, +} from '@backstage/catalog-model'; import { makeStyles } from '@material-ui/core/styles'; import Alert from '@material-ui/lab/Alert'; import Button from '@material-ui/core/Button'; @@ -80,7 +84,6 @@ export const IncidentNewModal = ({ const ilertApi = useApi(ilertApiRef); const alertApi = useApi(alertApiRef); const identityApi = useApi(identityApiRef); - const userName = identityApi.getUserId(); const source = window.location.toString(); const classes = useStyles(); const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); @@ -102,6 +105,11 @@ export const IncidentNewModal = ({ setIsLoading(true); setTimeout(async () => { try { + const { userEntityRef } = await identityApi.getBackstageIdentity(); + const { name: userName } = parseEntityRef(userEntityRef, { + defaultKind: 'User', + defaultNamespace: ENTITY_DEFAULT_NAMESPACE, + }); await ilertApi.createIncident({ integrationKey, summary, diff --git a/plugins/kafka/src/api/KafkaBackendClient.ts b/plugins/kafka/src/api/KafkaBackendClient.ts index aea5736aad..985e9a7ba4 100644 --- a/plugins/kafka/src/api/KafkaBackendClient.ts +++ b/plugins/kafka/src/api/KafkaBackendClient.ts @@ -31,7 +31,7 @@ export class KafkaBackendClient implements KafkaApi { private async internalGet(path: string): Promise { const url = `${await this.discoveryApi.getBaseUrl('kafka')}${path}`; - const idToken = await this.identityApi.getIdToken(); + const { token: idToken } = await this.identityApi.getCredentials(); const response = await fetch(url, { method: 'GET', headers: { diff --git a/plugins/kubernetes/src/api/KubernetesBackendClient.ts b/plugins/kubernetes/src/api/KubernetesBackendClient.ts index 4d44c021c4..209f5f950d 100644 --- a/plugins/kubernetes/src/api/KubernetesBackendClient.ts +++ b/plugins/kubernetes/src/api/KubernetesBackendClient.ts @@ -56,7 +56,7 @@ export class KubernetesBackendClient implements KubernetesApi { requestBody: KubernetesRequestBody, ): Promise { const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}${path}`; - const idToken = await this.identityApi.getIdToken(); + const { token: idToken } = await this.identityApi.getCredentials(); const response = await fetch(url, { method: 'POST', headers: { @@ -79,7 +79,7 @@ export class KubernetesBackendClient implements KubernetesApi { } async getClusters(): Promise<{ name: string; authProvider: string }[]> { - const idToken = await this.identityApi.getIdToken(); + const { token: idToken } = await this.identityApi.getCredentials(); const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}/clusters`; const response = await fetch(url, { diff --git a/plugins/pagerduty/src/components/TriggerButton/index.test.tsx b/plugins/pagerduty/src/components/TriggerButton/index.test.tsx index 3776bcaf1c..409f11ec04 100644 --- a/plugins/pagerduty/src/components/TriggerButton/index.test.tsx +++ b/plugins/pagerduty/src/components/TriggerButton/index.test.tsx @@ -22,17 +22,9 @@ import { EntityProvider } from '@backstage/plugin-catalog-react'; import { TriggerButton } from './'; import { ApiProvider } from '@backstage/core-app-api'; -import { - alertApiRef, - IdentityApi, - identityApiRef, -} from '@backstage/core-plugin-api'; +import { alertApiRef } from '@backstage/core-plugin-api'; describe('TriggerButton', () => { - const mockIdentityApi: Partial = { - getUserId: () => 'guest@example.com', - }; - const mockTriggerAlarmFn = jest.fn(); const mockPagerDutyApi = { triggerAlarm: mockTriggerAlarmFn, @@ -40,7 +32,6 @@ describe('TriggerButton', () => { const apis = TestApiRegistry.from( [alertApiRef, {}], - [identityApiRef, mockIdentityApi], [pagerDutyApiRef, mockPagerDutyApi], ); diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx index d6f378b4bf..c6874cc7f3 100644 --- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx +++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx @@ -30,7 +30,11 @@ import { describe('TriggerDialog', () => { const mockIdentityApi: Partial = { - getUserId: () => 'guest@example.com', + getBackstageIdentity: async () => ({ + type: 'user', + userEntityRef: 'user:default/guest', + ownershipEntityRefs: [], + }), }; const mockTriggerAlarmFn = jest.fn(); @@ -89,7 +93,7 @@ describe('TriggerDialog', () => { entity!.metadata!.annotations!['pagerduty.com/integration-key'], source: window.location.toString(), description, - userName: 'guest@example.com', + userName: 'guest', }); }); }); diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx index 7e1a602bc8..ef0659becb 100644 --- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx +++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx @@ -34,6 +34,10 @@ import { alertApiRef, identityApiRef, } from '@backstage/core-plugin-api'; +import { + ENTITY_DEFAULT_NAMESPACE, + parseEntityRef, +} from '@backstage/catalog-model'; type Props = { showDialog: boolean; @@ -49,18 +53,23 @@ export const TriggerDialog = ({ const { name, integrationKey } = usePagerdutyEntity(); const alertApi = useApi(alertApiRef); const identityApi = useApi(identityApiRef); - const userName = identityApi.getUserId(); const api = useApi(pagerDutyApiRef); const [description, setDescription] = useState(''); const [{ value, loading, error }, handleTriggerAlarm] = useAsyncFn( - async (descriptions: string) => + async (descriptions: string) => { + const { userEntityRef } = await identityApi.getBackstageIdentity(); + const { name: userName } = parseEntityRef(userEntityRef, { + defaultKind: 'User', + defaultNamespace: ENTITY_DEFAULT_NAMESPACE, + }); await api.triggerAlarm({ integrationKey: integrationKey as string, source: window.location.toString(), description: descriptions, userName, - }), + }); + }, ); const descriptionChanged = ( @@ -73,7 +82,7 @@ export const TriggerDialog = ({ if (value) { (async () => { alertApi.post({ - message: `Alarm successfully triggered by ${userName}`, + message: `Alarm successfully triggered`, }); handleDialog(); @@ -83,7 +92,7 @@ export const TriggerDialog = ({ onIncidentCreated?.(); })(); } - }, [value, alertApi, handleDialog, userName, onIncidentCreated]); + }, [value, alertApi, handleDialog, onIncidentCreated]); if (error) { alertApi.post({ diff --git a/plugins/permission-react/src/apis/IdentityPermissionApi.ts b/plugins/permission-react/src/apis/IdentityPermissionApi.ts index 818eca17fa..f536eb62b9 100644 --- a/plugins/permission-react/src/apis/IdentityPermissionApi.ts +++ b/plugins/permission-react/src/apis/IdentityPermissionApi.ts @@ -45,9 +45,10 @@ export class IdentityPermissionApi implements PermissionApi { } async authorize(request: AuthorizeRequest): Promise { - const response = await this.permissionClient.authorize([request], { - token: await this.identityApi.getIdToken(), - }); + const response = await this.permissionClient.authorize( + [request], + await this.identityApi.getCredentials(), + ); return response[0]; } } diff --git a/plugins/rollbar/src/api/RollbarClient.ts b/plugins/rollbar/src/api/RollbarClient.ts index d3a88be46d..b87a6c8cc8 100644 --- a/plugins/rollbar/src/api/RollbarClient.ts +++ b/plugins/rollbar/src/api/RollbarClient.ts @@ -58,7 +58,7 @@ export class RollbarClient implements RollbarApi { private async get(path: string): Promise { const url = `${await this.discoveryApi.getBaseUrl('rollbar')}${path}`; - const idToken = await this.identityApi.getIdToken(); + const { token: idToken } = await this.identityApi.getCredentials(); const response = await fetch(url, { headers: idToken ? { Authorization: `Bearer ${idToken}` } : {}, }); diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index ef59d4382f..70ed803ef7 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -124,7 +124,7 @@ export class ScaffolderClient implements ScaffolderApi { ): Promise { const { namespace, kind, name } = templateName; - const token = await this.identityApi.getIdToken(); + const { token } = await this.identityApi.getCredentials(); const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); const templatePath = [namespace, kind, name] .map(s => encodeURIComponent(s)) @@ -156,7 +156,7 @@ export class ScaffolderClient implements ScaffolderApi { templateName: string, values: Record, ): Promise { - const token = await this.identityApi.getIdToken(); + const { token } = await this.identityApi.getCredentials(); const url = `${await this.discoveryApi.getBaseUrl('scaffolder')}/v2/tasks`; const response = await fetch(url, { method: 'POST', @@ -178,7 +178,7 @@ export class ScaffolderClient implements ScaffolderApi { } async getTask(taskId: string) { - const token = await this.identityApi.getIdToken(); + const { token } = await this.identityApi.getCredentials(); const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); const url = `${baseUrl}/v2/tasks/${encodeURIComponent(taskId)}`; const response = await fetch(url, { @@ -295,7 +295,7 @@ export class ScaffolderClient implements ScaffolderApi { */ async listActions(): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); - const token = await this.identityApi.getIdToken(); + const { token } = await this.identityApi.getCredentials(); const response = await fetch(`${baseUrl}/v2/actions`, { headers: token ? { Authorization: `Bearer ${token}` } : {}, }); diff --git a/plugins/search/src/apis.ts b/plugins/search/src/apis.ts index 4ce638fcca..008002deb1 100644 --- a/plugins/search/src/apis.ts +++ b/plugins/search/src/apis.ts @@ -44,7 +44,7 @@ export class SearchClient implements SearchApi { } async query(query: SearchQuery): Promise { - const token = await this.identityApi.getIdToken(); + const { token } = await this.identityApi.getCredentials(); const queryString = qs.stringify(query); const url = `${await this.discoveryApi.getBaseUrl( 'search/query', diff --git a/plugins/sentry/src/api/production-api.ts b/plugins/sentry/src/api/production-api.ts index 1fc6ece37f..5062f67380 100644 --- a/plugins/sentry/src/api/production-api.ts +++ b/plugins/sentry/src/api/production-api.ts @@ -55,7 +55,7 @@ export class ProductionSentryApi implements SentryApi { if (!this.identityApi) { return {}; } - const token = await this.identityApi.getIdToken(); + const { token } = await this.identityApi.getCredentials(); return { headers: { authorization: `Bearer ${token}`, diff --git a/plugins/sonarqube/src/api/SonarQubeClient.test.ts b/plugins/sonarqube/src/api/SonarQubeClient.test.ts index a3a301d77a..b592953434 100644 --- a/plugins/sonarqube/src/api/SonarQubeClient.test.ts +++ b/plugins/sonarqube/src/api/SonarQubeClient.test.ts @@ -25,38 +25,16 @@ import { IdentityApi } from '@backstage/core-plugin-api'; const server = setupServer(); const identityApiAuthenticated: IdentityApi = { - getUserId() { - return 'jane-fonda'; - }, - getProfile() { - return { email: 'jane-fonda@spotify.com' }; - }, - async getIdToken() { - return Promise.resolve('fake-id-token'); - }, - async signOut() { - return Promise.resolve(); - }, + signOut: jest.fn(), getProfileInfo: jest.fn(), getBackstageIdentity: jest.fn(), - getCredentials: jest.fn(), + getCredentials: jest.fn().mockResolvedValue({ token: 'fake-id-token' }), }; const identityApiGuest: IdentityApi = { - getUserId() { - return 'guest'; - }, - getProfile() { - return {}; - }, - async getIdToken() { - return Promise.resolve(undefined); - }, - async signOut() { - return Promise.resolve(); - }, + signOut: jest.fn(), getProfileInfo: jest.fn(), getBackstageIdentity: jest.fn(), - getCredentials: jest.fn(), + getCredentials: jest.fn().mockResolvedValue({ token: undefined }), }; describe('SonarQubeClient', () => { diff --git a/plugins/sonarqube/src/api/SonarQubeClient.ts b/plugins/sonarqube/src/api/SonarQubeClient.ts index 85da65ecf3..39d0f11369 100644 --- a/plugins/sonarqube/src/api/SonarQubeClient.ts +++ b/plugins/sonarqube/src/api/SonarQubeClient.ts @@ -42,7 +42,7 @@ export class SonarQubeClient implements SonarQubeApi { path: string, query: { [key in string]: any }, ): Promise { - const idToken = await this.identityApi.getIdToken(); + const { token: idToken } = await this.identityApi.getCredentials(); const apiUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/sonarqube`; const response = await fetch( diff --git a/plugins/splunk-on-call/src/components/Incident/Incidents.test.tsx b/plugins/splunk-on-call/src/components/Incident/Incidents.test.tsx index 181c2fda77..0f2d28927d 100644 --- a/plugins/splunk-on-call/src/components/Incident/Incidents.test.tsx +++ b/plugins/splunk-on-call/src/components/Incident/Incidents.test.tsx @@ -20,24 +20,15 @@ import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils'; import { splunkOnCallApiRef } from '../../api'; import { MOCK_TEAM, MOCK_INCIDENT } from '../../api/mocks'; -import { - alertApiRef, - IdentityApi, - identityApiRef, -} from '@backstage/core-plugin-api'; +import { alertApiRef } from '@backstage/core-plugin-api'; import { ApiProvider } from '@backstage/core-app-api'; -const mockIdentityApi: Partial = { - getUserId: () => 'test', -}; - const mockSplunkOnCallApi = { getIncidents: jest.fn(), getTeams: jest.fn(), }; const apis = TestApiRegistry.from( [alertApiRef, {}], - [identityApiRef, mockIdentityApi], [splunkOnCallApiRef, mockSplunkOnCallApi], ); diff --git a/plugins/tech-insights/src/api/TechInsightsClient.ts b/plugins/tech-insights/src/api/TechInsightsClient.ts index 692169e80e..f2b57dd21c 100644 --- a/plugins/tech-insights/src/api/TechInsightsClient.ts +++ b/plugins/tech-insights/src/api/TechInsightsClient.ts @@ -59,7 +59,7 @@ export class TechInsightsClient implements TechInsightsApi { async getAllChecks(): Promise { const url = await this.discoveryApi.getBaseUrl('tech-insights'); - const token = await this.identityApi.getIdToken(); + const { token } = await this.identityApi.getCredentials(); const response = await fetch(`${url}/checks`, { headers: token ? { @@ -78,7 +78,7 @@ export class TechInsightsClient implements TechInsightsApi { checks?: Check[], ): Promise { const url = await this.discoveryApi.getBaseUrl('tech-insights'); - const token = await this.identityApi.getIdToken(); + const { token } = await this.identityApi.getCredentials(); const { namespace, kind, name } = entityParams; const checkIds = checks ? checks.map(check => check.id) : []; const requestBody = { checks: checkIds.length > 0 ? checkIds : undefined }; diff --git a/plugins/techdocs/src/client.test.ts b/plugins/techdocs/src/client.test.ts index d1a421c69f..9853b99f2f 100644 --- a/plugins/techdocs/src/client.test.ts +++ b/plugins/techdocs/src/client.test.ts @@ -40,9 +40,6 @@ describe('TechDocsStorageClient', () => { } as Partial; const discoveryApi = UrlPatternDiscovery.compile(mockBaseUrl); const identityApi: jest.Mocked = { - getIdToken: jest.fn(), - getProfile: jest.fn(), - getUserId: jest.fn(), signOut: jest.fn(), getProfileInfo: jest.fn(), getBackstageIdentity: jest.fn(), @@ -51,6 +48,7 @@ describe('TechDocsStorageClient', () => { beforeEach(() => { jest.resetAllMocks(); + identityApi.getCredentials.mockResolvedValue({ token: undefined }); }); it('should return correct base url based on defined storage', async () => { @@ -122,7 +120,7 @@ describe('TechDocsStorageClient', () => { }, ); - identityApi.getIdToken.mockResolvedValue('token'); + identityApi.getCredentials.mockResolvedValue({ token: 'token' }); await storageApi.syncEntityDocs(mockEntity); diff --git a/plugins/techdocs/src/client.ts b/plugins/techdocs/src/client.ts index 3963637349..5cf33ac8d5 100644 --- a/plugins/techdocs/src/client.ts +++ b/plugins/techdocs/src/client.ts @@ -65,7 +65,7 @@ export class TechDocsClient implements TechDocsApi { const apiOrigin = await this.getApiOrigin(); const requestUrl = `${apiOrigin}/metadata/techdocs/${namespace}/${kind}/${name}`; - const token = await this.identityApi.getIdToken(); + const { token } = await this.identityApi.getCredentials(); const request = await fetch(`${requestUrl}`, { headers: token ? { Authorization: `Bearer ${token}` } : {}, @@ -93,7 +93,7 @@ export class TechDocsClient implements TechDocsApi { const apiOrigin = await this.getApiOrigin(); const requestUrl = `${apiOrigin}/metadata/entity/${namespace}/${kind}/${name}`; - const token = await this.identityApi.getIdToken(); + const { token } = await this.identityApi.getCredentials(); const request = await fetch(`${requestUrl}`, { headers: token ? { Authorization: `Bearer ${token}` } : {}, @@ -160,7 +160,7 @@ export class TechDocsStorageClient implements TechDocsStorageApi { const storageUrl = await this.getStorageUrl(); const url = `${storageUrl}/${namespace}/${kind}/${name}/${path}`; - const token = await this.identityApi.getIdToken(); + const { token } = await this.identityApi.getCredentials(); const request = await fetch( `${url.endsWith('/') ? url : `${url}/`}index.html`, @@ -207,7 +207,7 @@ export class TechDocsStorageClient implements TechDocsStorageApi { const apiOrigin = await this.getApiOrigin(); const url = `${apiOrigin}/sync/${namespace}/${kind}/${name}`; - const token = await this.identityApi.getIdToken(); + const { token } = await this.identityApi.getCredentials(); return new Promise((resolve, reject) => { // Polyfill is used to add support for custom headers and auth diff --git a/plugins/todo/src/api/TodoClient.ts b/plugins/todo/src/api/TodoClient.ts index 7e4e4c8f79..4bfd5b77e5 100644 --- a/plugins/todo/src/api/TodoClient.ts +++ b/plugins/todo/src/api/TodoClient.ts @@ -46,7 +46,7 @@ export class TodoClient implements TodoApi { async listTodos(options: TodoListOptions): Promise { const { entity, offset, limit, orderBy, filters } = options; const baseUrl = await this.discoveryApi.getBaseUrl('todo'); - const token = await this.identityApi.getIdToken(); + const { token } = await this.identityApi.getCredentials(); const query = new URLSearchParams(); if (entity) {