From 3a50df5ee3ac4f655424c8f01274ade633936940 Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Fri, 4 Dec 2020 15:31:07 -0500 Subject: [PATCH 01/36] convert duration + billing date to intervals --- plugins/cost-insights/src/api/CostInsightsApi.ts | 11 ++--------- .../components/ProductInsights/ProductInsights.tsx | 8 +++----- plugins/cost-insights/src/index.ts | 1 - 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/plugins/cost-insights/src/api/CostInsightsApi.ts b/plugins/cost-insights/src/api/CostInsightsApi.ts index 9baad5ffab..28e72bccc6 100644 --- a/plugins/cost-insights/src/api/CostInsightsApi.ts +++ b/plugins/cost-insights/src/api/CostInsightsApi.ts @@ -18,7 +18,6 @@ import { createApiRef } from '@backstage/core'; import { Alert, Cost, - Duration, Entity, Group, Project, @@ -38,15 +37,9 @@ export type ProductInsightsOptions = { group: string; /** - * A time duration, such as P1M. See the Duration type for a detailed explanation - * of how the durations are interpreted in Cost Insights. + * An ISO 8601 repeating interval string, such as R2/P3M/2020-09-01 */ - duration: Duration; - - /** - * The most current date for which billing data is complete, in YYYY-MM-DD format. - */ - lastCompleteBillingDate: string; + intervals: string; /** * (optional) The project id from getGroupProjects or query parameters diff --git a/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx b/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx index 52abe3fd8b..0ef35bf04c 100644 --- a/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx +++ b/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx @@ -21,7 +21,7 @@ import { useApi } from '@backstage/core'; import { costInsightsApiRef } from '../../api'; import { ProductInsightsCardList } from '../ProductInsightsCard/ProductInsightsCardList'; import { Duration, Entity, Maybe, Product } from '../../types'; -import { DEFAULT_DURATION } from '../../utils/duration'; +import { intervalsOf, DEFAULT_DURATION } from '../../utils/duration'; import { DefaultLoadingAction, initialStatesOf, @@ -75,8 +75,7 @@ export const ProductInsights = ({ group: group, project: project, product: product.kind, - duration: duration, - lastCompleteBillingDate: lastCompleteBillingDate, + intervals: intervalsOf(duration, lastCompleteBillingDate), }); }, [client, group, project, lastCompleteBillingDate], @@ -97,8 +96,7 @@ export const ProductInsights = ({ group: group, project: project, product: product.kind, - duration: DEFAULT_DURATION, - lastCompleteBillingDate: lastCompleteBillingDate, + intervals: intervalsOf(DEFAULT_DURATION, lastCompleteBillingDate), }), ), ).then(settledResponseOf); diff --git a/plugins/cost-insights/src/index.ts b/plugins/cost-insights/src/index.ts index 28bdb1a8b3..7cd0456ad1 100644 --- a/plugins/cost-insights/src/index.ts +++ b/plugins/cost-insights/src/index.ts @@ -21,5 +21,4 @@ export * from './components'; export { useCurrency } from './hooks'; export * from './types'; export * from './utils/tests'; -export * from './utils/duration'; export * from './utils/alerts'; From cd26fb65778693662a9298ba0047d29fbfe41fe9 Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Fri, 4 Dec 2020 15:31:38 -0500 Subject: [PATCH 02/36] supress recharts react warnings in test --- .../ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx index db3f2f9bf6..0e24f042bc 100644 --- a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx +++ b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx @@ -27,6 +27,9 @@ import { AlertCost } from '../../types'; import { defaultCurrencies } from '../../utils/currency'; import { findAlways } from '../../utils/assert'; +// suppress recharts componentDidUpdate deprecation warnings +jest.spyOn(console, 'warn').mockImplementation(() => {}); + const engineers = findAlways(defaultCurrencies, c => c.kind === null); const MockProject = 'test-project-1'; From d6e8099ed58b2558b707edda62bb37b2d0f03e10 Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Fri, 4 Dec 2020 15:35:59 -0500 Subject: [PATCH 03/36] changeset --- .changeset/cost-insights-wet-walls-jog.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cost-insights-wet-walls-jog.md diff --git a/.changeset/cost-insights-wet-walls-jog.md b/.changeset/cost-insights-wet-walls-jog.md new file mode 100644 index 0000000000..8de3037f19 --- /dev/null +++ b/.changeset/cost-insights-wet-walls-jog.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-cost-insights': minor +--- + +convert duration + last completed billing date to intervals From 7babbfaf3cc4acbbf478bd99fee5b8d1083da979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=96stberg?= Date: Tue, 8 Dec 2020 12:06:58 +0100 Subject: [PATCH 04/36] Add support for GitHub Enterprise from integration-config --- plugins/catalog-import/src/api/CatalogImportApi.ts | 2 ++ plugins/catalog-import/src/api/CatalogImportClient.ts | 6 +++++- plugins/catalog-import/src/util/useGithubRepos.ts | 9 ++++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-import/src/api/CatalogImportApi.ts b/plugins/catalog-import/src/api/CatalogImportApi.ts index 0ce569171f..414eacf128 100644 --- a/plugins/catalog-import/src/api/CatalogImportApi.ts +++ b/plugins/catalog-import/src/api/CatalogImportApi.ts @@ -16,6 +16,7 @@ import { createApiRef } from '@backstage/core'; import { PartialEntity } from '../util/types'; +import { GitHubIntegrationConfig } from '@backstage/integration' export const catalogImportApiRef = createApiRef({ id: 'plugin.catalog-import.service', @@ -27,6 +28,7 @@ export interface CatalogImportApi { owner: string; repo: string; fileContent: string; + githubIntegrationConfig: GitHubIntegrationConfig; }): Promise<{ link: string; location: string }>; createRepositoryLocation(options: { location: string }): Promise; generateEntityDefinitions(options: { diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index aaa110329e..57c6a8490a 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -19,6 +19,7 @@ import { DiscoveryApi, OAuthApi } from '@backstage/core'; import { CatalogImportApi } from './CatalogImportApi'; import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-backend'; import { PartialEntity } from '../util/types'; +import { GitHubIntegrationConfig } from '@backstage/integration' export class CatalogImportClient implements CatalogImportApi { private readonly discoveryApi: DiscoveryApi; @@ -91,15 +92,18 @@ export class CatalogImportClient implements CatalogImportApi { owner, repo, fileContent, + githubIntegrationConfig, }: { owner: string; repo: string; fileContent: string; + githubIntegrationConfig: GitHubIntegrationConfig; }): Promise<{ link: string; location: string }> { const token = await this.githubAuthApi.getAccessToken(['repo']); const octo = new Octokit({ auth: token, + baseUrl: githubIntegrationConfig.apiBaseUrl, }); const branchName = 'backstage-integration'; @@ -179,7 +183,7 @@ export class CatalogImportClient implements CatalogImportApi { return { link: pullRequestResponse.data.html_url, - location: `https://github.com/${owner}/${repo}/blob/${repoData.data.default_branch}/${fileName}`, + location: `https://${githubIntegrationConfig.host}/${owner}/${repo}/blob/${repoData.data.default_branch}/${fileName}`, }; } } diff --git a/plugins/catalog-import/src/util/useGithubRepos.ts b/plugins/catalog-import/src/util/useGithubRepos.ts index a18edc2777..435d260f1b 100644 --- a/plugins/catalog-import/src/util/useGithubRepos.ts +++ b/plugins/catalog-import/src/util/useGithubRepos.ts @@ -15,15 +15,21 @@ */ import * as YAML from 'yaml'; -import { useApi } from '@backstage/core'; +import { useApi, configApiRef } from '@backstage/core'; import { catalogImportApiRef } from '../api/CatalogImportApi'; import { ConfigSpec } from '../components/ImportComponentPage'; +import { readGitHubIntegrationConfigs } from '@backstage/integration'; export function useGithubRepos() { const api = useApi(catalogImportApiRef); + const config = useApi(configApiRef); const submitPrToRepo = async (selectedRepo: ConfigSpec) => { const [ownerName, repoName] = selectedRepo.location.split('/').slice(-2); + const configs = readGitHubIntegrationConfigs( + config.getOptionalConfigArray('integrations.github') ?? [] + ) + const githubIntegrationConfig = configs[0] const submitPRResponse = await api .submitPrToRepo({ owner: ownerName, @@ -31,6 +37,7 @@ export function useGithubRepos() { fileContent: selectedRepo.config .map(entity => `---\n${YAML.stringify(entity)}`) .join('\n'), + githubIntegrationConfig }) .catch(e => { throw new Error(`Failed to submit PR to repo:\n${e.message}`); From ac454782f2f38ddb466208a6a4f3db1c25059f49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=96stberg?= Date: Tue, 8 Dec 2020 12:24:18 +0100 Subject: [PATCH 05/36] Add dependency on module --- plugins/catalog-import/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 74cb3635be..86995e1e7a 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -25,6 +25,7 @@ "@backstage/core": "^0.3.2", "@backstage/plugin-catalog": "^0.2.5", "@backstage/plugin-catalog-backend": "^0.3.0", + "@backstage/integration": "^0.1.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", From a3cb631ca5a1d8b509328224b743145714c871fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=96stberg?= Date: Tue, 8 Dec 2020 12:34:31 +0100 Subject: [PATCH 06/36] Ran prettier ... --- plugins/catalog-import/src/api/CatalogImportApi.ts | 2 +- plugins/catalog-import/src/api/CatalogImportClient.ts | 2 +- plugins/catalog-import/src/util/useGithubRepos.ts | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/catalog-import/src/api/CatalogImportApi.ts b/plugins/catalog-import/src/api/CatalogImportApi.ts index 414eacf128..99526a3eaa 100644 --- a/plugins/catalog-import/src/api/CatalogImportApi.ts +++ b/plugins/catalog-import/src/api/CatalogImportApi.ts @@ -16,7 +16,7 @@ import { createApiRef } from '@backstage/core'; import { PartialEntity } from '../util/types'; -import { GitHubIntegrationConfig } from '@backstage/integration' +import { GitHubIntegrationConfig } from '@backstage/integration'; export const catalogImportApiRef = createApiRef({ id: 'plugin.catalog-import.service', diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index 57c6a8490a..4ac015ffbf 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -19,7 +19,7 @@ import { DiscoveryApi, OAuthApi } from '@backstage/core'; import { CatalogImportApi } from './CatalogImportApi'; import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-backend'; import { PartialEntity } from '../util/types'; -import { GitHubIntegrationConfig } from '@backstage/integration' +import { GitHubIntegrationConfig } from '@backstage/integration'; export class CatalogImportClient implements CatalogImportApi { private readonly discoveryApi: DiscoveryApi; diff --git a/plugins/catalog-import/src/util/useGithubRepos.ts b/plugins/catalog-import/src/util/useGithubRepos.ts index 435d260f1b..af1ef43c73 100644 --- a/plugins/catalog-import/src/util/useGithubRepos.ts +++ b/plugins/catalog-import/src/util/useGithubRepos.ts @@ -27,9 +27,9 @@ export function useGithubRepos() { const submitPrToRepo = async (selectedRepo: ConfigSpec) => { const [ownerName, repoName] = selectedRepo.location.split('/').slice(-2); const configs = readGitHubIntegrationConfigs( - config.getOptionalConfigArray('integrations.github') ?? [] - ) - const githubIntegrationConfig = configs[0] + config.getOptionalConfigArray('integrations.github') ?? [], + ); + const githubIntegrationConfig = configs[0]; const submitPRResponse = await api .submitPrToRepo({ owner: ownerName, @@ -37,7 +37,7 @@ export function useGithubRepos() { fileContent: selectedRepo.config .map(entity => `---\n${YAML.stringify(entity)}`) .join('\n'), - githubIntegrationConfig + githubIntegrationConfig, }) .catch(e => { throw new Error(`Failed to submit PR to repo:\n${e.message}`); From d0c62e7dc7acede47ea900fd6efb760a5899de97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=96stberg?= Date: Wed, 9 Dec 2020 11:05:29 +0100 Subject: [PATCH 07/36] Add todo and find config by hostname --- .../catalog-import/src/util/useGithubRepos.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/plugins/catalog-import/src/util/useGithubRepos.ts b/plugins/catalog-import/src/util/useGithubRepos.ts index af1ef43c73..7f49bb3363 100644 --- a/plugins/catalog-import/src/util/useGithubRepos.ts +++ b/plugins/catalog-import/src/util/useGithubRepos.ts @@ -18,6 +18,9 @@ import * as YAML from 'yaml'; import { useApi, configApiRef } from '@backstage/core'; import { catalogImportApiRef } from '../api/CatalogImportApi'; import { ConfigSpec } from '../components/ImportComponentPage'; + +//TODO: O5ten, refactor into a core API instead of direct usage like this +//https://github.com/backstage/backstage/pull/3613#issuecomment-7408929430 import { readGitHubIntegrationConfigs } from '@backstage/integration'; export function useGithubRepos() { @@ -25,11 +28,14 @@ export function useGithubRepos() { const config = useApi(configApiRef); const submitPrToRepo = async (selectedRepo: ConfigSpec) => { - const [ownerName, repoName] = selectedRepo.location.split('/').slice(-2); + const [hostname, ownerName, repoName] = selectedRepo.location.split('/').slice(-3); const configs = readGitHubIntegrationConfigs( - config.getOptionalConfigArray('integrations.github') ?? [], - ); - const githubIntegrationConfig = configs[0]; + config.getOptionalConfigArray('integrations.github') ?? [] + ) + const githubIntegrationConfig = configs.find(v => v.host === hostname); + if(!githubIntegrationConfig) { + throw new Error(`Unable to locate github-integration for repo-location: ${selectedRepo.location}`); + } const submitPRResponse = await api .submitPrToRepo({ owner: ownerName, @@ -37,7 +43,7 @@ export function useGithubRepos() { fileContent: selectedRepo.config .map(entity => `---\n${YAML.stringify(entity)}`) .join('\n'), - githubIntegrationConfig, + githubIntegrationConfig }) .catch(e => { throw new Error(`Failed to submit PR to repo:\n${e.message}`); From 7dda6180b0728d582b0b34be3cd952d01c959018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=96stberg?= Date: Wed, 9 Dec 2020 11:11:17 +0100 Subject: [PATCH 08/36] Ran prettier --- .../catalog-import/src/util/useGithubRepos.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/plugins/catalog-import/src/util/useGithubRepos.ts b/plugins/catalog-import/src/util/useGithubRepos.ts index 7f49bb3363..af65799987 100644 --- a/plugins/catalog-import/src/util/useGithubRepos.ts +++ b/plugins/catalog-import/src/util/useGithubRepos.ts @@ -28,13 +28,17 @@ export function useGithubRepos() { const config = useApi(configApiRef); const submitPrToRepo = async (selectedRepo: ConfigSpec) => { - const [hostname, ownerName, repoName] = selectedRepo.location.split('/').slice(-3); + const [hostname, ownerName, repoName] = selectedRepo.location + .split('/') + .slice(-3); const configs = readGitHubIntegrationConfigs( - config.getOptionalConfigArray('integrations.github') ?? [] - ) + config.getOptionalConfigArray('integrations.github') ?? [], + ); const githubIntegrationConfig = configs.find(v => v.host === hostname); - if(!githubIntegrationConfig) { - throw new Error(`Unable to locate github-integration for repo-location: ${selectedRepo.location}`); + if (!githubIntegrationConfig) { + throw new Error( + `Unable to locate github-integration for repo-location: ${selectedRepo.location}`, + ); } const submitPRResponse = await api .submitPrToRepo({ @@ -43,7 +47,7 @@ export function useGithubRepos() { fileContent: selectedRepo.config .map(entity => `---\n${YAML.stringify(entity)}`) .join('\n'), - githubIntegrationConfig + githubIntegrationConfig, }) .catch(e => { throw new Error(`Failed to submit PR to repo:\n${e.message}`); From 5066852ef299c8bc1f03303ce397b669392c9a60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=96stberg?= Date: Wed, 9 Dec 2020 11:45:52 +0100 Subject: [PATCH 09/36] Update useGithubRepos.ts --- plugins/catalog-import/src/util/useGithubRepos.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-import/src/util/useGithubRepos.ts b/plugins/catalog-import/src/util/useGithubRepos.ts index af65799987..740f0581b1 100644 --- a/plugins/catalog-import/src/util/useGithubRepos.ts +++ b/plugins/catalog-import/src/util/useGithubRepos.ts @@ -19,8 +19,8 @@ import { useApi, configApiRef } from '@backstage/core'; import { catalogImportApiRef } from '../api/CatalogImportApi'; import { ConfigSpec } from '../components/ImportComponentPage'; -//TODO: O5ten, refactor into a core API instead of direct usage like this -//https://github.com/backstage/backstage/pull/3613#issuecomment-7408929430 +// TODO: (O5ten) Refactor into a core API instead of direct usage like this +// https://github.com/backstage/backstage/pull/3613#issuecomment-7408929430 import { readGitHubIntegrationConfigs } from '@backstage/integration'; export function useGithubRepos() { From 1d0d1a3a451580d828ceb115154f9e3dd2a6966c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=96stberg?= Date: Wed, 9 Dec 2020 12:32:09 +0100 Subject: [PATCH 10/36] Fix missing baseUrl in graphql client for GHE --- .../src/ingestion/processors/GithubOrgReaderProcessor.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts index 9aecb8b86e..a1e8b5dad5 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts @@ -64,6 +64,7 @@ export class GithubOrgReaderProcessor implements CatalogProcessor { const client = !provider.token ? graphql : graphql.defaults({ + baseUrl: provider.apiBaseUrl, headers: { authorization: `token ${provider.token}`, }, From 704f7c7be0b4b0f785fecb43efb6d8e0ed54ceb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=96stberg?= Date: Wed, 9 Dec 2020 12:35:22 +0100 Subject: [PATCH 11/36] Update app-config.yaml --- app-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-config.yaml b/app-config.yaml index 9d4d702473..0eb00ebbf3 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -133,7 +133,7 @@ catalog: $env: GITHUB_TOKEN #### Example for how to add your GitHub Enterprise instance using the API: # - target: https://ghe.example.net - # apiBaseUrl: https://ghe.example.net/api/v3 + # apiBaseUrl: https://ghe.example.net/api # token: # $env: GHE_TOKEN ldapOrg: From 6e8bb3ac076765280a591bc50e76ea6240892a9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 9 Dec 2020 16:10:30 +0100 Subject: [PATCH 12/36] catalog-backend: leave unknown placeholder-lookalikes untouched --- .changeset/breezy-chefs-report.md | 5 +++ .../processors/PlaceholderProcessor.test.ts | 38 ++++++++----------- .../processors/PlaceholderProcessor.ts | 19 +++++++--- 3 files changed, 34 insertions(+), 28 deletions(-) create mode 100644 .changeset/breezy-chefs-report.md diff --git a/.changeset/breezy-chefs-report.md b/.changeset/breezy-chefs-report.md new file mode 100644 index 0000000000..baaec3021f --- /dev/null +++ b/.changeset/breezy-chefs-report.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +leave unknown placeholder-lookalikes untouched in the catalog processing loop diff --git a/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.test.ts index 5a3511af6d..8b2632ad5d 100644 --- a/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.test.ts @@ -88,7 +88,7 @@ describe('PlaceholderProcessor', () => { ); }); - it('rejects multiple placeholders', async () => { + it('ignores multiple placeholders', async () => { const processor = new PlaceholderProcessor({ resolvers: { foo: jest.fn(), @@ -96,41 +96,35 @@ describe('PlaceholderProcessor', () => { }, reader, }); + const entity: Entity = { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'n', x: { $foo: 'a', $bar: 'b' } }, + }; await expect( - processor.preProcessEntity( - { - apiVersion: 'a', - kind: 'k', - metadata: { name: 'n', x: { $foo: 'a', $bar: 'b' } }, - }, - { type: 'a', target: 'b' }, - ), - ).rejects.toThrow( - 'Placeholders have to be on the form of a single $-prefixed key in an object', - ); + processor.preProcessEntity(entity, { type: 'a', target: 'b' }), + ).resolves.toEqual(entity); expect(read).not.toBeCalled(); }); - it('rejects unknown placeholders', async () => { + it('ignores unknown placeholders', async () => { const processor = new PlaceholderProcessor({ resolvers: { bar: jest.fn(), }, reader, }); + const entity: Entity = { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'n', x: { $foo: 'a' } }, + }; await expect( - processor.preProcessEntity( - { - apiVersion: 'a', - kind: 'k', - metadata: { name: 'n', x: { $foo: 'a' } }, - }, - { type: 'a', target: 'b' }, - ), - ).rejects.toThrow('Encountered unknown placeholder $foo'); + processor.preProcessEntity(entity, { type: 'a', target: 'b' }), + ).resolves.toEqual(entity); expect(read).not.toBeCalled(); }); diff --git a/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.ts index 0e23d51f61..b7b3df7b6e 100644 --- a/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.ts @@ -76,21 +76,28 @@ export class PlaceholderProcessor implements CatalogProcessor { ? [data, false] : [Object.fromEntries(entries.map(([k, [v]]) => [k, v])), true]; } else if (keys.length !== 1) { - throw new Error( - 'Placeholders have to be on the form of a single $-prefixed key in an object', - ); + // This was an object that had more than one key, some of which were + // dollar prefixed. We only handle the case where there is exactly one + // such key; anything else is left alone. + return [data, false]; } const resolverKey = keys[0].substr(1); + const resolverValue = data[keys[0]]; const resolver = this.options.resolvers[resolverKey]; - if (!resolver) { - throw new Error(`Encountered unknown placeholder \$${resolverKey}`); + if (!resolver || typeof resolverValue !== 'string') { + // If there was no such placeholder resolver or if the value was not a + // string, we err on the side of safety and assume that this is + // something that's best left alone. For example, if the input contains + // JSONSchema, there may be "$ref": "#/definitions/node" nodes in the + // document. + return [data, false]; } return [ await resolver({ key: resolverKey, - value: data[keys[0]], + value: resolverValue, baseUrl: location.target, read: this.options.reader.read.bind(this.options.reader), }), From 2b71db211668479ae337446d838aac0f59ad7759 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Tue, 8 Dec 2020 18:55:17 +0100 Subject: [PATCH 13/36] Consider group memberships in the OwnershipCard --- .changeset/witty-rabbits-smell.md | 5 ++ .../Cards/OwnershipCard/OwnershipCard.tsx | 13 ++--- .../org/src/components/getEntityRelations.ts | 42 ++++++++++++++ plugins/org/src/components/isOwnerOf.ts | 55 +++++++++++++++++++ 4 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 .changeset/witty-rabbits-smell.md create mode 100644 plugins/org/src/components/getEntityRelations.ts create mode 100644 plugins/org/src/components/isOwnerOf.ts diff --git a/.changeset/witty-rabbits-smell.md b/.changeset/witty-rabbits-smell.md new file mode 100644 index 0000000000..fbe2234b45 --- /dev/null +++ b/.changeset/witty-rabbits-smell.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-org': patch +--- + +Support transitive ownerships of users and groups. diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index 7c4ad7a56a..16a833cf56 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { InfoCard, useApi, Progress } from '@backstage/core'; -import { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { catalogApiRef } from '@backstage/plugin-catalog'; import { useAsync } from 'react-use'; import Alert from '@material-ui/lab/Alert'; @@ -28,6 +29,7 @@ import { Typography, } from '@material-ui/core'; import { pageTheme } from '@backstage/theme'; +import { isOwnerOf } from '../../isOwnerOf'; type EntitiesKinds = 'Component' | 'API'; type EntitiesTypes = @@ -118,9 +120,6 @@ export const OwnershipCard = ({ entity: Entity; variant: string; }) => { - const { - metadata: { name: groupName }, - } = entity; const catalogApi = useApi(catalogApiRef); const { loading, @@ -129,10 +128,8 @@ export const OwnershipCard = ({ } = useAsync(async () => { const entitiesList = await catalogApi.getEntities(); const ownedEntitiesList = entitiesList.items.filter(component => - component?.relations?.some( - r => r.type === RELATION_OWNED_BY && r.target.name === groupName, - ), - ) as Array; + isOwnerOf(entity, component), + ); return [ { diff --git a/plugins/org/src/components/getEntityRelations.ts b/plugins/org/src/components/getEntityRelations.ts new file mode 100644 index 0000000000..9230aaebee --- /dev/null +++ b/plugins/org/src/components/getEntityRelations.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { Entity, EntityName } from '@backstage/catalog-model'; + +// TODO: this file is copied from /packages/app/catalog/src/components/getEntityRelations.ts and +// should be replaced once common relation-functions are introduced. + +/** + * Get the related entity references. + */ +export function getEntityRelations( + entity: Entity | undefined, + relationType: string, + filter?: { kind: string }, +): EntityName[] { + let entityNames = + entity?.relations + ?.filter(r => r.type === relationType) + ?.map(r => r.target) || []; + + if (filter?.kind) { + entityNames = entityNames?.filter( + e => e.kind.toLowerCase() === filter.kind.toLowerCase(), + ); + } + + return entityNames; +} diff --git a/plugins/org/src/components/isOwnerOf.ts b/plugins/org/src/components/isOwnerOf.ts new file mode 100644 index 0000000000..dd7c7d6805 --- /dev/null +++ b/plugins/org/src/components/isOwnerOf.ts @@ -0,0 +1,55 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { + Entity, + EntityName, + getEntityName, + RELATION_MEMBER_OF, + RELATION_OWNED_BY, +} from '@backstage/catalog-model'; +import { getEntityRelations } from './getEntityRelations'; + +// TODO: this file is copied from /packages/app/catalog/src/components/isOwnerOf.ts and +// should be replaced once common relation-functions are introduced. + +/** + * Check if one entity is owned by another. Returns true, if the entity is owned by the + * owner directly, or if the entity is owned by a group that the owner is a member of. + */ +export function isOwnerOf(owner: Entity, owned: Entity) { + const possibleOwners: EntityName[] = [ + ...getEntityRelations(owner, RELATION_MEMBER_OF, { kind: 'group' }), + ...(owner ? [getEntityName(owner)] : []), + ]; + + const owners = getEntityRelations(owned, RELATION_OWNED_BY); + + for (const owner of owners) { + if ( + possibleOwners.find( + o => + owner.kind.toLowerCase() === o.kind.toLowerCase() && + owner.namespace.toLowerCase() === o.namespace.toLowerCase() && + owner.name.toLowerCase() === o.name.toLowerCase(), + ) !== undefined + ) { + return true; + } + } + + return false; +} From 59a879115887bc9f9d79f941572ce6668911c63d Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Wed, 9 Dec 2020 15:51:16 -0500 Subject: [PATCH 14/36] Fix comment typos --- .../catalog/src/components/EntityPageLayout/Tabbed/Tabbed.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog/src/components/EntityPageLayout/Tabbed/Tabbed.tsx b/plugins/catalog/src/components/EntityPageLayout/Tabbed/Tabbed.tsx index 096b2fa708..9a4e872d8f 100644 --- a/plugins/catalog/src/components/EntityPageLayout/Tabbed/Tabbed.tsx +++ b/plugins/catalog/src/components/EntityPageLayout/Tabbed/Tabbed.tsx @@ -39,8 +39,8 @@ const getSelectedIndexOrDefault = ( /** * Compound component, which allows you to define layout - * for EntityPage using Tabs as a subnavigation mechanism - * Constists of 2 parts: Tabbed.Layout and Tabbed.Content. + * for EntityPage using Tabs as a sub-navigation mechanism + * Consists of 2 parts: Tabbed.Layout and Tabbed.Content. * Takes care of: tabs, routes, document titles, spacing around content * * @example From 6fde301a993c0342581b98878a4f9129ba59a927 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Wed, 9 Dec 2020 15:56:27 -0500 Subject: [PATCH 15/36] FIx unneeded defensive code --- packages/config/src/reader.ts | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/packages/config/src/reader.ts b/packages/config/src/reader.ts index eb8c91e366..cd0a6f3221 100644 --- a/packages/config/src/reader.ts +++ b/packages/config/src/reader.ts @@ -272,23 +272,17 @@ export class ConfigReader implements Config { if (value === undefined) { return this.fallback?.readConfigValue(key, validate); } - if (value !== undefined) { - const result = validate(value); - if (result !== true) { - const { - key: keyName = key, - value: theValue = value, + const result = validate(value); + if (result !== true) { + const { key: keyName = key, value: theValue = value, expected } = result; + throw new TypeError( + errors.type( + this.fullKey(keyName), + this.context, + typeOf(theValue), expected, - } = result; - throw new TypeError( - errors.type( - this.fullKey(keyName), - this.context, - typeOf(theValue), - expected, - ), - ); - } + ), + ); } return value as T; From a6b8d6163d742767ed398286e4fa3d00b7d76a63 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Wed, 9 Dec 2020 16:02:52 -0500 Subject: [PATCH 16/36] Fix useless conditional --- plugins/catalog/src/components/Router.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog/src/components/Router.tsx b/plugins/catalog/src/components/Router.tsx index fe1ba3a593..298aa28d52 100644 --- a/plugins/catalog/src/components/Router.tsx +++ b/plugins/catalog/src/components/Router.tsx @@ -50,7 +50,7 @@ const EntityPageSwitch = ({ EntityPage }: { EntityPage: ComponentType }) => { const { entity, loading, error } = useEntity(); // Loading and error states if (loading) return ; - if (error || (!loading && !entity)) return ; + if (error || !entity) return ; // Otherwise EntityPage provided from the App // Note that EntityPage will include EntityPageLayout already From e3bd9fc2f480b2bfc8326c9598d5b6c87638cf6a Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Wed, 9 Dec 2020 16:12:43 -0500 Subject: [PATCH 17/36] Add changesets --- .changeset/poor-spies-grin.md | 5 +++++ .changeset/violet-windows-run.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/poor-spies-grin.md create mode 100644 .changeset/violet-windows-run.md diff --git a/.changeset/poor-spies-grin.md b/.changeset/poor-spies-grin.md new file mode 100644 index 0000000000..cab5df9029 --- /dev/null +++ b/.changeset/poor-spies-grin.md @@ -0,0 +1,5 @@ +--- +'@backstage/config': patch +--- + +Fix unneeded defensive code diff --git a/.changeset/violet-windows-run.md b/.changeset/violet-windows-run.md new file mode 100644 index 0000000000..5f385d4dfe --- /dev/null +++ b/.changeset/violet-windows-run.md @@ -0,0 +1,5 @@ +--- +'@backstage/config': patch +--- + +Fix useless conditional From a48f2704c91670713e9fe80abea19c54234fdde1 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Wed, 9 Dec 2020 16:17:48 -0500 Subject: [PATCH 18/36] Fix string template literal --- .../src/ingestion/processors/microsoftGraph/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/client.ts b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/client.ts index 6dded56aa1..9db76542f4 100644 --- a/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/client.ts +++ b/plugins/catalog-backend/src/ingestion/processors/microsoftGraph/client.ts @@ -184,7 +184,7 @@ export class MicrosoftGraphClient { const response = await this.requestApi(`organization/${tenantId}`); if (response.status !== 200) { - await this.handleError('organization/${tenantId}', response); + await this.handleError(`organization/${tenantId}`, response); } return await response.json(); From 38d63fbe1cc04cf8cc7e272865e179e72128cf73 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Wed, 9 Dec 2020 16:18:43 -0500 Subject: [PATCH 19/36] Add catalog-backend changeset --- .changeset/seven-tigers-mate.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/seven-tigers-mate.md diff --git a/.changeset/seven-tigers-mate.md b/.changeset/seven-tigers-mate.md new file mode 100644 index 0000000000..c48bcf649a --- /dev/null +++ b/.changeset/seven-tigers-mate.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Fix string template literal From 6e47fb65685b27807a568eca5eeead84dea60aa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=96stberg?= Date: Wed, 9 Dec 2020 15:31:43 +0100 Subject: [PATCH 20/36] Use parseGitUri --- plugins/catalog-import/src/util/useGithubRepos.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-import/src/util/useGithubRepos.ts b/plugins/catalog-import/src/util/useGithubRepos.ts index 740f0581b1..30e77e18a8 100644 --- a/plugins/catalog-import/src/util/useGithubRepos.ts +++ b/plugins/catalog-import/src/util/useGithubRepos.ts @@ -18,6 +18,7 @@ import * as YAML from 'yaml'; import { useApi, configApiRef } from '@backstage/core'; import { catalogImportApiRef } from '../api/CatalogImportApi'; import { ConfigSpec } from '../components/ImportComponentPage'; +import parseGitUri from 'git-url-parse'; // TODO: (O5ten) Refactor into a core API instead of direct usage like this // https://github.com/backstage/backstage/pull/3613#issuecomment-7408929430 @@ -28,9 +29,12 @@ export function useGithubRepos() { const config = useApi(configApiRef); const submitPrToRepo = async (selectedRepo: ConfigSpec) => { - const [hostname, ownerName, repoName] = selectedRepo.location - .split('/') - .slice(-3); + const { + name: repoName, + owner: ownerName, + resource: hostname, + } = parseGitUri(selectedRepo.location); + const configs = readGitHubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], ); From 9724bc1a12ead22a29b912307d79f11dbbb26b6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Dec 2020 10:37:40 +0100 Subject: [PATCH 21/36] build(deps-dev): bump nodemon from 2.0.4 to 2.0.6 (#3658) Bumps [nodemon](https://github.com/remy/nodemon) from 2.0.4 to 2.0.6. - [Release notes](https://github.com/remy/nodemon/releases) - [Commits](https://github.com/remy/nodemon/compare/v2.0.4...v2.0.6) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/yarn.lock b/yarn.lock index 73405beac2..3e95d570a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -844,14 +844,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-top-level-await@^7.12.1": - version "7.12.1" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz#dd6c0b357ac1bb142d98537450a319625d13d2a0" - integrity sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-top-level-await@^7.8.3": +"@babel/plugin-syntax-top-level-await@^7.12.1", "@babel/plugin-syntax-top-level-await@^7.8.3": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz#dd6c0b357ac1bb142d98537450a319625d13d2a0" integrity sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A== @@ -17930,9 +17923,9 @@ nodegit@0.27.0, nodegit@^0.27.0: tar-fs "^1.16.3" nodemon@^2.0.2: - version "2.0.4" - resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.4.tgz#55b09319eb488d6394aa9818148c0c2d1c04c416" - integrity sha512-Ltced+hIfTmaS28Zjv1BM552oQ3dbwPqI4+zI0SLgq+wpJhSyqgYude/aZa/3i31VCQWMfXJVxvu86abcam3uQ== + version "2.0.6" + resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.6.tgz#1abe1937b463aaf62f0d52e2b7eaadf28cc2240d" + integrity sha512-4I3YDSKXg6ltYpcnZeHompqac4E6JeAMpGm8tJnB9Y3T0ehasLa4139dJOcCrB93HHrUMsCrKtoAlXTqT5n4AQ== dependencies: chokidar "^3.2.2" debug "^3.2.6" @@ -17942,8 +17935,8 @@ nodemon@^2.0.2: semver "^5.7.1" supports-color "^5.5.0" touch "^3.1.0" - undefsafe "^2.0.2" - update-notifier "^4.0.0" + undefsafe "^2.0.3" + update-notifier "^4.1.0" "nopt@2 || 3": version "3.0.6" @@ -24060,7 +24053,7 @@ unc-path-regex@^0.1.2: resolved "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= -undefsafe@^2.0.2: +undefsafe@^2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae" integrity sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A== @@ -24272,10 +24265,10 @@ upath@^1.1.1, upath@^1.2.0: resolved "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== -update-notifier@^4.0.0: - version "4.1.0" - resolved "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.0.tgz#4866b98c3bc5b5473c020b1250583628f9a328f3" - integrity sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew== +update-notifier@^4.1.0: + version "4.1.3" + resolved "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz#be86ee13e8ce48fb50043ff72057b5bd598e1ea3" + integrity sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A== dependencies: boxen "^4.2.0" chalk "^3.0.0" From e1f4e24ef196341085588e6512de82ef6f57a2c8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 10 Dec 2020 11:06:23 +0100 Subject: [PATCH 22/36] dev-utils,test-utils: move @backstage/cli to dev deps --- .changeset/popular-flies-swim.md | 6 ++++++ packages/dev-utils/package.json | 2 +- packages/test-utils/package.json | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 .changeset/popular-flies-swim.md diff --git a/.changeset/popular-flies-swim.md b/.changeset/popular-flies-swim.md new file mode 100644 index 0000000000..42a0944dce --- /dev/null +++ b/.changeset/popular-flies-swim.md @@ -0,0 +1,6 @@ +--- +'@backstage/dev-utils': patch +'@backstage/test-utils': patch +--- + +Fix @backstage/cli not being a devDependency diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 4ceb0cec5e..21d6732ed5 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -29,7 +29,6 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/cli": "^0.4.0", "@backstage/core": "^0.3.1", "@backstage/test-utils": "^0.1.4", "@backstage/theme": "^0.2.1", @@ -46,6 +45,7 @@ "react-router-dom": "6.0.0-beta.0" }, "devDependencies": { + "@backstage/cli": "^0.4.0", "@types/jest": "^26.0.7", "@types/node": "^12.0.0" }, diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 86997a6767..d35080f204 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -29,7 +29,6 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/cli": "^0.4.0", "@backstage/core-api": "^0.2.4", "@backstage/test-utils-core": "^0.1.1", "@backstage/theme": "^0.2.0", @@ -46,6 +45,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { + "@backstage/cli": "^0.4.0", "@types/jest": "^26.0.7", "@types/node": "^12.0.0" }, From 1bb8afc61efdc788e3ea1f7f97c7c2977b5c29da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 10 Dec 2020 10:17:15 +0100 Subject: [PATCH 23/36] catalog-backend: move github util to own folder --- .../processors/GithubOrgReaderProcessor.ts | 2 +- .../processors/{util => github}/github.test.ts | 2 +- .../processors/{util => github}/github.ts | 0 .../src/ingestion/processors/github/index.ts | 17 +++++++++++++++++ .../src/ingestion/processors/index.ts | 8 +++----- 5 files changed, 22 insertions(+), 7 deletions(-) rename plugins/catalog-backend/src/ingestion/processors/{util => github}/github.test.ts (100%) rename plugins/catalog-backend/src/ingestion/processors/{util => github}/github.ts (100%) create mode 100644 plugins/catalog-backend/src/ingestion/processors/github/index.ts diff --git a/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts index 9aecb8b86e..27d5424e4e 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts @@ -18,9 +18,9 @@ import { LocationSpec } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { graphql } from '@octokit/graphql'; import { Logger } from 'winston'; +import { getOrganizationTeams, getOrganizationUsers } from './github'; import * as results from './results'; import { CatalogProcessor, CatalogProcessorEmit } from './types'; -import { getOrganizationTeams, getOrganizationUsers } from './util/github'; import { buildOrgHierarchy } from './util/org'; /** diff --git a/plugins/catalog-backend/src/ingestion/processors/util/github.test.ts b/plugins/catalog-backend/src/ingestion/processors/github/github.test.ts similarity index 100% rename from plugins/catalog-backend/src/ingestion/processors/util/github.test.ts rename to plugins/catalog-backend/src/ingestion/processors/github/github.test.ts index 1e35f44712..d10aba8acb 100644 --- a/plugins/catalog-backend/src/ingestion/processors/util/github.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/github/github.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ +import { msw } from '@backstage/test-utils'; import { graphql } from '@octokit/graphql'; import { graphql as graphqlMsw } from 'msw'; import { setupServer } from 'msw/node'; -import { msw } from '@backstage/test-utils'; import { getOrganizationTeams, getOrganizationUsers, diff --git a/plugins/catalog-backend/src/ingestion/processors/util/github.ts b/plugins/catalog-backend/src/ingestion/processors/github/github.ts similarity index 100% rename from plugins/catalog-backend/src/ingestion/processors/util/github.ts rename to plugins/catalog-backend/src/ingestion/processors/github/github.ts diff --git a/plugins/catalog-backend/src/ingestion/processors/github/index.ts b/plugins/catalog-backend/src/ingestion/processors/github/index.ts new file mode 100644 index 0000000000..e903ab669a --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/github/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { getOrganizationTeams, getOrganizationUsers } from './github'; diff --git a/plugins/catalog-backend/src/ingestion/processors/index.ts b/plugins/catalog-backend/src/ingestion/processors/index.ts index 49d4e03b64..2b477f18f9 100644 --- a/plugins/catalog-backend/src/ingestion/processors/index.ts +++ b/plugins/catalog-backend/src/ingestion/processors/index.ts @@ -16,11 +16,6 @@ import * as results from './results'; -export { results }; -export * from './types'; - -export { parseEntityYaml } from './util/parse'; - export { AnnotateLocationEntityProcessor } from './AnnotateLocationEntityProcessor'; export { BuiltinKindsEntityProcessor } from './BuiltinKindsEntityProcessor'; export { CodeOwnersProcessor } from './CodeOwnersProcessor'; @@ -32,4 +27,7 @@ export { MicrosoftGraphOrgReaderProcessor } from './MicrosoftGraphOrgReaderProce export { PlaceholderProcessor } from './PlaceholderProcessor'; export type { PlaceholderResolver } from './PlaceholderProcessor'; export { StaticLocationProcessor } from './StaticLocationProcessor'; +export * from './types'; export { UrlReaderProcessor } from './UrlReaderProcessor'; +export { parseEntityYaml } from './util/parse'; +export { results }; From 2bf71d60fbc74714e4a9ea15ed1c898f948a2b22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 10 Dec 2020 11:23:02 +0100 Subject: [PATCH 24/36] catalog-backend: get rid of winston warnings --- .../src/ingestion/processors/CodeOwnersProcessor.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts index 068a1e6e9a..be4f15e501 100644 --- a/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts @@ -14,9 +14,9 @@ * limitations under the License. */ +import { getVoidLogger } from '@backstage/backend-common'; import { LocationSpec } from '@backstage/catalog-model'; import { CodeOwnersEntry } from 'codeowners-utils'; -import { createLogger } from 'winston'; import { buildCodeOwnerUrl, buildUrl, @@ -28,7 +28,7 @@ import { resolveCodeOwner, } from './CodeOwnersProcessor'; -const logger = createLogger(); +const logger = getVoidLogger(); describe('CodeOwnersProcessor', () => { const mockUrl = ({ basePath = '' } = {}): string => From a6a2ca62049b7699b951133a02a9edb44ab04550 Mon Sep 17 00:00:00 2001 From: Askar Date: Thu, 10 Dec 2020 11:23:29 +0100 Subject: [PATCH 25/36] remove React's FC type from codebase (#3527) * WIP-packages: remove React's FC type from codebase * remove FC from other directories * fix build failures * add types to required packages --- .../quickstart-app-plugin/ExampleComponent.md | 4 +- .../ExampleFetchComponent.md | 6 +-- docs/api/utility-apis.md | 4 +- docs/reference/createPlugin-feature-flags.md | 2 +- docs/tutorials/quickstart-app-plugin.md | 10 ++--- packages/app/src/App.tsx | 4 +- packages/app/src/components/Root/LogoFull.tsx | 4 +- packages/app/src/components/Root/LogoIcon.tsx | 4 +- packages/app/src/components/Root/Root.tsx | 11 ++--- .../ExampleComponent/ExampleComponent.tsx.hbs | 4 +- .../ExampleFetchComponent.tsx.hbs | 6 +-- packages/core-api/package.json | 1 + .../core-api/src/apis/system/ApiProvider.tsx | 14 ++++-- packages/core-api/src/app/App.tsx | 13 +++--- packages/core-api/src/app/AppContext.tsx | 7 ++- .../core-api/src/app/AppThemeProvider.tsx | 6 +-- packages/core-api/src/icons/icons.tsx | 4 +- packages/core/package.json | 1 + packages/core/src/api-wrappers/createApp.tsx | 4 +- .../components/AlertDisplay/AlertDisplay.tsx | 6 +-- .../CopyTextButton/CopyTextButton.tsx | 4 +- .../FeatureCalloutCircular.tsx | 6 +-- .../HorizontalScrollGrid.tsx | 4 +- .../src/components/Lifecycle/Lifecycle.tsx | 4 +- packages/core/src/components/Link/Link.tsx | 2 +- .../LoginRequestListItem.tsx | 4 +- .../OAuthRequestDialog/OAuthRequestDialog.tsx | 6 +-- .../core/src/components/Progress/Progress.tsx | 4 +- .../src/components/ProgressBars/Gauge.tsx | 4 +- .../src/components/ProgressBars/GaugeCard.tsx | 4 +- .../components/ProgressBars/LinearGauge.tsx | 4 +- .../SimpleStepper/SimpleStepper.tsx | 6 +-- .../SimpleStepper/SimpleStepperFooter.tsx | 43 +++++++++++-------- .../SimpleStepper/SimpleStepperStep.tsx | 6 +-- .../core/src/components/Status/Status.tsx | 14 +++--- .../StructuredMetadataTable.stories.tsx | 4 +- .../SupportButton/SupportButton.tsx | 13 ++++-- .../src/components/Table/SubvalueCell.tsx | 4 +- packages/core/src/components/Tabs/TabBar.tsx | 4 +- .../core/src/components/Tabs/TabPanel.tsx | 5 +-- packages/core/src/components/Tabs/Tabs.tsx | 10 +---- .../src/components/TrendLine/TrendLine.tsx | 4 +- .../core/src/layout/BottomLink/BottomLink.tsx | 4 +- packages/core/src/layout/Content/Content.tsx | 6 +-- .../layout/ContentHeader/ContentHeader.tsx | 10 ++--- packages/core/src/layout/Header/Header.tsx | 18 +++----- .../HeaderActionMenu/HeaderActionMenu.tsx | 10 ++--- .../src/layout/HeaderLabel/HeaderLabel.tsx | 11 +++-- .../core/src/layout/HeaderTabs/HeaderTabs.tsx | 9 +++- .../core/src/layout/ItemCard/ItemCard.tsx | 6 +-- packages/core/src/layout/Page/Page.tsx | 4 +- packages/core/src/layout/Sidebar/Bar.tsx | 6 +-- packages/core/src/layout/Sidebar/Intro.tsx | 8 ++-- packages/core/src/layout/Sidebar/Page.tsx | 9 +++- .../core/src/layout/SignInPage/SignInPage.tsx | 6 +-- .../core/src/layout/TabbedCard/TabbedCard.tsx | 13 ++++-- .../default-app/packages/app/src/App.tsx | 4 +- .../default-app/packages/app/src/LogoFull.tsx | 4 +- .../default-app/packages/app/src/LogoIcon.tsx | 4 +- .../default-app/packages/app/src/sidebar.tsx | 4 +- packages/dev-utils/src/devApp/render.tsx | 4 +- .../src/testUtils/appWrappers.test.tsx | 10 ++--- .../test-utils/src/testUtils/appWrappers.tsx | 6 +-- .../EntityContextMenu/EntityContextMenu.tsx | 4 +- .../FavouriteEntity/FavouriteEntity.tsx | 2 +- .../UnregisterEntityDialog.tsx | 6 +-- .../src/hooks/useStarredEntities.test.tsx | 4 +- .../lib/ActionOutput/ActionOutput.tsx | 11 +++-- plugins/circleci/src/route-refs.tsx | 4 +- .../WorkflowRunsTable/WorkflowRunsTable.tsx | 6 +-- .../AlertActionCardList.tsx | 4 +- .../explore/src/components/ExploreCard.tsx | 4 +- .../WorkflowRunsTable/WorkflowRunsTable.tsx | 6 +-- .../components/ClusterList/ClusterList.tsx | 4 +- .../components/ClusterPage/ClusterPage.tsx | 4 +- .../components/ClusterTable/ClusterTable.tsx | 4 +- .../ClusterTemplateCard.tsx | 4 +- .../ClusterTemplateCardList.tsx | 4 +- .../components/ProfileCard/ProfileCard.tsx | 4 +- .../ProfileCardList/ProfileCardList.tsx | 4 +- .../ProfileCatalog/ProfileCatalog.tsx | 4 +- .../GraphiQLBrowser/GraphiQLBrowser.tsx | 4 +- .../lib/ActionOutput/ActionOutput.tsx | 8 ++-- .../BuildsPage/lib/CITable/CITable.tsx | 6 +-- plugins/lighthouse/package.json | 2 +- .../components/AuditList/AuditListTable.tsx | 4 +- .../src/components/AuditList/index.tsx | 4 +- .../src/components/AuditStatusIcon/index.tsx | 4 +- .../src/components/AuditView/index.tsx | 11 ++--- .../Cards/LastLighthouseAuditCard.tsx | 18 +++++--- .../src/components/CreateAudit/index.tsx | 4 +- .../src/hooks/useWebsiteForEntity.test.tsx | 4 +- .../NewRelicComponent/NewRelicComponent.tsx | 4 +- .../NewRelicFetchComponent.tsx | 8 ++-- .../src/components/ErrorCell/ErrorCell.tsx | 6 +-- .../src/components/ErrorGraph/ErrorGraph.tsx | 6 +-- 96 files changed, 316 insertions(+), 290 deletions(-) diff --git a/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md b/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md index 5d633544e3..9b5d77bc7c 100644 --- a/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md +++ b/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md @@ -3,7 +3,7 @@ ExampleComponent.tsx reference ```tsx -import React, { FC } from 'react'; +import React from 'react'; import { Typography, Grid } from '@material-ui/core'; import { InfoCard, @@ -18,7 +18,7 @@ import { import { useApi } from '@backstage/core-api'; import ExampleFetchComponent from '../ExampleFetchComponent'; -const ExampleComponent: FC<{}> = () => { +const ExampleComponent = () => { const identityApi = useApi(identityApiRef); const userId = identityApi.getUserId(); const profile = identityApi.getProfile(); diff --git a/contrib/docs/tutorials/quickstart-app-plugin/ExampleFetchComponent.md b/contrib/docs/tutorials/quickstart-app-plugin/ExampleFetchComponent.md index 08d14e8c4c..6061b69e93 100644 --- a/contrib/docs/tutorials/quickstart-app-plugin/ExampleFetchComponent.md +++ b/contrib/docs/tutorials/quickstart-app-plugin/ExampleFetchComponent.md @@ -3,7 +3,7 @@ ExampleFetchComponent.tsx reference ```tsx -import React, { FC } from 'react'; +import React from 'react'; import { useAsync } from 'react-use'; import Alert from '@material-ui/lab/Alert'; import { @@ -57,7 +57,7 @@ type DenseTableProps = { viewer: Viewer; }; -export const DenseTable: FC = ({ viewer }) => { +export const DenseTable = ({ viewer }: DenseTableProps) => { const columns: TableColumn[] = [ { title: 'Name', field: 'name' }, { title: 'Created', field: 'createdAt' }, @@ -76,7 +76,7 @@ export const DenseTable: FC = ({ viewer }) => { ); }; -const ExampleFetchComponent: FC<{}> = () => { +const ExampleFetchComponent = () => { const auth = useApi(githubAuthApiRef); const { value, loading, error } = useAsync(async (): Promise => { diff --git a/docs/api/utility-apis.md b/docs/api/utility-apis.md index 2547db5c5c..ff322c136f 100644 --- a/docs/api/utility-apis.md +++ b/docs/api/utility-apis.md @@ -33,10 +33,10 @@ hook exported by `@backstage/core`, or the `withApis` HOC if you prefer class components. For example, the `ErrorApi` can be accessed like this: ```tsx -import React, { FC } from 'react'; +import React from 'react'; import { useApi, errorApiRef } from '@backstage/core'; -export const MyComponent: FC<{}> = () => { +export const MyComponent = () => { const errorApi = useApi(errorApiRef); // Signal to the app that something went wrong, and display the error to the user. diff --git a/docs/reference/createPlugin-feature-flags.md b/docs/reference/createPlugin-feature-flags.md index bcea80e26b..622c085291 100644 --- a/docs/reference/createPlugin-feature-flags.md +++ b/docs/reference/createPlugin-feature-flags.md @@ -27,7 +27,7 @@ To inspect the state of a feature flag inside your plugin, you can use the `FeatureFlagsApi`, accessed via the `featureFlagsApiRef`. For example: ```tsx -import React, { FC } from 'react'; +import React from 'react'; import { Button } from '@material-ui/core'; import { featureFlagsApiRef, useApi } from '@backstage/core'; diff --git a/docs/tutorials/quickstart-app-plugin.md b/docs/tutorials/quickstart-app-plugin.md index 216ff9659f..0f8d9bb84b 100644 --- a/docs/tutorials/quickstart-app-plugin.md +++ b/docs/tutorials/quickstart-app-plugin.md @@ -81,13 +81,13 @@ import { useApi } from '@backstage/core-api'; _from inline:_ ```tsx -const ExampleComponent: FC<{}> = () => ( ... ) +const ExampleComponent = () => ( ... ) ``` _to block:_ ```tsx -const ExampleComponent: FC<{}> = () => { +const ExampleComponent = () => { return ( ... @@ -135,7 +135,7 @@ changes, let's start by wiping this component clean. 1. Replace everything in the file with the following: ```tsx -import React, { FC } from 'react'; +import React from 'react'; import { useAsync } from 'react-use'; import Alert from '@material-ui/lab/Alert'; import { @@ -147,7 +147,7 @@ import { import { useApi } from '@backstage/core-api'; import { graphql } from '@octokit/graphql'; -const ExampleFetchComponent: FC<{}> = () => { +const ExampleFetchComponent = () => { return
Nothing to see yet
; }; @@ -223,7 +223,7 @@ type DenseTableProps = { viewer: Viewer; }; -export const DenseTable: FC = ({ viewer }) => { +export const DenseTable = ({ viewer }: DenseTableProps) => { const columns: TableColumn[] = [ { title: 'Name', field: 'name' }, { title: 'Created', field: 'createdAt' }, diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 28aebeeb79..627b5d19aa 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -21,7 +21,7 @@ import { SignInPage, createRouteRef, } from '@backstage/core'; -import React, { FC } from 'react'; +import React from 'react'; import Root from './components/Root'; import * as plugins from './plugins'; import { apis } from './apis'; @@ -92,7 +92,7 @@ const AppRoutes = () => ( ); -const App: FC<{}> = () => ( +const App = () => ( diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx index d2b1bf1080..2fb767465b 100644 --- a/packages/app/src/components/Root/LogoFull.tsx +++ b/packages/app/src/components/Root/LogoFull.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { FC } from 'react'; +import React from 'react'; import { makeStyles } from '@material-ui/core'; const useStyles = makeStyles({ @@ -26,7 +26,7 @@ const useStyles = makeStyles({ fill: '#7df3e1', }, }); -const LogoFull: FC<{}> = () => { +const LogoFull = () => { const classes = useStyles(); return ( diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx index d70be3dd32..507e47ddb9 100644 --- a/packages/app/src/components/Root/LogoIcon.tsx +++ b/packages/app/src/components/Root/LogoIcon.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { FC } from 'react'; +import React from 'react'; import { makeStyles } from '@material-ui/core'; const useStyles = makeStyles({ @@ -27,7 +27,7 @@ const useStyles = makeStyles({ }, }); -const LogoIcon: FC<{}> = () => { +const LogoIcon = () => { const classes = useStyles(); return ( diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 47e7d32d1a..52dd397418 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -14,8 +14,7 @@ * limitations under the License. */ -import React, { FC, useContext } from 'react'; -import PropTypes from 'prop-types'; +import React, { useContext, PropsWithChildren } from 'react'; import { Link, makeStyles } from '@material-ui/core'; import HomeIcon from '@material-ui/icons/Home'; import ExtensionIcon from '@material-ui/icons/Extension'; @@ -55,7 +54,7 @@ const useSidebarLogoStyles = makeStyles({ }, }); -const SidebarLogo: FC<{}> = () => { +const SidebarLogo = () => { const classes = useSidebarLogoStyles(); const { isOpen } = useContext(SidebarContext); @@ -73,7 +72,7 @@ const SidebarLogo: FC<{}> = () => { ); }; -const Root: FC<{}> = ({ children }) => ( +const Root = ({ children }: PropsWithChildren<{}>) => ( @@ -102,8 +101,4 @@ const Root: FC<{}> = ({ children }) => ( ); -Root.propTypes = { - children: PropTypes.node, -}; - export default Root; diff --git a/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs b/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs index e08f1650d5..5f90f2de1e 100644 --- a/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs +++ b/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React from 'react'; import { Typography, Grid } from '@material-ui/core'; import { InfoCard, @@ -11,7 +11,7 @@ import { } from '@backstage/core'; import ExampleFetchComponent from '../ExampleFetchComponent'; -const ExampleComponent: FC<{}> = () => ( +const ExampleComponent = () => (
diff --git a/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx.hbs b/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx.hbs index 0af27a5935..8cc5ed2ab7 100644 --- a/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx.hbs +++ b/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx.hbs @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import { Table, TableColumn, Progress } from '@backstage/core'; import Alert from '@material-ui/lab/Alert'; @@ -38,7 +38,7 @@ type DenseTableProps = { users: User[]; }; -export const DenseTable: FC = ({ users }) => { +export const DenseTable = ({ users }: DenseTableProps) => { const classes = useStyles(); const columns: TableColumn[] = [ @@ -73,7 +73,7 @@ export const DenseTable: FC = ({ users }) => { ); }; -const ExampleFetchComponent: FC<{}> = () => { +const ExampleFetchComponent = () => { const { value, loading, error } = useAsync(async (): Promise => { const response = await fetch('https://randomuser.me/api/?results=20'); const data = await response.json(); diff --git a/packages/core-api/package.json b/packages/core-api/package.json index bfff89f27a..b1397f2e38 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -35,6 +35,7 @@ "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@types/react": "^16.9", + "@types/prop-types": "^15.7.3", "prop-types": "^15.7.2", "react": "^16.12.0", "react-router-dom": "6.0.0-beta.0", diff --git a/packages/core-api/src/apis/system/ApiProvider.tsx b/packages/core-api/src/apis/system/ApiProvider.tsx index f2aa70244e..91d35e5ee7 100644 --- a/packages/core-api/src/apis/system/ApiProvider.tsx +++ b/packages/core-api/src/apis/system/ApiProvider.tsx @@ -14,7 +14,12 @@ * limitations under the License. */ -import React, { FC, createContext, useContext, ReactNode } from 'react'; +import React, { + createContext, + useContext, + ReactNode, + PropsWithChildren, +} from 'react'; import PropTypes from 'prop-types'; import { ApiRef, ApiHolder, TypesToApiRefs } from './types'; import { ApiAggregator } from './ApiAggregator'; @@ -26,7 +31,10 @@ type ApiProviderProps = { const Context = createContext(undefined); -export const ApiProvider: FC = ({ apis, children }) => { +export const ApiProvider = ({ + apis, + children, +}: PropsWithChildren) => { const parentHolder = useContext(Context); const holder = parentHolder ? new ApiAggregator(apis, parentHolder) : apis; @@ -62,7 +70,7 @@ export function withApis(apis: TypesToApiRefs) { return function withApisWrapper

( WrappedComponent: React.ComponentType

, ) { - const Hoc: FC> = props => { + const Hoc = (props: PropsWithChildren>) => { const apiHolder = useContext(Context); if (!apiHolder) { diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx index 3d32259337..3ca91966bf 100644 --- a/packages/core-api/src/app/App.tsx +++ b/packages/core-api/src/app/App.tsx @@ -15,10 +15,10 @@ */ import React, { ComponentType, - FC, useMemo, useState, ReactElement, + PropsWithChildren, } from 'react'; import { Route, Routes, Navigate } from 'react-router-dom'; import { AppContextProvider } from './AppContext'; @@ -196,7 +196,7 @@ export class PrivateAppImpl implements BackstageApp { } getProvider(): ComponentType<{}> { - const Provider: FC<{}> = ({ children }) => { + const Provider = ({ children }: PropsWithChildren<{}>) => { const appThemeApi = useMemo( () => AppThemeSelector.createWithStorage(this.themes), [], @@ -233,10 +233,13 @@ export class PrivateAppImpl implements BackstageApp { } = this.components; // This wraps the sign-in page and waits for sign-in to be completed before rendering the app - const SignInPageWrapper: FC<{ + const SignInPageWrapper = ({ + component: Component, + children, + }: { component: ComponentType; children: ReactElement; - }> = ({ component: Component, children }) => { + }) => { const [result, setResult] = useState(); if (result) { @@ -247,7 +250,7 @@ export class PrivateAppImpl implements BackstageApp { return ; }; - const AppRouter: FC<{}> = ({ children }) => { + const AppRouter = ({ children }: PropsWithChildren<{}>) => { const configApi = useApi(configApiRef); let { pathname } = new URL( diff --git a/packages/core-api/src/app/AppContext.tsx b/packages/core-api/src/app/AppContext.tsx index ec3831992f..e0659d9e29 100644 --- a/packages/core-api/src/app/AppContext.tsx +++ b/packages/core-api/src/app/AppContext.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { createContext, useContext, FC } from 'react'; +import React, { createContext, PropsWithChildren, useContext } from 'react'; import { BackstageApp } from './types'; const Context = createContext(undefined); @@ -23,7 +23,10 @@ type Props = { app: BackstageApp; }; -export const AppContextProvider: FC = ({ app, children }) => ( +export const AppContextProvider = ({ + app, + children, +}: PropsWithChildren) => ( ); diff --git a/packages/core-api/src/app/AppThemeProvider.tsx b/packages/core-api/src/app/AppThemeProvider.tsx index 6bbcaea93a..993de23a7a 100644 --- a/packages/core-api/src/app/AppThemeProvider.tsx +++ b/packages/core-api/src/app/AppThemeProvider.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { FC, useMemo, useEffect, useState } from 'react'; +import React, { useMemo, useEffect, useState, PropsWithChildren } from 'react'; import { ThemeProvider, CssBaseline } from '@material-ui/core'; import { useApi, appThemeApiRef, AppTheme } from '../apis'; import { useObservable } from 'react-use'; @@ -68,7 +68,7 @@ const useShouldPreferDarkTheme = () => { return shouldPreferDark; }; -export const AppThemeProvider: FC<{}> = ({ children }) => { +export function AppThemeProvider({ children }: PropsWithChildren<{}>) { const appThemeApi = useApi(appThemeApiRef); const themeId = useObservable( appThemeApi.activeThemeId$(), @@ -94,4 +94,4 @@ export const AppThemeProvider: FC<{}> = ({ children }) => { {children} ); -}; +} diff --git a/packages/core-api/src/icons/icons.tsx b/packages/core-api/src/icons/icons.tsx index 488973b664..50c4b68e43 100644 --- a/packages/core-api/src/icons/icons.tsx +++ b/packages/core-api/src/icons/icons.tsx @@ -17,7 +17,7 @@ import { SvgIconProps } from '@material-ui/core'; import PeopleIcon from '@material-ui/icons/People'; import PersonIcon from '@material-ui/icons/Person'; -import React, { FC } from 'react'; +import React from 'react'; import { useApp } from '../app/AppContext'; import { IconComponent, SystemIconKey, SystemIcons } from './types'; @@ -27,7 +27,7 @@ export const defaultSystemIcons: SystemIcons = { }; const overridableSystemIcon = (key: SystemIconKey): IconComponent => { - const Component: FC = props => { + const Component = (props: SvgIconProps) => { const app = useApp(); const Icon = app.getSystemIcon(key); return ; diff --git a/packages/core/package.json b/packages/core/package.json index dd48a5d4ce..dc9ed4c540 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -38,6 +38,7 @@ "@types/dagre": "^0.7.44", "@types/react": "^16.9", "@types/react-sparklines": "^1.7.0", + "@types/prop-types": "^15.7.3", "classnames": "^2.2.6", "clsx": "^1.1.0", "d3-selection": "^2.0.0", diff --git a/packages/core/src/api-wrappers/createApp.tsx b/packages/core/src/api-wrappers/createApp.tsx index 4a1e58db42..c99ba0ed03 100644 --- a/packages/core/src/api-wrappers/createApp.tsx +++ b/packages/core/src/api-wrappers/createApp.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { FC } from 'react'; +import React from 'react'; import privateExports, { AppOptions, defaultSystemIcons, @@ -93,7 +93,7 @@ export function createApp(options?: AppOptions) { const DefaultNotFoundPage = () => ( ); - const DefaultBootErrorPage: FC = ({ step, error }) => { + const DefaultBootErrorPage = ({ step, error }: BootErrorPageProps) => { let message = ''; if (step === 'load-config') { message = `The configuration failed to load, someone should have a look at this error: ${error.message}`; diff --git a/packages/core/src/components/AlertDisplay/AlertDisplay.tsx b/packages/core/src/components/AlertDisplay/AlertDisplay.tsx index 30940f68ac..6d6646fa18 100644 --- a/packages/core/src/components/AlertDisplay/AlertDisplay.tsx +++ b/packages/core/src/components/AlertDisplay/AlertDisplay.tsx @@ -14,16 +14,14 @@ * limitations under the License. */ -import React, { FC, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Snackbar, IconButton } from '@material-ui/core'; import CloseIcon from '@material-ui/icons/Close'; import { Alert } from '@material-ui/lab'; import { AlertMessage, useApi, alertApiRef } from '@backstage/core-api'; -type Props = {}; - // TODO: improve on this and promote to a shared component for use by all apps. -export const AlertDisplay: FC = () => { +export const AlertDisplay = () => { const [messages, setMessages] = useState>([]); const alertApi = useApi(alertApiRef); diff --git a/packages/core/src/components/CopyTextButton/CopyTextButton.tsx b/packages/core/src/components/CopyTextButton/CopyTextButton.tsx index cb322e5b56..9f6cdd7e7c 100644 --- a/packages/core/src/components/CopyTextButton/CopyTextButton.tsx +++ b/packages/core/src/components/CopyTextButton/CopyTextButton.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { FC, useRef, useState, MouseEventHandler } from 'react'; +import React, { useRef, useState, MouseEventHandler } from 'react'; import { IconButton, makeStyles, Tooltip } from '@material-ui/core'; import PropTypes from 'prop-types'; import CopyIcon from '@material-ui/icons/FileCopy'; @@ -56,7 +56,7 @@ const defaultProps = { tooltipText: 'Text copied to clipboard', }; -export const CopyTextButton: FC = props => { +export const CopyTextButton = (props: Props) => { const { text, tooltipDelay, tooltipText } = { ...defaultProps, ...props, diff --git a/packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.tsx b/packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.tsx index 9dc0d25681..64722c24b0 100644 --- a/packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.tsx +++ b/packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.tsx @@ -16,7 +16,7 @@ import { ClickAwayListener, makeStyles, Typography } from '@material-ui/core'; import React, { - FC, + PropsWithChildren, useCallback, useEffect, useLayoutEffect, @@ -93,12 +93,12 @@ type Placement = { textWidth: number; }; -export const FeatureCalloutCircular: FC = ({ +export const FeatureCalloutCircular = ({ featureId, title, description, children, -}) => { +}: PropsWithChildren) => { const { show, hide } = useShowCallout(featureId); const portalElement = usePortal('core.callout'); const wrapperRef = useRef(null); diff --git a/packages/core/src/components/HorizontalScrollGrid/HorizontalScrollGrid.tsx b/packages/core/src/components/HorizontalScrollGrid/HorizontalScrollGrid.tsx index 4e321dfa95..073fb960e1 100644 --- a/packages/core/src/components/HorizontalScrollGrid/HorizontalScrollGrid.tsx +++ b/packages/core/src/components/HorizontalScrollGrid/HorizontalScrollGrid.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { FC } from 'react'; +import React, { PropsWithChildren } from 'react'; import classNames from 'classnames'; import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'; import ChevronRightIcon from '@material-ui/icons/ChevronRight'; @@ -181,7 +181,7 @@ function useSmoothScroll( return setScrollTarget; } -export const HorizontalScrollGrid: FC = props => { +export const HorizontalScrollGrid = (props: PropsWithChildren) => { const { scrollStep = 100, scrollSpeed = 50, diff --git a/packages/core/src/components/Lifecycle/Lifecycle.tsx b/packages/core/src/components/Lifecycle/Lifecycle.tsx index 416df27a30..d388452cdc 100644 --- a/packages/core/src/components/Lifecycle/Lifecycle.tsx +++ b/packages/core/src/components/Lifecycle/Lifecycle.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { FC } from 'react'; +import React from 'react'; import CSS from 'csstype'; import { makeStyles } from '@material-ui/core'; @@ -38,7 +38,7 @@ const useStyles = makeStyles({ }, }); -export const Lifecycle: FC = props => { +export const Lifecycle = (props: Props) => { const classes = useStyles(props); const { shorthand, alpha } = props; return shorthand ? ( diff --git a/packages/core/src/components/Link/Link.tsx b/packages/core/src/components/Link/Link.tsx index 59a9604fa7..c5426ac434 100644 --- a/packages/core/src/components/Link/Link.tsx +++ b/packages/core/src/components/Link/Link.tsx @@ -19,7 +19,7 @@ import { Link as MaterialLink } from '@material-ui/core'; import { Link as RouterLink } from 'react-router-dom'; type Props = ComponentProps & - ComponentProps & { component?: React.FC }; + ComponentProps & { component?: React.ReactNode }; /** * Thin wrapper on top of material-ui's Link component diff --git a/packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx b/packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx index cc7d99660b..cc1df3b529 100644 --- a/packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx +++ b/packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx @@ -22,7 +22,7 @@ import { Typography, Theme, } from '@material-ui/core'; -import React, { FC, useState } from 'react'; +import React, { useState } from 'react'; import { PendingAuthRequest } from '@backstage/core-api'; const useItemStyles = makeStyles(theme => ({ @@ -37,7 +37,7 @@ type RowProps = { setBusy: (busy: boolean) => void; }; -const LoginRequestListItem: FC = ({ request, busy, setBusy }) => { +const LoginRequestListItem = ({ request, busy, setBusy }: RowProps) => { const classes = useItemStyles(); const [error, setError] = useState(); diff --git a/packages/core/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx b/packages/core/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx index 07078c3fa2..06b53332dc 100644 --- a/packages/core/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx +++ b/packages/core/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx @@ -24,7 +24,7 @@ import { Theme, Button, } from '@material-ui/core'; -import React, { FC, useMemo, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { useObservable } from 'react-use'; import LoginRequestListItem from './LoginRequestListItem'; import { useApi, oauthRequestApiRef } from '@backstage/core-api'; @@ -41,9 +41,7 @@ const useStyles = makeStyles(theme => ({ }, })); -type OAuthRequestDialogProps = {}; - -export const OAuthRequestDialog: FC = () => { +export const OAuthRequestDialog = () => { const classes = useStyles(); const [busy, setBusy] = useState(false); const oauthRequestApi = useApi(oauthRequestApiRef); diff --git a/packages/core/src/components/Progress/Progress.tsx b/packages/core/src/components/Progress/Progress.tsx index 80f4f38cc9..aacdf8821b 100644 --- a/packages/core/src/components/Progress/Progress.tsx +++ b/packages/core/src/components/Progress/Progress.tsx @@ -14,10 +14,10 @@ * limitations under the License. */ -import React, { FC, useState, useEffect } from 'react'; +import React, { useState, useEffect, PropsWithChildren } from 'react'; import { LinearProgress, LinearProgressProps } from '@material-ui/core'; -export const Progress: FC = props => { +export const Progress = (props: PropsWithChildren) => { const [isVisible, setIsVisible] = useState(false); useEffect(() => { diff --git a/packages/core/src/components/ProgressBars/Gauge.tsx b/packages/core/src/components/ProgressBars/Gauge.tsx index 4c339bf8e1..ca6a3a66ab 100644 --- a/packages/core/src/components/ProgressBars/Gauge.tsx +++ b/packages/core/src/components/ProgressBars/Gauge.tsx @@ -17,7 +17,7 @@ import { makeStyles, useTheme } from '@material-ui/core'; import { BackstageTheme } from '@backstage/theme'; import { Circle } from 'rc-progress'; -import React, { FC } from 'react'; +import React from 'react'; const useStyles = makeStyles(theme => ({ root: { @@ -77,7 +77,7 @@ export function getProgressColor( return palette.status.ok; } -export const Gauge: FC = props => { +export const Gauge = (props: Props) => { const classes = useStyles(props); const theme = useTheme(); const { value, fractional, inverse, unit, max } = { diff --git a/packages/core/src/components/ProgressBars/GaugeCard.tsx b/packages/core/src/components/ProgressBars/GaugeCard.tsx index 4eb4b2e075..7f281c67a5 100644 --- a/packages/core/src/components/ProgressBars/GaugeCard.tsx +++ b/packages/core/src/components/ProgressBars/GaugeCard.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { FC } from 'react'; +import React from 'react'; import { makeStyles } from '@material-ui/core'; import { InfoCard } from '../../layout/InfoCard'; import { BottomLinkProps } from '../../layout/BottomLink'; @@ -36,7 +36,7 @@ const useStyles = makeStyles({ }, }); -export const GaugeCard: FC = props => { +export const GaugeCard = (props: Props) => { const classes = useStyles(props); const { title, subheader, progress, deepLink, variant } = props; diff --git a/packages/core/src/components/ProgressBars/LinearGauge.tsx b/packages/core/src/components/ProgressBars/LinearGauge.tsx index a6aea59f19..9bb7b34c09 100644 --- a/packages/core/src/components/ProgressBars/LinearGauge.tsx +++ b/packages/core/src/components/ProgressBars/LinearGauge.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { FC } from 'react'; +import React from 'react'; import { Tooltip, useTheme } from '@material-ui/core'; // @ts-ignore import { Line } from 'rc-progress'; @@ -28,7 +28,7 @@ type Props = { value: number; }; -export const LinearGauge: FC = ({ value }) => { +export const LinearGauge = ({ value }: Props) => { const theme = useTheme(); if (isNaN(value)) { return null; diff --git a/packages/core/src/components/SimpleStepper/SimpleStepper.tsx b/packages/core/src/components/SimpleStepper/SimpleStepper.tsx index 75fc1e5e62..ff65534843 100644 --- a/packages/core/src/components/SimpleStepper/SimpleStepper.tsx +++ b/packages/core/src/components/SimpleStepper/SimpleStepper.tsx @@ -16,9 +16,9 @@ import React, { Children, isValidElement, - FC, useState, useEffect, + PropsWithChildren, } from 'react'; import { Stepper as MuiStepper } from '@material-ui/core'; @@ -47,12 +47,12 @@ export interface StepperProps { activeStep?: number; } -export const SimpleStepper: FC = ({ +export const SimpleStepper = ({ children, elevated, onStepChange, activeStep = 0, -}) => { +}: PropsWithChildren) => { const [stepIndex, setStepIndex] = useState(activeStep); const [stepHistory, setStepHistory] = useState([0]); diff --git a/packages/core/src/components/SimpleStepper/SimpleStepperFooter.tsx b/packages/core/src/components/SimpleStepper/SimpleStepperFooter.tsx index c8cddb375d..a49e20913c 100644 --- a/packages/core/src/components/SimpleStepper/SimpleStepperFooter.tsx +++ b/packages/core/src/components/SimpleStepper/SimpleStepperFooter.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useContext, FC, ReactNode } from 'react'; +import React, { useContext, ReactNode, PropsWithChildren } from 'react'; import { Button, makeStyles } from '@material-ui/core'; import { StepActions } from './SimpleStepperStep'; import { VerticalStepperContext } from './SimpleStepper'; @@ -27,20 +27,33 @@ const useStyles = makeStyles(theme => ({ }, })); -export const RestartBtn: FC<{ +interface CommonBtnProps { text?: string; handleClick?: () => void; stepIndex: number; -}> = ({ text, handleClick }) => ( - -); -const NextBtn: FC<{ - text?: string; - handleClick?: () => void; +} +interface RestartBtnProps extends CommonBtnProps {} + +interface NextBtnProps extends CommonBtnProps { disabled?: boolean; last?: boolean; stepIndex: number; -}> = ({ text, handleClick, disabled, last, stepIndex }) => ( +} +interface BackBtnProps extends CommonBtnProps { + disabled?: boolean; + stepIndex: number; +} +export const RestartBtn = ({ text, handleClick }: RestartBtnProps) => ( + +); + +const NextBtn = ({ + text, + handleClick, + disabled, + last, + stepIndex, +}: NextBtnProps) => (