From da661f836e6a730f2a157feb8ec367d7603c9ab0 Mon Sep 17 00:00:00 2001 From: Debajyoti Halder Date: Tue, 26 Jan 2021 12:29:48 +0530 Subject: [PATCH 001/110] Update eslint rules --- packages/cli/config/eslint.backend.js | 4 ++-- packages/cli/config/eslint.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/cli/config/eslint.backend.js b/packages/cli/config/eslint.backend.js index 7acb538d38..6739d1c2ce 100644 --- a/packages/cli/config/eslint.backend.js +++ b/packages/cli/config/eslint.backend.js @@ -38,8 +38,8 @@ module.exports = { }, ignorePatterns: ['.eslintrc.js', '**/dist/**', '**/dist-types/**'], rules: { - '@typescript-eslint/no-shadow': 'off', - '@typescript-eslint/no-redeclare': 'off', + '@typescript-eslint/no-shadow': 'error', + '@typescript-eslint/no-redeclare': 'error', 'no-console': 0, // Permitted in console programs 'new-cap': ['error', { capIsNew: false }], // Because Express constructs things e.g. like 'const r = express.Router()' diff --git a/packages/cli/config/eslint.js b/packages/cli/config/eslint.js index 044d737d15..c440e53bc0 100644 --- a/packages/cli/config/eslint.js +++ b/packages/cli/config/eslint.js @@ -45,9 +45,9 @@ module.exports = { }, ignorePatterns: ['.eslintrc.js', '**/dist/**', '**/dist-types/**'], rules: { - '@typescript-eslint/no-shadow': 'off', - '@typescript-eslint/no-redeclare': 'off', - 'no-undef': 'off', + '@typescript-eslint/no-shadow': 'error', + '@typescript-eslint/no-redeclare': 'error', + 'import/newline-after-import': 'error', 'import/no-duplicates': 'warn', 'import/no-extraneous-dependencies': [ From 6106345e6d139a64a7713e066a670a9bdff002db Mon Sep 17 00:00:00 2001 From: Debajyoti Halder Date: Fri, 29 Jan 2021 11:55:12 +0530 Subject: [PATCH 002/110] Update project with latest eslint rules --- .../src/reading/tree/TarArchiveResponse.ts | 4 +-- .../src/reading/tree/ZipArchiveResponse.ts | 10 +++---- packages/cli/config/eslint.backend.js | 2 ++ packages/cli/config/eslint.js | 2 ++ packages/cli/src/commands/config/print.ts | 4 +-- .../cli/src/commands/versions/bump.test.ts | 4 +-- .../lib/bundler/LinkedPackageResolvePlugin.ts | 2 +- packages/cli/src/lib/bundler/paths.ts | 6 ++--- .../config-loader/src/lib/schema/collect.ts | 4 ++- .../lib/AuthConnector/DirectAuthConnector.ts | 10 +++---- packages/core-api/src/routing/FlatRoutes.tsx | 4 +-- .../core/src/layout/HeaderTabs/HeaderTabs.tsx | 4 +-- packages/e2e-test/src/commands/run.ts | 6 ++--- .../integration/src/ScmIntegrations.test.ts | 8 +++--- .../github/GithubCredentialsProvider.test.ts | 12 ++++----- .../src/github/GithubCredentialsProvider.ts | 2 +- .../src/stages/publish/local.test.ts | 4 +-- .../ApiDefinitionCard.test.tsx | 4 +-- .../src/catalog/DatabaseEntitiesCatalog.ts | 2 +- ...sOrganizationCloudAccountProcessor.test.ts | 9 ++++--- .../processors/BuiltinKindsEntityProcessor.ts | 4 +-- .../src/components/ImportComponentForm.tsx | 14 +++++----- .../src/components/ImportComponentPage.tsx | 12 ++++----- .../components/EntityLayout/EntityLayout.tsx | 6 +++-- .../components/EntityLayout/TabbedLayout.tsx | 4 +-- .../components/EntitySwitch/EntitySwitch.tsx | 4 +-- plugins/catalog/src/components/isOwnerOf.ts | 8 +++--- .../AlertActionCardList.tsx | 4 +-- .../src/components/BarChart/BarChartLabel.tsx | 4 +-- .../src/components/BarChart/BarChartSteps.tsx | 4 +-- .../ProductInsights/ProductInsights.tsx | 27 ++++++++++--------- plugins/cost-insights/src/utils/change.ts | 4 +-- plugins/jenkins/src/components/useBuilds.ts | 10 +++---- .../getKubernetesObjectsForServiceHandler.ts | 6 ++--- .../DeploymentDrawer.tsx | 10 +++---- .../HorizontalPodAutoscalerDrawer.tsx | 14 +++++----- .../IngressesAccordions/IngressDrawer.tsx | 4 +-- .../src/components/Pods/PodDrawer.tsx | 16 +++++------ .../ServicesAccordions/ServiceDrawer.tsx | 4 +-- plugins/kubernetes/src/utils/pod.tsx | 8 +++--- .../Group/GroupProfile/GroupProfileCard.tsx | 4 +-- plugins/org/src/components/isOwnerOf.ts | 8 +++--- .../Escalation/EscalationPolicy.tsx | 4 +-- .../TriggerDialog/TriggerDialog.tsx | 4 +-- .../stages/prepare/bitbucket.test.ts | 8 +++--- .../scaffolder/stages/prepare/github.test.ts | 8 +++--- .../MultistepJsonForm/MultistepJsonForm.tsx | 4 +-- .../components/TemplatePage/TemplatePage.tsx | 12 +++++---- .../components/SearchResult/SearchResult.tsx | 8 +++--- 49 files changed, 173 insertions(+), 157 deletions(-) diff --git a/packages/backend-common/src/reading/tree/TarArchiveResponse.ts b/packages/backend-common/src/reading/tree/TarArchiveResponse.ts index 5927eb75a1..05dd140b16 100644 --- a/packages/backend-common/src/reading/tree/TarArchiveResponse.ts +++ b/packages/backend-common/src/reading/tree/TarArchiveResponse.ts @@ -15,7 +15,7 @@ */ import tar, { Parse, ParseStream, ReadEntry } from 'tar'; -import path from 'path'; +import platformPath from 'path'; import fs from 'fs-extra'; import { Readable, pipeline as pipelineCb } from 'stream'; import { promisify } from 'util'; @@ -136,7 +136,7 @@ export class TarArchiveResponse implements ReadTreeResponse { const dir = options?.targetDir ?? - (await fs.mkdtemp(path.join(this.workDir, 'backstage-'))); + (await fs.mkdtemp(platformPath.join(this.workDir, 'backstage-'))); const strip = this.subPath ? this.subPath.split('/').length - 1 : 0; diff --git a/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts b/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts index 07d34faaa3..15bc558132 100644 --- a/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts +++ b/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import path from 'path'; +import platformPath from 'path'; import fs from 'fs-extra'; import unzipper, { Entry } from 'unzipper'; import archiver from 'archiver'; @@ -131,7 +131,7 @@ export class ZipArchiveResponse implements ReadTreeResponse { const dir = options?.targetDir ?? - (await fs.mkdtemp(path.join(this.workDir, 'backstage-'))); + (await fs.mkdtemp(platformPath.join(this.workDir, 'backstage-'))); await this.stream .pipe(unzipper.Parse()) @@ -140,11 +140,11 @@ export class ZipArchiveResponse implements ReadTreeResponse { // as a zip can have files with directories without directory entries if (entry.type === 'File' && this.shouldBeIncluded(entry)) { const entryPath = this.getPath(entry); - const dirname = path.dirname(entryPath); + const dirname = platformPath.dirname(entryPath); if (dirname) { - await fs.mkdirp(path.join(dir, dirname)); + await fs.mkdirp(platformPath.join(dir, dirname)); } - entry.pipe(fs.createWriteStream(path.join(dir, entryPath))); + entry.pipe(fs.createWriteStream(platformPath.join(dir, entryPath))); } else { entry.autodrain(); } diff --git a/packages/cli/config/eslint.backend.js b/packages/cli/config/eslint.backend.js index 6739d1c2ce..e7fd4cb688 100644 --- a/packages/cli/config/eslint.backend.js +++ b/packages/cli/config/eslint.backend.js @@ -38,6 +38,8 @@ module.exports = { }, ignorePatterns: ['.eslintrc.js', '**/dist/**', '**/dist-types/**'], rules: { + 'no-shadow': 'off', + 'no-redeclare': 'off', '@typescript-eslint/no-shadow': 'error', '@typescript-eslint/no-redeclare': 'error', diff --git a/packages/cli/config/eslint.js b/packages/cli/config/eslint.js index c440e53bc0..5c2206f8c1 100644 --- a/packages/cli/config/eslint.js +++ b/packages/cli/config/eslint.js @@ -45,6 +45,8 @@ module.exports = { }, ignorePatterns: ['.eslintrc.js', '**/dist/**', '**/dist-types/**'], rules: { + 'no-shadow': 'off', + 'no-redeclare': 'off', '@typescript-eslint/no-shadow': 'error', '@typescript-eslint/no-redeclare': 'error', diff --git a/packages/cli/src/commands/config/print.ts b/packages/cli/src/commands/config/print.ts index 56dcd3f753..930f2c98ca 100644 --- a/packages/cli/src/commands/config/print.ts +++ b/packages/cli/src/commands/config/print.ts @@ -63,8 +63,8 @@ function serializeConfigData( } const sanitizedConfigs = schema.process(appConfigs, { - valueTransform: (value, { visibility }) => - visibility === 'secret' ? '' : value, + valueTransform: (value, context) => + context.visibility === 'secret' ? '' : value, }); return ConfigReader.fromConfigs(sanitizedConfigs).get(); diff --git a/packages/cli/src/commands/versions/bump.test.ts b/packages/cli/src/commands/versions/bump.test.ts index 987d4f6f31..31803d4827 100644 --- a/packages/cli/src/commands/versions/bump.test.ts +++ b/packages/cli/src/commands/versions/bump.test.ts @@ -108,7 +108,7 @@ describe('bump', () => { paths.targetDir = '/'; jest .spyOn(paths, 'resolveTargetRoot') - .mockImplementation((...paths) => resolvePath('/', ...paths)); + .mockImplementation((...path) => resolvePath('/', ...path)); jest.spyOn(runObj, 'runPlain').mockImplementation(async (...[, , , name]) => JSON.stringify({ type: 'inspect', @@ -204,7 +204,7 @@ describe('bump', () => { paths.targetDir = '/'; jest .spyOn(paths, 'resolveTargetRoot') - .mockImplementation((...paths) => resolvePath('/', ...paths)); + .mockImplementation((...path) => resolvePath('/', ...path)); jest.spyOn(runObj, 'runPlain').mockImplementation(async () => ''); jest.spyOn(runObj, 'run').mockResolvedValue(undefined); diff --git a/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.ts b/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.ts index 0478337c61..fed8b39a19 100644 --- a/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.ts +++ b/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.ts @@ -41,7 +41,7 @@ export class LinkedPackageResolvePlugin implements ResolvePlugin { callback: () => void, ) => { const pkg = this.packages.find( - pkg => data.path && isChildPath(pkg.location, data.path), + pkge => data.path && isChildPath(pkge.location, data.path), ); if (!pkg) { callback(); diff --git a/packages/cli/src/lib/bundler/paths.ts b/packages/cli/src/lib/bundler/paths.ts index f38416ca4e..c8d48a7199 100644 --- a/packages/cli/src/lib/bundler/paths.ts +++ b/packages/cli/src/lib/bundler/paths.ts @@ -42,14 +42,14 @@ export type BundlingPathsOptions = { export function resolveBundlingPaths(options: BundlingPathsOptions) { const { entry } = options; - const resolveTargetModule = (path: string) => { + const resolveTargetModule = (pathString: string) => { for (const ext of ['mjs', 'js', 'ts', 'tsx', 'jsx']) { - const filePath = paths.resolveTarget(`${path}.${ext}`); + const filePath = paths.resolveTarget(`${pathString}.${ext}`); if (fs.pathExistsSync(filePath)) { return filePath; } } - return paths.resolveTarget(`${path}.js`); + return paths.resolveTarget(`${pathString}.js`); }; let targetPublic = undefined; diff --git a/packages/config-loader/src/lib/schema/collect.ts b/packages/config-loader/src/lib/schema/collect.ts index 0e53562875..611413931e 100644 --- a/packages/config-loader/src/lib/schema/collect.ts +++ b/packages/config-loader/src/lib/schema/collect.ts @@ -118,7 +118,9 @@ export async function collectConfigSchemas( } await Promise.all( - depNames.map(name => processItem({ name, parentPath: pkgPath })), + depNames.map(depName => + processItem({ name: depName, parentPath: pkgPath }), + ), ); } diff --git a/packages/core-api/src/lib/AuthConnector/DirectAuthConnector.ts b/packages/core-api/src/lib/AuthConnector/DirectAuthConnector.ts index 517bf82ae7..71671edba6 100644 --- a/packages/core-api/src/lib/AuthConnector/DirectAuthConnector.ts +++ b/packages/core-api/src/lib/AuthConnector/DirectAuthConnector.ts @@ -27,11 +27,11 @@ type Options = { provider: AuthProvider & { id: string }; }; -export type DirectAuthResponse = { - userId: string; - profile: ProfileInfo; - backstageIdentity: BackstageIdentity; -}; +// export type DirectAuthResponse = { +// userId: string; +// profile: ProfileInfo; +// backstageIdentity: BackstageIdentity; +// }; export class DirectAuthConnector { private readonly discoveryApi: DiscoveryApi; diff --git a/packages/core-api/src/routing/FlatRoutes.tsx b/packages/core-api/src/routing/FlatRoutes.tsx index a6783964a7..cefb347290 100644 --- a/packages/core-api/src/routing/FlatRoutes.tsx +++ b/packages/core-api/src/routing/FlatRoutes.tsx @@ -25,8 +25,8 @@ type RouteObject = { // Similar to the same function from react-router, this collects routes from the // children, but only the first level of routes -function createRoutesFromChildren(children: ReactNode): RouteObject[] { - return Children.toArray(children) +function createRoutesFromChildren(childrenNode: ReactNode): RouteObject[] { + return Children.toArray(childrenNode) .flatMap(child => { if (!isValidElement(child)) { return []; diff --git a/packages/core/src/layout/HeaderTabs/HeaderTabs.tsx b/packages/core/src/layout/HeaderTabs/HeaderTabs.tsx index a1b4e1a97f..ef1f596f83 100644 --- a/packages/core/src/layout/HeaderTabs/HeaderTabs.tsx +++ b/packages/core/src/layout/HeaderTabs/HeaderTabs.tsx @@ -38,13 +38,13 @@ const useStyles = makeStyles(theme => ({ }, })); -export type Tab = { +export type objectTab = { id: string; label: string; }; type HeaderTabsProps = { - tabs: Tab[]; + tabs: objectTab[]; onChange?: (index: number) => void; selectedIndex?: number; }; diff --git a/packages/e2e-test/src/commands/run.ts b/packages/e2e-test/src/commands/run.ts index 2ddcc333d2..5f4894f9e9 100644 --- a/packages/e2e-test/src/commands/run.ts +++ b/packages/e2e-test/src/commands/run.ts @@ -104,11 +104,11 @@ async function buildDistWorkspace(workspaceName: string, rootDir: string) { { helpers: { version(name: string) { - const pkg = require(`${name}/package.json`); - if (!pkg) { + const pkge = require(`${name}/package.json`); + if (!pkge) { throw new Error(`No version available for package ${name}`); } - return pkg.version; + return pkge.version; }, }, }, diff --git a/packages/integration/src/ScmIntegrations.test.ts b/packages/integration/src/ScmIntegrations.test.ts index b43e69eba4..69eb6cc367 100644 --- a/packages/integration/src/ScmIntegrations.test.ts +++ b/packages/integration/src/ScmIntegrations.test.ts @@ -43,10 +43,10 @@ describe('ScmIntegrations', () => { } as GitLabIntegrationConfig); const i = new ScmIntegrations({ - azure: basicIntegrations([azure], i => i.config.host), - bitbucket: basicIntegrations([bitbucket], i => i.config.host), - github: basicIntegrations([github], i => i.config.host), - gitlab: basicIntegrations([gitlab], i => i.config.host), + azure: basicIntegrations([azure], item => item.config.host), + bitbucket: basicIntegrations([bitbucket], item => item.config.host), + github: basicIntegrations([github], item => item.config.host), + gitlab: basicIntegrations([gitlab], item => item.config.host), }); it('can get the specifics', () => { diff --git a/packages/integration/src/github/GithubCredentialsProvider.test.ts b/packages/integration/src/github/GithubCredentialsProvider.test.ts index f708f75184..3eb1d33306 100644 --- a/packages/integration/src/github/GithubCredentialsProvider.test.ts +++ b/packages/integration/src/github/GithubCredentialsProvider.test.ts @@ -215,21 +215,21 @@ describe('GithubCredentialsProvider tests', () => { }); it('should return the default token if no app is configured', async () => { - const github = GithubCredentialsProvider.create({ + const githubID = GithubCredentialsProvider.create({ host: 'github.com', apps: [], token: 'fallback_token', }); await expect( - github.getCredentials({ + githubID.getCredentials({ url: 'https://github.com/404/foobar', }), ).resolves.toEqual(expect.objectContaining({ token: 'fallback_token' })); }); it('should return the configured token if listing installations throws', async () => { - const github = GithubCredentialsProvider.create({ + const githubID = GithubCredentialsProvider.create({ host: 'github.com', apps: [ { @@ -245,19 +245,19 @@ describe('GithubCredentialsProvider tests', () => { octokit.apps.listInstallations.mockRejectedValue({ status: 304 }); await expect( - github.getCredentials({ + githubID.getCredentials({ url: 'https://github.com/backstage', }), ).resolves.toEqual(expect.objectContaining({ token: 'hardcoded_token' })); }); it('should return undefined if no token or apps are configured', async () => { - const github = GithubCredentialsProvider.create({ + const githubID = GithubCredentialsProvider.create({ host: 'github.com', }); await expect( - github.getCredentials({ + githubID.getCredentials({ url: 'https://github.com/backstage', }), ).resolves.toEqual({ headers: undefined, token: undefined }); diff --git a/packages/integration/src/github/GithubCredentialsProvider.ts b/packages/integration/src/github/GithubCredentialsProvider.ts index dfb809ee5f..a22c69909b 100644 --- a/packages/integration/src/github/GithubCredentialsProvider.ts +++ b/packages/integration/src/github/GithubCredentialsProvider.ts @@ -177,7 +177,7 @@ export class GithubAppCredentialsMux { ), ); - const result = results.find(result => result.credentials); + const result = results.find(resultItem => resultItem.credentials); if (result) { return result.credentials!.accessToken; } diff --git a/packages/techdocs-common/src/stages/publish/local.test.ts b/packages/techdocs-common/src/stages/publish/local.test.ts index efd56285ef..d444d25540 100644 --- a/packages/techdocs-common/src/stages/publish/local.test.ts +++ b/packages/techdocs-common/src/stages/publish/local.test.ts @@ -28,9 +28,9 @@ jest.mock('fs-extra', () => { const fsOriginal = jest.requireActual('fs-extra'); return { ...fsOriginal, - access: jest.fn().mockImplementation((path, checkType, callback) => { + access: jest.fn().mockImplementation((paths, checkType, callback) => { if ( - path.includes('http://localhost:7000/static') && + paths.includes('http://localhost:7000/static') && checkType === fs.constants.F_OK ) { callback(); diff --git a/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.test.tsx b/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.test.tsx index d59699ab9a..19fb886d74 100644 --- a/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.test.tsx +++ b/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.test.tsx @@ -74,8 +74,8 @@ paths: type: 'openapi', title: 'OpenAPI', rawLanguage: 'yaml', - component: definition => ( - + component: definitionString => ( + ), }); diff --git a/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.ts b/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.ts index 76fc63c9b3..b03183cf34 100644 --- a/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.ts @@ -124,7 +124,7 @@ export class DatabaseEntitiesCatalog implements EntitiesCatalog { // produce tens of thousands of entities, and those are too large batch // sizes to reasonably send to the database. const batches = Object.values(requestsByKindAndNamespace) - .map(requests => chunk(requests, BATCH_SIZE)) + .map(request => chunk(request, BATCH_SIZE)) .flat(); // Bound the number of concurrent batches. We want a bit of concurrency for diff --git a/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.test.ts index 6eb7a45c63..28200021cc 100644 --- a/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.test.ts @@ -65,7 +65,10 @@ describe('AwsOrganizationCloudAccountProcessor', () => { }); it('filters out accounts not in specified location target', async () => { - const location = { type: 'aws-cloud-accounts', target: 'o-1vl18kc5a3' }; + const locationTest = { + type: 'aws-cloud-accounts', + target: 'o-1vl18kc5a3', + }; listAccounts.mockImplementation(() => Promise.resolve({ Accounts: [ @@ -83,11 +86,11 @@ describe('AwsOrganizationCloudAccountProcessor', () => { NextToken: undefined, }), ); - await processor.readLocation(location, false, emit); + await processor.readLocation(locationTest, false, emit); expect(emit).toBeCalledTimes(1); expect(emit).toBeCalledWith({ type: 'entity', - location, + location: locationTest, entity: { apiVersion: 'backstage.io/v1alpha1', kind: 'Resource', diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts index c75a46874d..ef94c54010 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts @@ -66,8 +66,8 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor { async validateEntityKind(entity: Entity): Promise { for (const validator of this.validators) { - const result = await validator.check(entity); - if (result) { + const results = await validator.check(entity); + if (results) { return true; } } diff --git a/plugins/catalog-import/src/components/ImportComponentForm.tsx b/plugins/catalog-import/src/components/ImportComponentForm.tsx index c233be5a8c..0e59fed3c9 100644 --- a/plugins/catalog-import/src/components/ImportComponentForm.tsx +++ b/plugins/catalog-import/src/components/ImportComponentForm.tsx @@ -71,8 +71,8 @@ export const RegisterComponentForm = ({ const onSubmit = async (formData: Record) => { const { componentLocation: target } = formData; - async function saveCatalogFileConfig(target: string) { - const data = await catalogApi.addLocation({ target }); + async function saveCatalogFileConfig(targetString: string) { + const data = await catalogApi.addLocation({ target: targetString }); saveConfig({ type: 'file', location: data.location.target, @@ -80,12 +80,12 @@ export const RegisterComponentForm = ({ }); } - async function trySaveRepositoryConfig(target: string) { - const existingCatalog = await checkForExistingCatalogInfo(target); + async function trySaveRepositoryConfig(targetString: string) { + const existingCatalog = await checkForExistingCatalogInfo(targetString); if (existingCatalog.exists) { - const targetUrl = target.endsWith('/') - ? `${target}${existingCatalog.url}` - : `${target}/${existingCatalog.url}`; + const targetUrl = targetString.endsWith('/') + ? `${targetString}${existingCatalog.url}` + : `${targetString}/${existingCatalog.url}`; await saveCatalogFileConfig(targetUrl); } else { saveConfig({ diff --git a/plugins/catalog-import/src/components/ImportComponentPage.tsx b/plugins/catalog-import/src/components/ImportComponentPage.tsx index e284e95456..adae137a50 100644 --- a/plugins/catalog-import/src/components/ImportComponentPage.tsx +++ b/plugins/catalog-import/src/components/ImportComponentPage.tsx @@ -46,20 +46,20 @@ function manifestGenerationAvailable(configApi: ConfigApi): boolean { function repositories(configApi: ConfigApi): string[] { const integrations = configApi.getConfig('integrations'); - const repositories = []; + const repos = []; if (integrations.has('github')) { - repositories.push('GitHub'); + repos.push('GitHub'); } if (integrations.has('bitbucket')) { - repositories.push('Bitbucket'); + repos.push('Bitbucket'); } if (integrations.has('gitlab')) { - repositories.push('GitLab'); + repos.push('GitLab'); } if (integrations.has('azure')) { - repositories.push('Azure'); + repos.push('Azure'); } - return repositories; + return repos; } export const ImportComponentPage = ({ diff --git a/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx b/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx index 09b8d02c60..7f09cf557b 100644 --- a/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx +++ b/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx @@ -53,8 +53,10 @@ const Route: (props: SubRoute) => null = () => null; // This causes all mount points that are discovered within this route to use the path of the route itself attachComponentData(Route, 'core.gatherMountPoints', true); -export function createSubRoutesFromChildren(children: ReactNode): SubRoute[] { - return Children.toArray(children).flatMap(child => { +export function createSubRoutesFromChildren( + childrenNode: ReactNode, +): SubRoute[] { + return Children.toArray(childrenNode).flatMap(child => { if (!isValidElement(child)) { return []; } diff --git a/plugins/catalog/src/components/EntityLayout/TabbedLayout.tsx b/plugins/catalog/src/components/EntityLayout/TabbedLayout.tsx index a2209ba492..47f7b12eb3 100644 --- a/plugins/catalog/src/components/EntityLayout/TabbedLayout.tsx +++ b/plugins/catalog/src/components/EntityLayout/TabbedLayout.tsx @@ -52,12 +52,12 @@ export const TabbedLayout = ({ routes }: { routes: SubRoute[] }) => { [routes], ); - const onTabChange = (index: number) => + const onTabChange = (tabIndex: number) => // Remove trailing /* // And remove leading / for relative navigation // Note! route resolves relative to the position in the React tree, // not relative to current location - navigate(routes[index].path.replace(/\/\*$/, '').replace(/^\//, '')); + navigate(routes[tabIndex].path.replace(/\/\*$/, '').replace(/^\//, '')); return ( <> diff --git a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx index f36bc29cee..3333a85d29 100644 --- a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx +++ b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx @@ -35,8 +35,8 @@ type SwitchCase = { children: JSX.Element; }; -function createSwitchCasesFromChildren(children: ReactNode): SwitchCase[] { - return Children.toArray(children).flatMap(child => { +function createSwitchCasesFromChildren(childrenNode: ReactNode): SwitchCase[] { + return Children.toArray(childrenNode).flatMap(child => { if (!isValidElement(child)) { return []; } diff --git a/plugins/catalog/src/components/isOwnerOf.ts b/plugins/catalog/src/components/isOwnerOf.ts index 455e06dc13..d135e4030a 100644 --- a/plugins/catalog/src/components/isOwnerOf.ts +++ b/plugins/catalog/src/components/isOwnerOf.ts @@ -34,13 +34,13 @@ export function isOwnerOf(owner: Entity, owned: Entity) { const owners = getEntityRelations(owned, RELATION_OWNED_BY); - for (const owner of owners) { + for (const ownerItem of owners) { if ( possibleOwners.find( o => - owner.kind.toLowerCase() === o.kind.toLowerCase() && - owner.namespace.toLowerCase() === o.namespace.toLowerCase() && - owner.name.toLowerCase() === o.name.toLowerCase(), + ownerItem.kind.toLowerCase() === o.kind.toLowerCase() && + ownerItem.namespace.toLowerCase() === o.namespace.toLowerCase() && + ownerItem.name.toLowerCase() === o.name.toLowerCase(), ) !== undefined ) { return true; diff --git a/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCardList.tsx b/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCardList.tsx index aaadbed09d..e836c3ef01 100644 --- a/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCardList.tsx +++ b/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCardList.tsx @@ -18,11 +18,11 @@ import { Paper, Divider } from '@material-ui/core'; import { AlertActionCard } from './AlertActionCard'; import { Alert } from '../../types'; -type AlertActionCardList = { +type AlertActionCardListType = { alerts: Array; }; -export const AlertActionCardList = ({ alerts }: AlertActionCardList) => ( +export const AlertActionCardList = ({ alerts }: AlertActionCardListType) => ( {alerts.map((alert, index) => ( diff --git a/plugins/cost-insights/src/components/BarChart/BarChartLabel.tsx b/plugins/cost-insights/src/components/BarChart/BarChartLabel.tsx index c4402893f3..71bb08d563 100644 --- a/plugins/cost-insights/src/components/BarChart/BarChartLabel.tsx +++ b/plugins/cost-insights/src/components/BarChart/BarChartLabel.tsx @@ -18,7 +18,7 @@ import React, { PropsWithChildren } from 'react'; import { Box, Typography } from '@material-ui/core'; import { useBarChartLabelStyles } from '../../utils/styles'; -type BarChartLabel = { +type BarChartLabelType = { x: number; y: number; height: number; @@ -33,7 +33,7 @@ export const BarChartLabel = ({ width, details, children, -}: PropsWithChildren) => { +}: PropsWithChildren) => { const classes = useBarChartLabelStyles(); const translateX = width * -0.5; diff --git a/plugins/cost-insights/src/components/BarChart/BarChartSteps.tsx b/plugins/cost-insights/src/components/BarChart/BarChartSteps.tsx index 11a295965e..9576e74b42 100644 --- a/plugins/cost-insights/src/components/BarChart/BarChartSteps.tsx +++ b/plugins/cost-insights/src/components/BarChart/BarChartSteps.tsx @@ -18,7 +18,7 @@ import React from 'react'; import { ButtonBase } from '@material-ui/core'; import { useBarChartStepperStyles as useStyles } from '../../utils/styles'; -export type BarChartSteps = { +export type BarChartStepsType = { steps: number; activeStep: number; onClick: (index: number) => void; @@ -28,7 +28,7 @@ export const BarChartSteps = ({ steps, activeStep, onClick, -}: BarChartSteps) => { +}: BarChartStepsType) => { const classes = useStyles(); const handleOnClick = (index: number) => ( event: React.MouseEvent, diff --git a/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx b/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx index 0ef35bf04c..381827e4e2 100644 --- a/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx +++ b/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx @@ -83,28 +83,31 @@ export const ProductInsights = ({ useEffect(() => { async function getAllProductInsights( - group: string, - project: Maybe, - products: Product[], - lastCompleteBillingDate: string, + groupId: string, + projectId: Maybe, + productsId: Product[], + lastCompleteBillingDateId: string, ) { try { dispatchLoadingProducts(true); const responses = await Promise.allSettled( - products.map(product => + productsId.map(product => client.getProductInsights({ - group: group, - project: project, + group: groupId, + project: projectId, product: product.kind, - intervals: intervalsOf(DEFAULT_DURATION, lastCompleteBillingDate), + intervals: intervalsOf( + DEFAULT_DURATION, + lastCompleteBillingDateId, + ), }), ), ).then(settledResponseOf); - const initialStates = initialStatesOf(products, responses).sort( + const initialStatesNow = initialStatesOf(productsId, responses).sort( totalAggregationSort, ); - setStates(initialStates); + setStates(initialStatesNow); } catch (e) { setError(e); } finally { @@ -125,8 +128,8 @@ export const ProductInsights = ({ useEffect( function handleOnLoaded() { if (onceRef.current) { - const products = initialStates.map(state => state.product); - onLoaded(products); + const currentProducts = initialStates.map(state => state.product); + onLoaded(currentProducts); } else { onceRef.current = true; } diff --git a/plugins/cost-insights/src/utils/change.ts b/plugins/cost-insights/src/utils/change.ts index fc50439aad..143751fa89 100644 --- a/plugins/cost-insights/src/utils/change.ts +++ b/plugins/cost-insights/src/utils/change.ts @@ -26,10 +26,10 @@ import { DateAggregation, } from '../types'; import dayjs, { OpUnitType } from 'dayjs'; -import duration from 'dayjs/plugin/duration'; +import durationPlugin from 'dayjs/plugin/duration'; import { inclusiveStartDateOf } from './duration'; -dayjs.extend(duration); +dayjs.extend(durationPlugin); // Used for displaying status colors export function growthOf(ratio: number, amount?: number) { diff --git a/plugins/jenkins/src/components/useBuilds.ts b/plugins/jenkins/src/components/useBuilds.ts index db7c7ddd55..a1ff3a1eef 100644 --- a/plugins/jenkins/src/components/useBuilds.ts +++ b/plugins/jenkins/src/components/useBuilds.ts @@ -36,17 +36,17 @@ export function useBuilds(owner: string, repo: string, branch?: string) { const { loading, value: builds, retry } = useAsyncRetry(async () => { try { - let builds; + let build; if (branch) { - builds = await api.getLastBuild(`${owner}/${repo}/${branch}`); + build = await api.getLastBuild(`${owner}/${repo}/${branch}`); } else { - builds = await api.getFolder(`${owner}/${repo}`); + build = await api.getFolder(`${owner}/${repo}`); } - const size = Array.isArray(builds) ? builds?.[0].build_num! : 1; + const size = Array.isArray(build) ? build?.[0].build_num! : 1; setTotal(size); - return builds || []; + return build || []; } catch (e) { errorApi.post(e); throw e; diff --git a/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts b/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts index 2dd1e32b96..811a3c8acb 100644 --- a/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts +++ b/plugins/kubernetes-backend/src/service/getKubernetesObjectsForServiceHandler.ts @@ -85,18 +85,18 @@ export const handleGetKubernetesObjectsForService: GetKubernetesObjectsForServic ] || `backstage.io/kubernetes-id=${requestBody.entity.metadata.name}`; return Promise.all( - clusterDetailsDecoratedForAuth.map(clusterDetails => { + clusterDetailsDecoratedForAuth.map(clusterDetailsItem => { return fetcher .fetchObjectsForService({ serviceId, - clusterDetails, + clusterDetails: clusterDetailsItem, objectTypesToFetch, labelSelector, } as ObjectFetchParams) .then(result => { return { cluster: { - name: clusterDetails.name, + name: clusterDetailsItem.name, }, resources: result.responses, errors: result.errors, diff --git a/plugins/kubernetes/src/components/DeploymentsAccordions/DeploymentDrawer.tsx b/plugins/kubernetes/src/components/DeploymentsAccordions/DeploymentDrawer.tsx index 6bacad9b16..8c1365769f 100644 --- a/plugins/kubernetes/src/components/DeploymentsAccordions/DeploymentDrawer.tsx +++ b/plugins/kubernetes/src/components/DeploymentsAccordions/DeploymentDrawer.tsx @@ -32,8 +32,8 @@ export const DeploymentDrawer = ({ object={deployment} expanded={expanded} kind="Deployment" - renderObject={(deployment: V1Deployment) => { - const conditions = (deployment.status?.conditions ?? []) + renderObject={(deploymentObj: V1Deployment) => { + const conditions = (deploymentObj.status?.conditions ?? []) .map(renderCondition) .reduce((accum, next) => { accum[next[0]] = next[1]; @@ -41,10 +41,10 @@ export const DeploymentDrawer = ({ }, {} as { [key: string]: React.ReactNode }); return { - strategy: deployment.spec?.strategy ?? '???', - minReadySeconds: deployment.spec?.minReadySeconds ?? '???', + strategy: deploymentObj.spec?.strategy ?? '???', + minReadySeconds: deploymentObj.spec?.minReadySeconds ?? '???', progressDeadlineSeconds: - deployment.spec?.progressDeadlineSeconds ?? '???', + deploymentObj.spec?.progressDeadlineSeconds ?? '???', ...conditions, }; }} diff --git a/plugins/kubernetes/src/components/HorizontalPodAutoscalers/HorizontalPodAutoscalerDrawer.tsx b/plugins/kubernetes/src/components/HorizontalPodAutoscalers/HorizontalPodAutoscalerDrawer.tsx index 00ab2cad84..26dcdfd249 100644 --- a/plugins/kubernetes/src/components/HorizontalPodAutoscalers/HorizontalPodAutoscalerDrawer.tsx +++ b/plugins/kubernetes/src/components/HorizontalPodAutoscalers/HorizontalPodAutoscalerDrawer.tsx @@ -32,16 +32,16 @@ export const HorizontalPodAutoscalerDrawer = ({ kind="HorizontalPodAutoscaler" object={hpa} expanded={expanded} - renderObject={(hpa: V1HorizontalPodAutoscaler) => { + renderObject={(hpaObject: V1HorizontalPodAutoscaler) => { return { targetCPUUtilizationPercentage: - hpa.spec?.targetCPUUtilizationPercentage, + hpaObject.spec?.targetCPUUtilizationPercentage, currentCPUUtilizationPercentage: - hpa.status?.currentCPUUtilizationPercentage, - minReplicas: hpa.spec?.minReplicas, - maxReplicas: hpa.spec?.maxReplicas, - currentReplicas: hpa.status?.currentReplicas, - desiredReplicas: hpa.status?.desiredReplicas, + hpaObject.status?.currentCPUUtilizationPercentage, + minReplicas: hpaObject.spec?.minReplicas, + maxReplicas: hpaObject.spec?.maxReplicas, + currentReplicas: hpaObject.status?.currentReplicas, + desiredReplicas: hpaObject.status?.desiredReplicas, }; }} > diff --git a/plugins/kubernetes/src/components/IngressesAccordions/IngressDrawer.tsx b/plugins/kubernetes/src/components/IngressesAccordions/IngressDrawer.tsx index 9e63f22fda..d619700ac2 100644 --- a/plugins/kubernetes/src/components/IngressesAccordions/IngressDrawer.tsx +++ b/plugins/kubernetes/src/components/IngressesAccordions/IngressDrawer.tsx @@ -31,8 +31,8 @@ export const IngressDrawer = ({ object={ingress} expanded={expanded} kind="Ingress" - renderObject={(ingress: ExtensionsV1beta1Ingress) => { - return ingress.spec || {}; + renderObject={(ingressObject: ExtensionsV1beta1Ingress) => { + return ingressObject.spec || {}; }} > { - const phase = pod.status?.phase ?? 'unknown'; + renderObject={(podObject: V1Pod) => { + const phase = podObject.status?.phase ?? 'unknown'; const ports = - pod.spec?.containers?.map(c => { + podObject.spec?.containers?.map(c => { return { [c.name]: c.ports, }; }) ?? 'N/A'; - const conditions = (pod.status?.conditions ?? []) + const conditions = (podObject.status?.conditions ?? []) .map(renderCondition) .reduce((accum, next) => { accum[next[0]] = next[1]; @@ -55,11 +55,11 @@ export const PodDrawer = ({ }, {} as { [key: string]: React.ReactNode }); return { - images: imageChips(pod), + images: imageChips(podObject), phase: phase, - 'Containers Ready': containersReady(pod), - 'Total Restarts': totalRestarts(pod), - 'Container Statuses': containerStatuses(pod), + 'Containers Ready': containersReady(podObject), + 'Total Restarts': totalRestarts(podObject), + 'Container Statuses': containerStatuses(podObject), ...conditions, 'Exposed ports': ports, }; diff --git a/plugins/kubernetes/src/components/ServicesAccordions/ServiceDrawer.tsx b/plugins/kubernetes/src/components/ServicesAccordions/ServiceDrawer.tsx index d48f17813a..d0962c6314 100644 --- a/plugins/kubernetes/src/components/ServicesAccordions/ServiceDrawer.tsx +++ b/plugins/kubernetes/src/components/ServicesAccordions/ServiceDrawer.tsx @@ -31,8 +31,8 @@ export const ServiceDrawer = ({ object={service} expanded={expanded} kind="Service" - renderObject={(service: V1Service) => { - return service.spec || {}; + renderObject={(serviceObject: V1Service) => { + return serviceObject.spec || {}; }} > { export const containersReady = (pod: V1Pod): string => { const containerStatuses = pod.status?.containerStatuses ?? []; - const containersReady = containerStatuses.filter(cs => cs.ready).length; + const containersReadyItem = containerStatuses.filter(cs => cs.ready).length; - return `${containersReady}/${containerStatuses.length}`; + return `${containersReadyItem}/${containerStatuses.length}`; }; export const totalRestarts = (pod: V1Pod): number => { @@ -47,8 +47,8 @@ export const totalRestarts = (pod: V1Pod): number => { }; export const containerStatuses = (pod: V1Pod): ReactNode => { - const containerStatuses = pod.status?.containerStatuses ?? []; - const errors = containerStatuses.reduce((accum, next) => { + const containerStatusesItem = pod.status?.containerStatuses ?? []; + const errors = containerStatusesItem.reduce((accum, next) => { if (next.state === undefined) { return accum; } diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx index 779cb1d92d..0bf104bf61 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx @@ -73,12 +73,12 @@ export const GroupProfileCard = ({ } = group; const parent = group?.relations ?.filter(r => r.type === RELATION_CHILD_OF) - ?.map(group => group.target.name) + ?.map(groupItem => groupItem.target.name) .toString(); const childrens = group?.relations ?.filter(r => r.type === RELATION_PARENT_OF) - ?.map(group => group.target.name); + ?.map(groupItem => groupItem.target.name); const displayName = profile?.displayName ?? name; diff --git a/plugins/org/src/components/isOwnerOf.ts b/plugins/org/src/components/isOwnerOf.ts index dd7c7d6805..51b3f41ae8 100644 --- a/plugins/org/src/components/isOwnerOf.ts +++ b/plugins/org/src/components/isOwnerOf.ts @@ -38,13 +38,13 @@ export function isOwnerOf(owner: Entity, owned: Entity) { const owners = getEntityRelations(owned, RELATION_OWNED_BY); - for (const owner of owners) { + for (const ownerItem of owners) { if ( possibleOwners.find( o => - owner.kind.toLowerCase() === o.kind.toLowerCase() && - owner.namespace.toLowerCase() === o.namespace.toLowerCase() && - owner.name.toLowerCase() === o.name.toLowerCase(), + ownerItem.kind.toLowerCase() === o.kind.toLowerCase() && + ownerItem.namespace.toLowerCase() === o.namespace.toLowerCase() && + ownerItem.name.toLowerCase() === o.name.toLowerCase(), ) !== undefined ) { return true; diff --git a/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx b/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx index faa480f6e2..6fd036330a 100644 --- a/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx +++ b/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx @@ -32,11 +32,11 @@ export const EscalationPolicy = ({ policyId }: Props) => { const { value: users, loading, error } = useAsync(async () => { const oncalls = await api.getOnCallByPolicyId(policyId); - const users = oncalls + const usersItem = oncalls .sort((a, b) => a.escalation_level - b.escalation_level) .map(oncall => oncall.user); - return users; + return usersItem; }); if (error) { diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx index 4fa88a1b9a..c390ed1f82 100644 --- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx +++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx @@ -52,11 +52,11 @@ export const TriggerDialog = ({ const [description, setDescription] = useState(''); const [{ value, loading, error }, handleTriggerAlarm] = useAsyncFn( - async (description: string) => + async (descriptions: string) => await api.triggerAlarm({ integrationKey, source: window.location.toString(), - description, + description: descriptions, userName, }), ); diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/prepare/bitbucket.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/prepare/bitbucket.test.ts index ff74fffdd7..d2fdedccf2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/prepare/bitbucket.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/prepare/bitbucket.test.ts @@ -93,12 +93,12 @@ describe('BitbucketPreparer', () => { }); it('calls the clone command with the correct arguments if an app password is provided for a repository', async () => { - const preparer = BitbucketPreparer.fromConfig({ + const preparerCheck = BitbucketPreparer.fromConfig({ host: 'bitbucket.org', username: 'fake-user', appPassword: 'fake-password', }); - await preparer.prepare(mockEntity, { logger }); + await preparerCheck.prepare(mockEntity, { logger }); expect(Git.fromAuth).toHaveBeenCalledWith({ logger, @@ -128,12 +128,12 @@ describe('BitbucketPreparer', () => { }); it('calls the clone command with with token for auth method', async () => { - const preparer = BitbucketPreparer.fromConfig({ + const preparerCheck = BitbucketPreparer.fromConfig({ host: 'bitbucket.org', token: 'fake-token', }); - await preparer.prepare(mockEntity, { logger }); + await preparerCheck.prepare(mockEntity, { logger }); expect(Git.fromAuth).toHaveBeenCalledWith({ logger, diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.test.ts index b43545f5fc..3221c5d4ca 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.test.ts @@ -104,12 +104,12 @@ describe('GitHubPreparer', () => { }); it('return the temp directory with the path to the folder if it is specified', async () => { - const preparer = GithubPreparer.fromConfig({ + const preparerCheck = GithubPreparer.fromConfig({ host: 'github.com', token: 'fake-token', }); mockEntity.spec.path = './template/test/1/2/3'; - const response = await preparer.prepare(mockEntity, { + const response = await preparerCheck.prepare(mockEntity, { logger: getVoidLogger(), }); expect(response.split('\\').join('/')).toMatch( @@ -118,13 +118,13 @@ describe('GitHubPreparer', () => { }); it('return the working directory with the path to the folder if it is specified', async () => { - const preparer = GithubPreparer.fromConfig({ + const preparerCheck = GithubPreparer.fromConfig({ host: 'github.com', token: 'fake-token', }); mockEntity.spec.path = './template/test/1/2/3'; - const response = await preparer.prepare(mockEntity, { + const response = await preparerCheck.prepare(mockEntity, { workingDirectory: '/workDir', logger: getVoidLogger(), }); diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 528d42b94f..0d66fbf92b 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -30,7 +30,7 @@ import { Theme as MuiTheme } from '@rjsf/material-ui'; import React, { useState } from 'react'; const Form = withTheme(MuiTheme); -type Step = { +type StepType = { schema: JSONSchema; label: string; } & Partial, 'schema'>>; @@ -39,7 +39,7 @@ type Props = { /** * Steps for the form, each contains label and form schema */ - steps: Step[]; + steps: StepType[]; formData: Record; onChange: (e: IChangeEvent) => void; onReset: () => void; diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index 60f5ad8dec..9b089fae43 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -92,8 +92,8 @@ export const TemplatePage = () => { ); const [jobId, setJobId] = useState(null); - const job = useJobPolling(jobId, async job => { - if (!job.metadata.catalogInfoUrl) { + const job = useJobPolling(jobId, async jobItem => { + if (!jobItem.metadata.catalogInfoUrl) { errorApi.post( new Error(`No catalogInfoUrl returned from the scaffolder`), ); @@ -103,7 +103,9 @@ export const TemplatePage = () => { try { const { entities: [createdEntity], - } = await catalogApi.addLocation({ target: job.metadata.catalogInfoUrl }); + } = await catalogApi.addLocation({ + target: jobItem.metadata.catalogInfoUrl, + }); const resolvedPath = generatePath( `/catalog/${entityRoute.path}`, @@ -122,8 +124,8 @@ export const TemplatePage = () => { const handleCreate = async () => { try { - const jobId = await scaffolderApi.scaffold(templateName, formState); - setJobId(jobId); + const Id = await scaffolderApi.scaffold(templateName, formState); + setJobId(Id); setModalOpen(true); } catch (e) { errorApi.post(e); diff --git a/plugins/search/src/components/SearchResult/SearchResult.tsx b/plugins/search/src/components/SearchResult/SearchResult.tsx index f81cd4f66d..4b82679fcc 100644 --- a/plugins/search/src/components/SearchResult/SearchResult.tsx +++ b/plugins/search/src/components/SearchResult/SearchResult.tsx @@ -59,10 +59,10 @@ type TableHeaderProps = { handleToggleFilters: () => void; }; -type Filters = { - selected: string; - checked: Array; -}; +// type Filters = { +// selected: string; +// checked: Array; +// }; // TODO: move out column to make the search result component more generic const columns: TableColumn[] = [ From c9d6493bef0aa7de4ac36d33336ee24140372754 Mon Sep 17 00:00:00 2001 From: Debajyoti Halder Date: Fri, 29 Jan 2021 12:15:35 +0530 Subject: [PATCH 003/110] Add missing eslint rule --- packages/cli/config/eslint.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/config/eslint.js b/packages/cli/config/eslint.js index 5c2206f8c1..37bd7bed94 100644 --- a/packages/cli/config/eslint.js +++ b/packages/cli/config/eslint.js @@ -49,7 +49,7 @@ module.exports = { 'no-redeclare': 'off', '@typescript-eslint/no-shadow': 'error', '@typescript-eslint/no-redeclare': 'error', - + 'no-undef': 'off', 'import/newline-after-import': 'error', 'import/no-duplicates': 'warn', 'import/no-extraneous-dependencies': [ From 877f46c1777f16acf84b3884a2a5c5ab59bf90d5 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Fri, 29 Jan 2021 16:15:34 -0500 Subject: [PATCH 004/110] Support custom status codes --- .../reader/components/TechDocsNotFound.test.tsx | 17 +++++++++++++++++ .../src/reader/components/TechDocsNotFound.tsx | 5 +++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/plugins/techdocs/src/reader/components/TechDocsNotFound.test.tsx b/plugins/techdocs/src/reader/components/TechDocsNotFound.test.tsx index d3c98fec78..c6562ec8ad 100644 --- a/plugins/techdocs/src/reader/components/TechDocsNotFound.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsNotFound.test.tsx @@ -41,3 +41,20 @@ describe('', ( expect(rendered.getByTestId('go-back-link')).toBeDefined(); }); }); + +describe('', () => { + it('should render with a custom status code, custom error message and go back link', () => { + const rendered = render( + wrapInTestApp( + , + ), + ); + rendered.getByText(/This is a custom error message/i); + rendered.getByText(/500/i); + rendered.getByText(/Looks like someone dropped the mic!/i); + expect(rendered.getByTestId('go-back-link')).toBeDefined(); + }); +}); diff --git a/plugins/techdocs/src/reader/components/TechDocsNotFound.tsx b/plugins/techdocs/src/reader/components/TechDocsNotFound.tsx index cdacc1cb7e..04f2106161 100644 --- a/plugins/techdocs/src/reader/components/TechDocsNotFound.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsNotFound.tsx @@ -19,9 +19,10 @@ import { ErrorPage, useApi, configApiRef } from '@backstage/core'; type Props = { errorMessage?: string; + statusCode?: number; }; -export const TechDocsNotFound = ({ errorMessage }: Props) => { +export const TechDocsNotFound = ({ errorMessage, statusCode }: Props) => { const techdocsBuilder = useApi(configApiRef).getOptionalString( 'techdocs.builder', ); @@ -37,7 +38,7 @@ export const TechDocsNotFound = ({ errorMessage }: Props) => { return ( From cb61bc8cbb3c0d58dcfc4d74aaeeeb38bb6df263 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Fri, 29 Jan 2021 16:16:14 -0500 Subject: [PATCH 005/110] Hardcode file not found status --- plugins/techdocs/src/reader/components/Reader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index 1264e2b6eb..c9e76ddeeb 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -155,7 +155,7 @@ export const Reader = ({ entityId, onReady }: Props) => { ]); if (error) { - return ; + return ; } return ( From 45de779d5fa75721dd8bb1865c3e63a8ba8c4976 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Fri, 29 Jan 2021 16:20:07 -0500 Subject: [PATCH 006/110] Add changeset --- .changeset/green-rabbits-burn.md | 5 +++++ plugins/techdocs/src/api.ts | 24 ++++++++++++++++-------- 2 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 .changeset/green-rabbits-burn.md diff --git a/.changeset/green-rabbits-burn.md b/.changeset/green-rabbits-burn.md new file mode 100644 index 0000000000..5af68c2000 --- /dev/null +++ b/.changeset/green-rabbits-burn.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Enhance API calls to support trapping 500 errors from techdocs-backend diff --git a/plugins/techdocs/src/api.ts b/plugins/techdocs/src/api.ts index 3a950aeeb3..5f2cf924d7 100644 --- a/plugins/techdocs/src/api.ts +++ b/plugins/techdocs/src/api.ts @@ -119,14 +119,22 @@ export class TechDocsStorageApi implements TechDocsStorage { `${url.endsWith('/') ? url : `${url}/`}index.html`, ); - if (request.status === 404) { - let errorMessage = 'Page not found. '; - // path is empty for the home page of an entity's docs site - if (!path) { - errorMessage += - 'This could be because there is no index.md file in the root of the docs directory of this repository.'; - } - throw new Error(errorMessage); + let errorMessage = ''; + switch (request.status) { + case 404: + errorMessage = 'Page not found. '; + // path is empty for the home page of an entity's docs site + if (!path) { + errorMessage += + 'This could be because there is no index.md file in the root of the docs directory of this repository.'; + } + throw new Error(errorMessage); + case 500: + errorMessage = 'Could not generate documentation. '; + throw new Error(errorMessage); + default: + // Do nothing + break; } return request.text(); From 96dbdfbb41a65524411cce4544abd1f64fba4ded Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Sun, 31 Jan 2021 22:02:25 -0500 Subject: [PATCH 007/110] Update error message --- plugins/techdocs/src/api.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/techdocs/src/api.ts b/plugins/techdocs/src/api.ts index 5f2cf924d7..fc007503fa 100644 --- a/plugins/techdocs/src/api.ts +++ b/plugins/techdocs/src/api.ts @@ -130,7 +130,8 @@ export class TechDocsStorageApi implements TechDocsStorage { } throw new Error(errorMessage); case 500: - errorMessage = 'Could not generate documentation. '; + errorMessage = + 'Could not generate documentation or an error in the TechDocs backend. '; throw new Error(errorMessage); default: // Do nothing From 9cda5593afcfec038a637e5a7da5a1a14204c202 Mon Sep 17 00:00:00 2001 From: Kaparwan Manoj Date: Tue, 2 Feb 2021 10:38:47 +1100 Subject: [PATCH 008/110] bitbucket own hosted v5.11.1 branchUrl fix and enbaled error tracing for mkdocs process stack trace --- packages/integration/src/bitbucket/core.ts | 5 +++-- packages/techdocs-common/src/stages/generate/helpers.ts | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/integration/src/bitbucket/core.ts b/packages/integration/src/bitbucket/core.ts index f5235d8189..e9a76d1ee0 100644 --- a/packages/integration/src/bitbucket/core.ts +++ b/packages/integration/src/bitbucket/core.ts @@ -32,10 +32,11 @@ export async function getBitbucketDefaultBranch( const isHosted = resource === 'bitbucket.org'; // Bitbucket Server https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp184 + // Changed branchUrl to support Atlassian Bitbucket v5.11.1 , which has different branchUrl format const branchUrl = isHosted ? `${config.apiBaseUrl}/repositories/${project}/${repoName}` - : `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/default-branch`; - + : `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches/default`; + const response = await fetch(branchUrl, getBitbucketRequestOptions(config)); if (!response.ok) { const message = `Failed to retrieve default branch from ${branchUrl}, ${response.status} ${response.statusText}`; diff --git a/packages/techdocs-common/src/stages/generate/helpers.ts b/packages/techdocs-common/src/stages/generate/helpers.ts index e396157b92..73be0e8ff0 100644 --- a/packages/techdocs-common/src/stages/generate/helpers.ts +++ b/packages/techdocs-common/src/stages/generate/helpers.ts @@ -133,6 +133,10 @@ export const runCommand = async ({ process.stderr.on('data', stream => { logStream.write(stream); + // added below change to make OS ( child process) exception to the backend logs + const bufferbase64 = Buffer.from(stream, 'base64'); + const textAscii = bufferbase64.toString('ascii'); + console.log(' Process Stderror trace: ', textAscii); }); process.on('error', error => { From 7717c46517ef01bd7aa35c9269df7123d826b323 Mon Sep 17 00:00:00 2001 From: Debajyoti Halder Date: Tue, 2 Feb 2021 16:42:37 +0530 Subject: [PATCH 009/110] Fix variable names of conflicting types --- .../src/lib/AuthConnector/DirectAuthConnector.ts | 14 +------------- packages/core/src/layout/HeaderTabs/HeaderTabs.tsx | 8 ++++---- .../src/github/GithubCredentialsProvider.test.ts | 12 ++++++------ .../src/components/BarChart/BarChartLabel.tsx | 4 ++-- .../src/components/BarChart/BarChartSteps.tsx | 4 ++-- .../components/ProductInsights/ProductInsights.tsx | 10 +++++----- .../MultistepJsonForm/MultistepJsonForm.tsx | 10 +++++----- .../src/components/TemplatePage/TemplatePage.tsx | 4 ++-- .../src/components/SearchResult/SearchResult.tsx | 5 ----- 9 files changed, 27 insertions(+), 44 deletions(-) diff --git a/packages/core-api/src/lib/AuthConnector/DirectAuthConnector.ts b/packages/core-api/src/lib/AuthConnector/DirectAuthConnector.ts index 71671edba6..e7764fcbaa 100644 --- a/packages/core-api/src/lib/AuthConnector/DirectAuthConnector.ts +++ b/packages/core-api/src/lib/AuthConnector/DirectAuthConnector.ts @@ -13,12 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - AuthProvider, - ProfileInfo, - BackstageIdentity, - DiscoveryApi, -} from '../../apis/definitions'; +import { AuthProvider, DiscoveryApi } from '../../apis/definitions'; import { showLoginPopup } from '../loginPopup'; type Options = { @@ -26,13 +21,6 @@ type Options = { environment?: string; provider: AuthProvider & { id: string }; }; - -// export type DirectAuthResponse = { -// userId: string; -// profile: ProfileInfo; -// backstageIdentity: BackstageIdentity; -// }; - export class DirectAuthConnector { private readonly discoveryApi: DiscoveryApi; private readonly environment: string | undefined; diff --git a/packages/core/src/layout/HeaderTabs/HeaderTabs.tsx b/packages/core/src/layout/HeaderTabs/HeaderTabs.tsx index ef1f596f83..718a103f00 100644 --- a/packages/core/src/layout/HeaderTabs/HeaderTabs.tsx +++ b/packages/core/src/layout/HeaderTabs/HeaderTabs.tsx @@ -18,7 +18,7 @@ // This is just a temporary solution to implementing tabs for now import React, { useState, useEffect } from 'react'; -import { makeStyles, Tabs, Tab } from '@material-ui/core'; +import { makeStyles, Tabs, Tab as TabUI } from '@material-ui/core'; const useStyles = makeStyles(theme => ({ tabsWrapper: { @@ -38,13 +38,13 @@ const useStyles = makeStyles(theme => ({ }, })); -export type objectTab = { +export type Tab = { id: string; label: string; }; type HeaderTabsProps = { - tabs: objectTab[]; + tabs: Tab[]; onChange?: (index: number) => void; selectedIndex?: number; }; @@ -81,7 +81,7 @@ export const HeaderTabs = ({ value={selectedTab} > {tabs.map((tab, index) => ( - { }); it('should return the default token if no app is configured', async () => { - const githubID = GithubCredentialsProvider.create({ + const githubProvider = GithubCredentialsProvider.create({ host: 'github.com', apps: [], token: 'fallback_token', }); await expect( - githubID.getCredentials({ + githubProvider.getCredentials({ url: 'https://github.com/404/foobar', }), ).resolves.toEqual(expect.objectContaining({ token: 'fallback_token' })); }); it('should return the configured token if listing installations throws', async () => { - const githubID = GithubCredentialsProvider.create({ + const githubProvider = GithubCredentialsProvider.create({ host: 'github.com', apps: [ { @@ -245,19 +245,19 @@ describe('GithubCredentialsProvider tests', () => { octokit.apps.listInstallations.mockRejectedValue({ status: 304 }); await expect( - githubID.getCredentials({ + githubProvider.getCredentials({ url: 'https://github.com/backstage', }), ).resolves.toEqual(expect.objectContaining({ token: 'hardcoded_token' })); }); it('should return undefined if no token or apps are configured', async () => { - const githubID = GithubCredentialsProvider.create({ + const githubProvider = GithubCredentialsProvider.create({ host: 'github.com', }); await expect( - githubID.getCredentials({ + githubProvider.getCredentials({ url: 'https://github.com/backstage', }), ).resolves.toEqual({ headers: undefined, token: undefined }); diff --git a/plugins/cost-insights/src/components/BarChart/BarChartLabel.tsx b/plugins/cost-insights/src/components/BarChart/BarChartLabel.tsx index 71bb08d563..cd882c6afd 100644 --- a/plugins/cost-insights/src/components/BarChart/BarChartLabel.tsx +++ b/plugins/cost-insights/src/components/BarChart/BarChartLabel.tsx @@ -18,7 +18,7 @@ import React, { PropsWithChildren } from 'react'; import { Box, Typography } from '@material-ui/core'; import { useBarChartLabelStyles } from '../../utils/styles'; -type BarChartLabelType = { +type BarChartLabelObject = { x: number; y: number; height: number; @@ -33,7 +33,7 @@ export const BarChartLabel = ({ width, details, children, -}: PropsWithChildren) => { +}: PropsWithChildren) => { const classes = useBarChartLabelStyles(); const translateX = width * -0.5; diff --git a/plugins/cost-insights/src/components/BarChart/BarChartSteps.tsx b/plugins/cost-insights/src/components/BarChart/BarChartSteps.tsx index 9576e74b42..ec573da68f 100644 --- a/plugins/cost-insights/src/components/BarChart/BarChartSteps.tsx +++ b/plugins/cost-insights/src/components/BarChart/BarChartSteps.tsx @@ -18,7 +18,7 @@ import React from 'react'; import { ButtonBase } from '@material-ui/core'; import { useBarChartStepperStyles as useStyles } from '../../utils/styles'; -export type BarChartStepsType = { +export type BarChartStepsObject = { steps: number; activeStep: number; onClick: (index: number) => void; @@ -28,7 +28,7 @@ export const BarChartSteps = ({ steps, activeStep, onClick, -}: BarChartStepsType) => { +}: BarChartStepsObject) => { const classes = useStyles(); const handleOnClick = (index: number) => ( event: React.MouseEvent, diff --git a/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx b/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx index 381827e4e2..b13376d24a 100644 --- a/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx +++ b/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx @@ -85,26 +85,26 @@ export const ProductInsights = ({ async function getAllProductInsights( groupId: string, projectId: Maybe, - productsId: Product[], - lastCompleteBillingDateId: string, + productIds: Product[], + lastCompleteBillingDateString: string, ) { try { dispatchLoadingProducts(true); const responses = await Promise.allSettled( - productsId.map(product => + productIds.map(product => client.getProductInsights({ group: groupId, project: projectId, product: product.kind, intervals: intervalsOf( DEFAULT_DURATION, - lastCompleteBillingDateId, + lastCompleteBillingDateString, ), }), ), ).then(settledResponseOf); - const initialStatesNow = initialStatesOf(productsId, responses).sort( + const initialStatesNow = initialStatesOf(productIds, responses).sort( totalAggregationSort, ); setStates(initialStatesNow); diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 0d66fbf92b..27c5b4f18a 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -19,7 +19,7 @@ import { Box, Button, Paper, - Step, + Step as StepUI, StepContent, StepLabel, Stepper, @@ -30,7 +30,7 @@ import { Theme as MuiTheme } from '@rjsf/material-ui'; import React, { useState } from 'react'; const Form = withTheme(MuiTheme); -type StepType = { +type Step = { schema: JSONSchema; label: string; } & Partial, 'schema'>>; @@ -39,7 +39,7 @@ type Props = { /** * Steps for the form, each contains label and form schema */ - steps: StepType[]; + steps: Step[]; formData: Record; onChange: (e: IChangeEvent) => void; onReset: () => void; @@ -67,7 +67,7 @@ export const MultistepJsonForm = ({ <> {steps.map(({ label, schema, ...formProps }) => ( - + {label}
-
+ ))}
{activeStep === steps.length && ( diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index 9b089fae43..e385f9adc3 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -124,8 +124,8 @@ export const TemplatePage = () => { const handleCreate = async () => { try { - const Id = await scaffolderApi.scaffold(templateName, formState); - setJobId(Id); + const id = await scaffolderApi.scaffold(templateName, formState); + setJobId(id); setModalOpen(true); } catch (e) { errorApi.post(e); diff --git a/plugins/search/src/components/SearchResult/SearchResult.tsx b/plugins/search/src/components/SearchResult/SearchResult.tsx index 4b82679fcc..dc4a502515 100644 --- a/plugins/search/src/components/SearchResult/SearchResult.tsx +++ b/plugins/search/src/components/SearchResult/SearchResult.tsx @@ -59,11 +59,6 @@ type TableHeaderProps = { handleToggleFilters: () => void; }; -// type Filters = { -// selected: string; -// checked: Array; -// }; - // TODO: move out column to make the search result component more generic const columns: TableColumn[] = [ { From 786aa9ae3ac8dde2f2097fd36f1f18b96e37176f Mon Sep 17 00:00:00 2001 From: Manoj - Date: Tue, 2 Feb 2021 22:59:51 +1100 Subject: [PATCH 010/110] branchUrl backward compatibility if 404 with new format --- packages/integration/src/bitbucket/core.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/integration/src/bitbucket/core.ts b/packages/integration/src/bitbucket/core.ts index e9a76d1ee0..f22c727f63 100644 --- a/packages/integration/src/bitbucket/core.ts +++ b/packages/integration/src/bitbucket/core.ts @@ -32,12 +32,19 @@ export async function getBitbucketDefaultBranch( const isHosted = resource === 'bitbucket.org'; // Bitbucket Server https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp184 - // Changed branchUrl to support Atlassian Bitbucket v5.11.1 , which has different branchUrl format const branchUrl = isHosted ? `${config.apiBaseUrl}/repositories/${project}/${repoName}` - : `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches/default`; + : `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/default-branch`; - const response = await fetch(branchUrl, getBitbucketRequestOptions(config)); + var response = await fetch(branchUrl, getBitbucketRequestOptions(config)); + + if (response.status === 404) { + // First try the new format, and then if it gets specifically a 404 it should try the old format + // (to support old Atlassian Bitbucket v5.11.1 format ) + branchUrl = `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches/default`; + response = await fetch(branchUrl, getBitbucketRequestOptions(config)); + } + if (!response.ok) { const message = `Failed to retrieve default branch from ${branchUrl}, ${response.status} ${response.statusText}`; throw new Error(message); From 07f86e88ffaf06d31a289a105bde52b8d225ab9c Mon Sep 17 00:00:00 2001 From: Manoj - Date: Tue, 2 Feb 2021 23:02:31 +1100 Subject: [PATCH 011/110] removed logging statements ( used for debug in local earlier) --- packages/techdocs-common/src/stages/generate/helpers.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/techdocs-common/src/stages/generate/helpers.ts b/packages/techdocs-common/src/stages/generate/helpers.ts index 73be0e8ff0..e396157b92 100644 --- a/packages/techdocs-common/src/stages/generate/helpers.ts +++ b/packages/techdocs-common/src/stages/generate/helpers.ts @@ -133,10 +133,6 @@ export const runCommand = async ({ process.stderr.on('data', stream => { logStream.write(stream); - // added below change to make OS ( child process) exception to the backend logs - const bufferbase64 = Buffer.from(stream, 'base64'); - const textAscii = bufferbase64.toString('ascii'); - console.log(' Process Stderror trace: ', textAscii); }); process.on('error', error => { From 21e18aced4a1392053407dfab0fca997bdb50bf3 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 20 Jan 2021 17:18:07 +0100 Subject: [PATCH 012/110] Create inital class structure --- .../src/scaffolder/tasks/Database.ts | 35 +++++++++++++++++++ .../src/scaffolder/tasks/TaskObserver.ts | 15 ++++++++ .../src/scaffolder/tasks/taskWorker.ts | 34 ++++++++++++++++++ .../src/scaffolder/tasks/tasksDispatcher.ts | 29 +++++++++++++++ .../src/scaffolder/tasks/types.ts | 30 ++++++++++++++++ 5 files changed, 143 insertions(+) create mode 100644 plugins/scaffolder-backend/src/scaffolder/tasks/Database.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/tasks/TaskObserver.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/tasks/taskWorker.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/tasks/tasksDispatcher.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/tasks/types.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/Database.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/Database.ts new file mode 100644 index 0000000000..f446f1e90a --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/Database.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2021 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 { Task, Status } from './types'; + +export class Database { + private readonly store = new Map(); + + get(taskId: string) { + return this.store.get(taskId); + } + + write(task: Task) { + return task; + } + + writeStatus(taskId: string, status: Status) { + const task = this.store.get(taskId); + if (task) { + this.store.set(taskId, { ...task, status }); + } + } +} diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskObserver.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskObserver.ts new file mode 100644 index 0000000000..863d6e76e1 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskObserver.ts @@ -0,0 +1,15 @@ +/* + * Copyright 2021 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. + */ diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/taskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/taskWorker.ts new file mode 100644 index 0000000000..4b1754523c --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/taskWorker.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2021 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 { Status, ClaimResponse } from './types'; +import { Database } from './Database'; + +export class TaskWorker { + static async fromConfig() {} + + constructor(private readonly database: Database) {} + + claim(): Promise { + return Promise.resolve(undefined); + } + + setStatus(taskId: string, status: Status) { + this.database.writeStatus(taskId, status); + } + + heartbeat(runId: number) {} +} diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/tasksDispatcher.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/tasksDispatcher.ts new file mode 100644 index 0000000000..c3dfaa3c88 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/tasksDispatcher.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2021 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 { Task } from './types'; +import { Database } from './Database'; + +export class taskDispatcher { + static async fromConfig(config: { database: Database }) {} + + constructor(private readonly db: Database) {} + + dispatch(task: Task): Promise { + this.db.write(task); + return Promise.resolve('uuid'); + } +} diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts new file mode 100644 index 0000000000..000e292e16 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2021 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 type Status = 'OPEN' | 'PROCESSING' | 'FAILED' | 'CANCELLED'; +export type Task = { + taskId: string; + metadata: string; + status: Status; + lastHeartbeat: string; + retryCount: number; + createdAt: string; +}; + +export type ClaimResponse = { + runId: number; + task: Task; +}; From 5e06a9f45b65f26b971ae7101dc2c2f709a4bba5 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 21 Jan 2021 15:53:01 +0100 Subject: [PATCH 013/110] Refactor into broker --- plugins/scaffolder-backend/package.json | 2 + .../src/scaffolder/tasks/Database.ts | 74 ++++++++++-- .../src/scaffolder/tasks/TaskBroker.test.ts | 63 ++++++++++ .../src/scaffolder/tasks/TaskBroker.ts | 109 ++++++++++++++++++ .../src/scaffolder/tasks/TaskObserver.ts | 15 --- .../src/scaffolder/tasks/taskWorker.ts | 34 ------ .../src/scaffolder/tasks/tasksDispatcher.ts | 29 ----- .../src/scaffolder/tasks/types.ts | 40 +++++-- 8 files changed, 272 insertions(+), 94 deletions(-) create mode 100644 plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.test.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.ts delete mode 100644 plugins/scaffolder-backend/src/scaffolder/tasks/TaskObserver.ts delete mode 100644 plugins/scaffolder-backend/src/scaffolder/tasks/taskWorker.ts delete mode 100644 plugins/scaffolder-backend/src/scaffolder/tasks/tasksDispatcher.ts diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 7af9f567e3..97ab723e81 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -53,7 +53,9 @@ "helmet": "^4.0.0", "isomorphic-git": "^1.8.0", "jsonschema": "^1.2.6", + "luxon": "^1.25.0", "morgan": "^1.10.0", + "p-queue": "^6.3.0", "uuid": "^8.2.0", "winston": "^3.2.1", "yaml": "^1.10.0" diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/Database.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/Database.ts index f446f1e90a..7c3f343b0f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/Database.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/Database.ts @@ -13,23 +13,79 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Task, Status } from './types'; +import { DbTaskRow, TaskSpec } from './types'; +import { v4 as uuid } from 'uuid'; +import { DateTime } from 'luxon'; -export class Database { - private readonly store = new Map(); +export interface Database { + get(taskId: string): Promise; + // updateTask(task: Task): Promise; + createTask(task: TaskSpec): Promise; + claimTask(): Promise; + heartBeat(runId: string): Promise; +} - get(taskId: string) { - return this.store.get(taskId); +export class InMemoryDatabase implements Database { + private readonly store = new Map(); + + async heartBeat(runId: string): Promise { + let task: DbTaskRow | undefined; + + for (const t of this.store.values()) { + if (t.runId === runId) { + task = t; + } + } + + if (!task) { + throw new Error('No task with matching runId found'); + } + + this.store.set(task.taskId, { + ...task, + lastHeartbeat: DateTime.local().toString(), + }); } - write(task: Task) { - return task; + async claimTask(): Promise { + for (const t of this.store.values()) { + if (t.status === 'OPEN') { + const task: DbTaskRow = { + ...t, + status: 'PROCESSING', + runId: uuid(), + }; + this.store.set(t.taskId, task); + return task; + } + } + throw new Error('No task found'); } - writeStatus(taskId: string, status: Status) { + async createTask(spec: TaskSpec): Promise { + return { + taskId: uuid(), + spec, + status: 'OPEN', + retryCount: 0, + createdAt: new Date().toISOString(), + }; + } + + async get(taskId: string): Promise { const task = this.store.get(taskId); if (task) { - this.store.set(taskId, { ...task, status }); + return task; } + throw new Error(`could not found task ${taskId}`); } + + // async updateTask(task: Task): Promise { + // if (!task.taskId) { + // throw new Error('Task must contain id'); + // } + + // this.store.set(task.taskId, task); + // return task; + // } } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.test.ts new file mode 100644 index 0000000000..4fb7439bf8 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.test.ts @@ -0,0 +1,63 @@ +/* + * Copyright 2021 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 { MemoryTaskBroker, TaskAgent } from './TaskBroker'; + +describe('MemoryTaskBroker', () => { + it('should claim a dispatched work item', async () => { + const broker = new MemoryTaskBroker(); + + await broker.dispatch({}); + await expect(broker.claim()).resolves.toEqual(expect.any(TaskAgent)); + }); + + it('should wait for a dispatched work item', async () => { + const broker = new MemoryTaskBroker(); + + const promise = broker.claim(); + + await expect(Promise.race([promise, 'waiting'])).resolves.toBe('waiting'); + + await broker.dispatch({}); + await expect(promise).resolves.toEqual(expect.any(TaskAgent)); + }); + + it('should dispatch multiple items and claim them in order', async () => { + const broker = new MemoryTaskBroker(); + + await broker.dispatch({ name: 'a' }); + await broker.dispatch({ name: 'b' }); + await broker.dispatch({ name: 'c' }); + + const taskA = await broker.claim(); + const taskB = await broker.claim(); + const taskC = await broker.claim(); + await expect(taskA).toEqual(expect.any(TaskAgent)); + await expect(taskB).toEqual(expect.any(TaskAgent)); + await expect(taskC).toEqual(expect.any(TaskAgent)); + await expect(taskA.spec.name).toBe('a'); + await expect(taskB.spec.name).toBe('b'); + await expect(taskC.spec.name).toBe('c'); + }); + + it('should complete a task', async () => { + const broker = new MemoryTaskBroker(); + + await broker.dispatch({}); + const task = await broker.claim(); + await task.complete('COMPLETED'); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.ts new file mode 100644 index 0000000000..322e669373 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.ts @@ -0,0 +1,109 @@ +/* + * Copyright 2021 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 { + CompletedTaskState, + Task, + TaskSpec, + TaskBroker, + Status, +} from './types'; +import { v4 as uuid } from 'uuid'; +import { InMemoryDatabase } from './database'; + +export class TaskAgent implements Task { + private heartbeartInterval?: ReturnType; + + static create(state: TaskState, db: InMemoryDatabase) { + const agent = new TaskAgent(state, db); + agent.start(); + return agent; + } + + // Runs heartbeat internally + private constructor( + private readonly state: TaskState, + private readonly db: InMemoryDatabase, + ) {} + + get spec() { + return this.state.spec; + } + + async emitLog(message: string): Promise { + throw new Error('Method not implemented.'); + } + + async complete(result: CompletedTaskState): Promise { + this.state.status = result === 'FAILED' ? 'COMPLETED' : 'FAILED'; + } + + private start() { + this.heartbeartInterval = setInterval(() => { + const runId = 'iiiid'; + this.db.heartBeat(runId); + }, 4269); + } +} + +interface TaskState { + spec: TaskSpec; + status: Status; + runId: string | undefined; +} + +function defer() { + let resolve = () => {}; + const promise = new Promise(_resolve => { + resolve = _resolve; + }); + return { promise, resolve }; +} + +export class MemoryTaskBroker implements TaskBroker { + private readonly db = new InMemoryDatabase(); + private readonly tasks = new Array(); + private deferredDispatch = defer(); + + async claim(): Promise { + for (;;) { + const pendingTask = await this.db.claimTask(); + if (pendingTask) { + return TaskAgent.create(pendingTask, this.db); + } + + await this.waitForDispatch(); + } + } + + async dispatch(spec: TaskSpec): Promise { + this.tasks.push({ + spec, + status: 'OPEN', + runId: undefined, + }); + this.signalDispatch(); + } + + private waitForDispatch() { + return this.deferredDispatch.promise; + } + + private signalDispatch() { + this.deferredDispatch.resolve(); + this.deferredDispatch = defer(); + } +} diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskObserver.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskObserver.ts deleted file mode 100644 index 863d6e76e1..0000000000 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskObserver.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright 2021 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. - */ diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/taskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/taskWorker.ts deleted file mode 100644 index 4b1754523c..0000000000 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/taskWorker.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2021 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 { Status, ClaimResponse } from './types'; -import { Database } from './Database'; - -export class TaskWorker { - static async fromConfig() {} - - constructor(private readonly database: Database) {} - - claim(): Promise { - return Promise.resolve(undefined); - } - - setStatus(taskId: string, status: Status) { - this.database.writeStatus(taskId, status); - } - - heartbeat(runId: number) {} -} diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/tasksDispatcher.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/tasksDispatcher.ts deleted file mode 100644 index c3dfaa3c88..0000000000 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/tasksDispatcher.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2021 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 { Task } from './types'; -import { Database } from './Database'; - -export class taskDispatcher { - static async fromConfig(config: { database: Database }) {} - - constructor(private readonly db: Database) {} - - dispatch(task: Task): Promise { - this.db.write(task); - return Promise.resolve('uuid'); - } -} diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index 000e292e16..bd8bb0ade5 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -14,17 +14,43 @@ * limitations under the License. */ -export type Status = 'OPEN' | 'PROCESSING' | 'FAILED' | 'CANCELLED'; -export type Task = { +export type Status = + | 'OPEN' + | 'PROCESSING' + | 'FAILED' + | 'CANCELLED' + | 'COMPLETED'; + +export type CompletedTaskState = 'FAILED' | 'COMPLETED'; + +export type DbTaskRow = { taskId: string; - metadata: string; + spec: TaskSpec; status: Status; - lastHeartbeat: string; + lastHeartbeat?: string; retryCount: number; createdAt: string; + runId?: string; +}; + +export type DbTaskEventRow = { + id: number; + runId?: number; + stageName: string; + createdAt: string; }; -export type ClaimResponse = { - runId: number; - task: Task; +export type TaskSpec = { + metadata: string; }; + +export interface Task { + spec: TaskSpec; + emitLog(message: string): Promise; + complete(result: CompletedTaskState): Promise; +} + +export interface TaskBroker { + claim(): Promise; + dispatch(spec: TaskSpec): Promise; +} From 5958318b322cba9c95464eadd5d617cde838d62b Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 22 Jan 2021 09:33:30 +0100 Subject: [PATCH 014/110] Implement createTask and setStatus --- .../src/scaffolder/tasks/Database.ts | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/Database.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/Database.ts index 7c3f343b0f..a3ce732586 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/Database.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/Database.ts @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { DbTaskRow, TaskSpec } from './types'; +import { DbTaskRow, Status, TaskSpec } from './types'; import { v4 as uuid } from 'uuid'; import { DateTime } from 'luxon'; export interface Database { get(taskId: string): Promise; - // updateTask(task: Task): Promise; createTask(task: TaskSpec): Promise; claimTask(): Promise; heartBeat(runId: string): Promise; + setStatus(taskId: string, status: Status): Promise; } export class InMemoryDatabase implements Database { @@ -63,13 +63,15 @@ export class InMemoryDatabase implements Database { } async createTask(spec: TaskSpec): Promise { - return { + const taskRow = { taskId: uuid(), spec, - status: 'OPEN', + status: 'OPEN' as Status, retryCount: 0, createdAt: new Date().toISOString(), }; + this.store.set(taskRow.taskId, taskRow); + return taskRow; } async get(taskId: string): Promise { @@ -80,12 +82,11 @@ export class InMemoryDatabase implements Database { throw new Error(`could not found task ${taskId}`); } - // async updateTask(task: Task): Promise { - // if (!task.taskId) { - // throw new Error('Task must contain id'); - // } - - // this.store.set(task.taskId, task); - // return task; - // } + async setStatus(taskId: string, status: Status): Promise { + const task = this.store.get(taskId); + if (!task) { + throw new Error(`no task found`); + } + this.store.set(task.taskId, { ...task, status }); + } } From a003fffbbb52a898166a91054a95f4ed51146ec6 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 22 Jan 2021 09:34:05 +0100 Subject: [PATCH 015/110] Use InMemoryDatabase --- .../src/scaffolder/tasks/TaskBroker.test.ts | 18 +++++---- .../src/scaffolder/tasks/TaskBroker.ts | 40 +++++++++---------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.test.ts index 4fb7439bf8..ef4b60cfd6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.test.ts @@ -20,7 +20,9 @@ describe('MemoryTaskBroker', () => { it('should claim a dispatched work item', async () => { const broker = new MemoryTaskBroker(); - await broker.dispatch({}); + await broker.dispatch({ + metadata: '', + }); await expect(broker.claim()).resolves.toEqual(expect.any(TaskAgent)); }); @@ -31,16 +33,16 @@ describe('MemoryTaskBroker', () => { await expect(Promise.race([promise, 'waiting'])).resolves.toBe('waiting'); - await broker.dispatch({}); + await broker.dispatch({ metadata: 'foo' }); await expect(promise).resolves.toEqual(expect.any(TaskAgent)); }); it('should dispatch multiple items and claim them in order', async () => { const broker = new MemoryTaskBroker(); - await broker.dispatch({ name: 'a' }); - await broker.dispatch({ name: 'b' }); - await broker.dispatch({ name: 'c' }); + await broker.dispatch({ metadata: 'a' }); + await broker.dispatch({ metadata: 'b' }); + await broker.dispatch({ metadata: 'c' }); const taskA = await broker.claim(); const taskB = await broker.claim(); @@ -48,9 +50,9 @@ describe('MemoryTaskBroker', () => { await expect(taskA).toEqual(expect.any(TaskAgent)); await expect(taskB).toEqual(expect.any(TaskAgent)); await expect(taskC).toEqual(expect.any(TaskAgent)); - await expect(taskA.spec.name).toBe('a'); - await expect(taskB.spec.name).toBe('b'); - await expect(taskC.spec.name).toBe('c'); + await expect(taskA.spec.metadata).toBe('a'); + await expect(taskB.spec.metadata).toBe('b'); + await expect(taskC.spec.metadata).toBe('c'); }); it('should complete a task', async () => { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.ts index 322e669373..c6fb56b42a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.ts @@ -14,14 +14,7 @@ * limitations under the License. */ -import { - CompletedTaskState, - Task, - TaskSpec, - TaskBroker, - Status, -} from './types'; -import { v4 as uuid } from 'uuid'; +import { CompletedTaskState, Task, TaskSpec, TaskBroker } from './types'; import { InMemoryDatabase } from './database'; export class TaskAgent implements Task { @@ -48,20 +41,25 @@ export class TaskAgent implements Task { } async complete(result: CompletedTaskState): Promise { - this.state.status = result === 'FAILED' ? 'COMPLETED' : 'FAILED'; + this.db.setStatus( + this.state.taskId, + result === 'FAILED' ? 'COMPLETED' : 'FAILED', + ); } private start() { this.heartbeartInterval = setInterval(() => { - const runId = 'iiiid'; - this.db.heartBeat(runId); - }, 4269); + if (!this.state.runId) { + throw new Error('no run id provided'); + } + this.db.heartBeat(this.state.runId); + }, 1000); } } interface TaskState { spec: TaskSpec; - status: Status; + taskId: string; runId: string | undefined; } @@ -75,14 +73,20 @@ function defer() { export class MemoryTaskBroker implements TaskBroker { private readonly db = new InMemoryDatabase(); - private readonly tasks = new Array(); private deferredDispatch = defer(); async claim(): Promise { for (;;) { const pendingTask = await this.db.claimTask(); if (pendingTask) { - return TaskAgent.create(pendingTask, this.db); + return TaskAgent.create( + { + runId: pendingTask.runId, + taskId: pendingTask.taskId, + spec: pendingTask.spec, + }, + this.db, + ); } await this.waitForDispatch(); @@ -90,11 +94,7 @@ export class MemoryTaskBroker implements TaskBroker { } async dispatch(spec: TaskSpec): Promise { - this.tasks.push({ - spec, - status: 'OPEN', - runId: undefined, - }); + await this.db.createTask(spec); this.signalDispatch(); } From 971742b6cbdcf5e020cc9f76ebc4f8c66e76ccab Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 22 Jan 2021 11:21:51 +0100 Subject: [PATCH 016/110] return undefined if no task is found --- plugins/scaffolder-backend/src/scaffolder/tasks/Database.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/Database.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/Database.ts index a3ce732586..6f49a1f577 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/Database.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/Database.ts @@ -20,7 +20,7 @@ import { DateTime } from 'luxon'; export interface Database { get(taskId: string): Promise; createTask(task: TaskSpec): Promise; - claimTask(): Promise; + claimTask(): Promise; heartBeat(runId: string): Promise; setStatus(taskId: string, status: Status): Promise; } @@ -47,7 +47,7 @@ export class InMemoryDatabase implements Database { }); } - async claimTask(): Promise { + async claimTask(): Promise { for (const t of this.store.values()) { if (t.status === 'OPEN') { const task: DbTaskRow = { @@ -59,7 +59,7 @@ export class InMemoryDatabase implements Database { return task; } } - throw new Error('No task found'); + return undefined; } async createTask(spec: TaskSpec): Promise { From fcbfc56be8fcc5417219960a5a64e854151093d4 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 22 Jan 2021 11:50:09 +0100 Subject: [PATCH 017/110] Add dispatchResult MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Patrik Oldsberg --- .../src/scaffolder/tasks/TaskBroker.test.ts | 24 ++++++++----- .../src/scaffolder/tasks/TaskBroker.ts | 35 ++++++++++++------- .../src/scaffolder/tasks/types.ts | 6 +++- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.test.ts index ef4b60cfd6..388e2fdec8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.test.ts @@ -14,12 +14,14 @@ * limitations under the License. */ +import { InMemoryDatabase } from './Database'; import { MemoryTaskBroker, TaskAgent } from './TaskBroker'; describe('MemoryTaskBroker', () => { - it('should claim a dispatched work item', async () => { - const broker = new MemoryTaskBroker(); + const storage = new InMemoryDatabase(); + const broker = new MemoryTaskBroker(storage); + it('should claim a dispatched work item', async () => { await broker.dispatch({ metadata: '', }); @@ -27,8 +29,6 @@ describe('MemoryTaskBroker', () => { }); it('should wait for a dispatched work item', async () => { - const broker = new MemoryTaskBroker(); - const promise = broker.claim(); await expect(Promise.race([promise, 'waiting'])).resolves.toBe('waiting'); @@ -38,8 +38,6 @@ describe('MemoryTaskBroker', () => { }); it('should dispatch multiple items and claim them in order', async () => { - const broker = new MemoryTaskBroker(); - await broker.dispatch({ metadata: 'a' }); await broker.dispatch({ metadata: 'b' }); await broker.dispatch({ metadata: 'c' }); @@ -56,10 +54,18 @@ describe('MemoryTaskBroker', () => { }); it('should complete a task', async () => { - const broker = new MemoryTaskBroker(); - - await broker.dispatch({}); + const dispatchResult = await broker.dispatch({ metadata: 'foo' }); const task = await broker.claim(); await task.complete('COMPLETED'); + const taskRow = await storage.get(dispatchResult.taskId); + expect(taskRow.status).toBe('COMPLETED'); + }); + + it('should fail a task', async () => { + const dispatchResult = await broker.dispatch({ metadata: 'foo' }); + const task = await broker.claim(); + await task.complete('FAILED'); + const taskRow = await storage.get(dispatchResult.taskId); + expect(taskRow.status).toBe('FAILED'); }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.ts index c6fb56b42a..6d6fb0a72f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.ts @@ -14,14 +14,20 @@ * limitations under the License. */ -import { CompletedTaskState, Task, TaskSpec, TaskBroker } from './types'; -import { InMemoryDatabase } from './database'; +import { + CompletedTaskState, + Task, + TaskSpec, + TaskBroker, + DispatchResult, +} from './types'; +import { InMemoryDatabase } from './Database'; export class TaskAgent implements Task { private heartbeartInterval?: ReturnType; - static create(state: TaskState, db: InMemoryDatabase) { - const agent = new TaskAgent(state, db); + static create(state: TaskState, storage: InMemoryDatabase) { + const agent = new TaskAgent(state, storage); agent.start(); return agent; } @@ -29,7 +35,7 @@ export class TaskAgent implements Task { // Runs heartbeat internally private constructor( private readonly state: TaskState, - private readonly db: InMemoryDatabase, + private readonly storage: InMemoryDatabase, ) {} get spec() { @@ -41,9 +47,9 @@ export class TaskAgent implements Task { } async complete(result: CompletedTaskState): Promise { - this.db.setStatus( + this.storage.setStatus( this.state.taskId, - result === 'FAILED' ? 'COMPLETED' : 'FAILED', + result === 'FAILED' ? 'FAILED' : 'COMPLETED', ); } @@ -52,7 +58,7 @@ export class TaskAgent implements Task { if (!this.state.runId) { throw new Error('no run id provided'); } - this.db.heartBeat(this.state.runId); + this.storage.heartBeat(this.state.runId); }, 1000); } } @@ -72,12 +78,12 @@ function defer() { } export class MemoryTaskBroker implements TaskBroker { - private readonly db = new InMemoryDatabase(); + constructor(private readonly storage: InMemoryDatabase) {} private deferredDispatch = defer(); async claim(): Promise { for (;;) { - const pendingTask = await this.db.claimTask(); + const pendingTask = await this.storage.claimTask(); if (pendingTask) { return TaskAgent.create( { @@ -85,7 +91,7 @@ export class MemoryTaskBroker implements TaskBroker { taskId: pendingTask.taskId, spec: pendingTask.spec, }, - this.db, + this.storage, ); } @@ -93,9 +99,12 @@ export class MemoryTaskBroker implements TaskBroker { } } - async dispatch(spec: TaskSpec): Promise { - await this.db.createTask(spec); + async dispatch(spec: TaskSpec): Promise { + const taskRow = await this.storage.createTask(spec); this.signalDispatch(); + return { + taskId: taskRow.taskId, + }; } private waitForDispatch() { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index bd8bb0ade5..e8cea06005 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -44,6 +44,10 @@ export type TaskSpec = { metadata: string; }; +export type DispatchResult = { + taskId: string; +}; + export interface Task { spec: TaskSpec; emitLog(message: string): Promise; @@ -52,5 +56,5 @@ export interface Task { export interface TaskBroker { claim(): Promise; - dispatch(spec: TaskSpec): Promise; + dispatch(spec: TaskSpec): Promise; } From 06da4425e0ecb46920ad95c700864c15f4eda4ba Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 22 Jan 2021 11:58:59 +0100 Subject: [PATCH 018/110] Prepend broker files with Memory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Patrik Oldsberg --- .../src/scaffolder/tasks/{Database.ts => MemoryDatabase.ts} | 2 +- .../tasks/{TaskBroker.test.ts => MemoryTaskBroker.test.ts} | 6 +++--- .../scaffolder/tasks/{TaskBroker.ts => MemoryTaskBroker.ts} | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) rename plugins/scaffolder-backend/src/scaffolder/tasks/{Database.ts => MemoryDatabase.ts} (97%) rename plugins/scaffolder-backend/src/scaffolder/tasks/{TaskBroker.test.ts => MemoryTaskBroker.test.ts} (93%) rename plugins/scaffolder-backend/src/scaffolder/tasks/{TaskBroker.ts => MemoryTaskBroker.ts} (94%) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/Database.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryDatabase.ts similarity index 97% rename from plugins/scaffolder-backend/src/scaffolder/tasks/Database.ts rename to plugins/scaffolder-backend/src/scaffolder/tasks/MemoryDatabase.ts index 6f49a1f577..65e64bf2a8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/Database.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryDatabase.ts @@ -25,7 +25,7 @@ export interface Database { setStatus(taskId: string, status: Status): Promise; } -export class InMemoryDatabase implements Database { +export class MemoryDatabase implements Database { private readonly store = new Map(); async heartBeat(runId: string): Promise { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.test.ts similarity index 93% rename from plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.test.ts rename to plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.test.ts index 388e2fdec8..f1493ab3d0 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.test.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { InMemoryDatabase } from './Database'; -import { MemoryTaskBroker, TaskAgent } from './TaskBroker'; +import { MemoryDatabase } from './MemoryDatabase'; +import { MemoryTaskBroker, TaskAgent } from './MemoryTaskBroker'; describe('MemoryTaskBroker', () => { - const storage = new InMemoryDatabase(); + const storage = new MemoryDatabase(); const broker = new MemoryTaskBroker(storage); it('should claim a dispatched work item', async () => { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts similarity index 94% rename from plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.ts rename to plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts index 6d6fb0a72f..d29aeb9be6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts @@ -21,12 +21,12 @@ import { TaskBroker, DispatchResult, } from './types'; -import { InMemoryDatabase } from './Database'; +import { MemoryDatabase } from './MemoryDatabase'; export class TaskAgent implements Task { private heartbeartInterval?: ReturnType; - static create(state: TaskState, storage: InMemoryDatabase) { + static create(state: TaskState, storage: MemoryDatabase) { const agent = new TaskAgent(state, storage); agent.start(); return agent; @@ -35,7 +35,7 @@ export class TaskAgent implements Task { // Runs heartbeat internally private constructor( private readonly state: TaskState, - private readonly storage: InMemoryDatabase, + private readonly storage: MemoryDatabase, ) {} get spec() { From 4f67e8aa21c9d1455137990a493614731d7d7333 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 22 Jan 2021 12:51:05 +0100 Subject: [PATCH 019/110] Fix typo --- .../scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts index d29aeb9be6..a5d90898f5 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts @@ -78,7 +78,7 @@ function defer() { } export class MemoryTaskBroker implements TaskBroker { - constructor(private readonly storage: InMemoryDatabase) {} + constructor(private readonly storage: MemoryDatabase) {} private deferredDispatch = defer(); async claim(): Promise { From 1c1ff59ebac9a8b150da6d47a1e07ac1a8f860d8 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 22 Jan 2021 16:58:41 +0100 Subject: [PATCH 020/110] Add new apis using task broker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Patrik Oldsberg --- .../scaffolder/tasks/MemoryDatabase.test.ts | 21 ++++ .../src/scaffolder/tasks/MemoryDatabase.ts | 50 +++++++- .../src/scaffolder/tasks/MemoryTaskBroker.ts | 54 ++++++++- .../src/scaffolder/tasks/TaskWorker.ts | 110 ++++++++++++++++++ .../src/scaffolder/tasks/index.ts | 19 +++ .../src/scaffolder/tasks/types.ts | 11 +- .../scaffolder-backend/src/service/router.ts | 73 ++++++++++++ 7 files changed, 325 insertions(+), 13 deletions(-) create mode 100644 plugins/scaffolder-backend/src/scaffolder/tasks/MemoryDatabase.test.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/tasks/index.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryDatabase.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryDatabase.test.ts new file mode 100644 index 0000000000..c5522cb38a --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryDatabase.test.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2021 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. + */ + +describe('MemoryDatabase', () => { + it('should be tested', async () => { + expect(1).toBe(2); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryDatabase.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryDatabase.ts index 65e64bf2a8..69fb6a1567 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryDatabase.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryDatabase.ts @@ -13,22 +13,62 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { DbTaskRow, Status, TaskSpec } from './types'; + +import { DbTaskRow, DbTaskEventRow, Status, TaskSpec } from './types'; import { v4 as uuid } from 'uuid'; -import { DateTime } from 'luxon'; export interface Database { get(taskId: string): Promise; createTask(task: TaskSpec): Promise; claimTask(): Promise; - heartBeat(runId: string): Promise; + heartbeat(runId: string): Promise; setStatus(taskId: string, status: Status): Promise; } +type EmitOptions = { + taskId: string; + runId: string; + event: string; +}; + +type ReadOptions = { + taskId: string; + after?: number | undefined; +}; + export class MemoryDatabase implements Database { private readonly store = new Map(); + private readonly events = new Array(); - async heartBeat(runId: string): Promise { + async emit({ taskId, runId, event }: EmitOptions) { + this.events.push({ + id: this.events.length, + taskId, + runId, + event, + createdAt: new Date().toISOString(), + }); + } + + async getEvents({ + taskId, + after, + }: ReadOptions): Promise<{ events: DbTaskEventRow[] }> { + const events = this.events.filter(event => { + if (event.taskId !== taskId) { + return false; + } + if (after !== undefined) { + if (event.id <= after) { + return false; + } + } + return true; + }); + return { events }; + } + + async heartbeat(runId: string): Promise { let task: DbTaskRow | undefined; for (const t of this.store.values()) { @@ -43,7 +83,7 @@ export class MemoryDatabase implements Database { this.store.set(task.taskId, { ...task, - lastHeartbeat: DateTime.local().toString(), + lastHeartbeat: new Date().toISOString(), }); } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts index a5d90898f5..2491c9a091 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts @@ -20,6 +20,7 @@ import { TaskSpec, TaskBroker, DispatchResult, + DbTaskEventRow, } from './types'; import { MemoryDatabase } from './MemoryDatabase'; @@ -43,14 +44,22 @@ export class TaskAgent implements Task { } async emitLog(message: string): Promise { - throw new Error('Method not implemented.'); + await this.storage.emit({ + taskId: this.state.taskId, + runId: this.state.runId, + event: message, + }); } async complete(result: CompletedTaskState): Promise { - this.storage.setStatus( + await this.storage.setStatus( this.state.taskId, result === 'FAILED' ? 'FAILED' : 'COMPLETED', ); + + if (this.heartbeartInterval) { + clearInterval(this.heartbeartInterval); + } } private start() { @@ -58,7 +67,7 @@ export class TaskAgent implements Task { if (!this.state.runId) { throw new Error('no run id provided'); } - this.storage.heartBeat(this.state.runId); + this.storage.heartbeat(this.state.runId); }, 1000); } } @@ -66,7 +75,7 @@ export class TaskAgent implements Task { interface TaskState { spec: TaskSpec; taskId: string; - runId: string | undefined; + runId: string; } function defer() { @@ -87,7 +96,7 @@ export class MemoryTaskBroker implements TaskBroker { if (pendingTask) { return TaskAgent.create( { - runId: pendingTask.runId, + runId: pendingTask.runId!, taskId: pendingTask.taskId, spec: pendingTask.spec, }, @@ -107,6 +116,41 @@ export class MemoryTaskBroker implements TaskBroker { }; } + observe( + options: { + taskId: string; + after: number | undefined; + }, + callback: (result: { events: DbTaskEventRow[] }) => void, + ): () => void { + const { taskId } = options; + + let cancelled = false; + const unsubscribe = () => { + cancelled = true; + }; + + (async () => { + let after = options.after; + while (!cancelled) { + const result = await this.storage.getEvents({ taskId, after: after }); + const { events } = result; + if (events.length) { + after = events[events.length - 1].id; + try { + callback(result); + } catch (error) { + console.log('DEBUG: error =', error); + } + } + + await new Promise(resolve => setTimeout(resolve, 1000)); + } + })(); + + return unsubscribe; + } + private waitForDispatch() { return this.deferredDispatch.promise; } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts new file mode 100644 index 0000000000..dc15b3db65 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -0,0 +1,110 @@ +/* + * Copyright 2021 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 { TaskBroker, Task } from './types'; +import { Logger } from 'winston'; +import Docker from 'dockerode'; +import { CatalogEntityClient } from '../../lib/catalog'; +import { + FilePreparer, + parseLocationAnnotation, + PreparerBuilder, + TemplaterBuilder, + PublisherBuilder, +} from '../stages'; + +type Options = { + logger: Logger; + taskBroker: TaskBroker; + workingDirectory: string; + dockerClient: Docker; + entityClient: CatalogEntityClient; + preparers: PreparerBuilder; + templaters: TemplaterBuilder; + publishers: PublisherBuilder; +}; + +export class TaskWorker { + constructor(private readonly options: Options) {} + + start() { + (async () => { + for (;;) { + const task = await this.options.taskBroker.claim(); + await this.runOneTask(task); + } + })(); + } + + async runOneTask(task: Task) { + const { + dockerClient, + preparers, + templaters, + publishers, + workingDirectory, + logger, + } = this.options; + + try { + task.emitLog('Task claimed, waiting ...'); + // Give us some time to curl observe + await new Promise(resolve => setTimeout(resolve, 5000)); + + const { values, template } = task.spec; + task.emitLog('Prepare the skeleton'); + const { protocol, location: pullPath } = parseLocationAnnotation( + task.spec.template, + ); + + const preparer = + protocol === 'file' ? new FilePreparer() : preparers.get(pullPath); + const templater = templaters.get(template); + const publisher = publishers.get(values.storePath); + + const skeletonDir = await preparer.prepare(task.spec.template, { + logger, + workingDirectory: workingDirectory, + }); + + task.emitLog('Run the templater'); + const { resultDir } = await templater.run({ + directory: skeletonDir, + dockerClient, + logStream: process.stdout, // yay + values: values, + }); + + task.emitLog('Publish template'); + logger.info('Will now store the template'); + + logger.info('Totally storing the template now'); + await new Promise(resolve => setTimeout(resolve, 5000)); + // const result = await publisher.publish({ + // values: values, + // directory: resultDir, + // logger, + // }); + // task.emitLog(`Result: ${JSON.stringify(result)}`); + + task.emitLog(`Completely done now!`); + + await task.complete('COMPLETED'); + } catch (error) { + await task.complete('FAILED'); + } + } +} diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts new file mode 100644 index 0000000000..1cc9842fed --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2021 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 { MemoryDatabase } from './MemoryDatabase'; +export { MemoryTaskBroker } from './MemoryTaskBroker'; +export { TaskWorker } from './TaskWorker'; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index e8cea06005..12cb9fee1f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -14,6 +14,9 @@ * limitations under the License. */ +import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; +import { TemplaterValues } from '..'; + export type Status = | 'OPEN' | 'PROCESSING' @@ -35,13 +38,15 @@ export type DbTaskRow = { export type DbTaskEventRow = { id: number; - runId?: number; - stageName: string; + runId: string; + taskId: string; + event: string; createdAt: string; }; export type TaskSpec = { - metadata: string; + template: TemplateEntityV1alpha1; + values: TemplaterValues; }; export type DispatchResult = { diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 9098e5d05e..706027f62e 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -33,6 +33,11 @@ import { import { CatalogEntityClient } from '../lib/catalog'; import { validate, ValidatorResult } from 'jsonschema'; import parseGitUrl from 'git-url-parse'; +import { + MemoryTaskBroker, + MemoryDatabase, + TaskWorker, +} from '../scaffolder/tasks'; export interface RouterOptions { preparers: PreparerBuilder; @@ -63,6 +68,18 @@ export async function createRouter( const logger = parentLogger.child({ plugin: 'scaffolder' }); const jobProcessor = await JobProcessor.fromConfig({ config, logger }); + const taskBroker = new MemoryTaskBroker(new MemoryDatabase()); + const worker = new TaskWorker({ + logger, + taskBroker, + workingDirectory: 'todo', + dockerClient, + entityClient, + preparers, + publishers, + templaters, + }); + worker.start(); router .get('/v1/job/:jobId', ({ params }, res) => { @@ -88,6 +105,62 @@ export async function createRouter( error: job.error, }); }) + // curl -X POST -d '{"templateName":"springboot-template","values": {"storePath":"https://github.com/jhaals/foo", "component_id":"woop", "description": "apa", "owner": "me" }}' -H 'Content-Type: application/json' localhost:7000/api/scaffolder/v2/tasks + .post('/v2/tasks', async (req, res) => { + const templateName: string = req.body.templateName; + const values: TemplaterValues = { + ...req.body.values, + destination: { + git: parseGitUrl(req.body.values.storePath), + }, + }; + const template = await entityClient.findTemplate(templateName); + + const validationResult: ValidatorResult = validate( + values, + template.spec.schema, + ); + + if (!validationResult.valid) { + res.status(400).json({ errors: validationResult.errors }); + return; + } + const result = await taskBroker.dispatch({ + template, + values, + }); + + res.status(201).json({ id: result.taskId }); + }) + + .get('/v2/tasks/:taskId/eventstream', async (req, res) => { + const { taskId } = req.params; + const after = Number(req.query.after) || undefined; + logger.info('event stream opened'); + + // Mandatory headers and http status to keep connection open + res.writeHead(200, { + Connection: 'keep-alive', + 'Cache-Control': 'no-cache', + 'Content-Type': 'text/event-stream', + }); + // After client opens connection send all nests as string + const unsubscribe = taskBroker.observe( + { taskId, after }, + ({ events }) => { + for (const event of events) { + res.write(`event:${JSON.stringify(event)}\n\n`); + } + }, + ); + // When client closes connection we update the clients list + // avoiding the disconnected one + req.on('close', () => { + unsubscribe(); + logger.info('event stream closed'); + }); + }) + .post('/v1/jobs', async (req, res) => { const templateName: string = req.body.templateName; const values: TemplaterValues = { From dfea89fa98562ef30713eba9db5942d8351f3e70 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 25 Jan 2021 10:12:15 +0100 Subject: [PATCH 021/110] Pass taskSpec in tests --- .../scaffolder/tasks/MemoryTaskBroker.test.ts | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.test.ts index f1493ab3d0..aa23044749 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.test.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; +import { TemplaterValues } from '../stages/templater/types'; import { MemoryDatabase } from './MemoryDatabase'; import { MemoryTaskBroker, TaskAgent } from './MemoryTaskBroker'; @@ -21,10 +23,13 @@ describe('MemoryTaskBroker', () => { const storage = new MemoryDatabase(); const broker = new MemoryTaskBroker(storage); + const taskSpec = { + values: {} as TemplaterValues, + template: {} as TemplateEntityV1alpha1, + }; + it('should claim a dispatched work item', async () => { - await broker.dispatch({ - metadata: '', - }); + await broker.dispatch(taskSpec); await expect(broker.claim()).resolves.toEqual(expect.any(TaskAgent)); }); @@ -33,14 +38,23 @@ describe('MemoryTaskBroker', () => { await expect(Promise.race([promise, 'waiting'])).resolves.toBe('waiting'); - await broker.dispatch({ metadata: 'foo' }); + await broker.dispatch(taskSpec); await expect(promise).resolves.toEqual(expect.any(TaskAgent)); }); it('should dispatch multiple items and claim them in order', async () => { - await broker.dispatch({ metadata: 'a' }); - await broker.dispatch({ metadata: 'b' }); - await broker.dispatch({ metadata: 'c' }); + await broker.dispatch({ + values: { owner: 'a' } as TemplaterValues, + template: {} as TemplateEntityV1alpha1, + }); + await broker.dispatch({ + values: { owner: 'b' } as TemplaterValues, + template: {} as TemplateEntityV1alpha1, + }); + await broker.dispatch({ + values: { owner: 'c' } as TemplaterValues, + template: {} as TemplateEntityV1alpha1, + }); const taskA = await broker.claim(); const taskB = await broker.claim(); @@ -48,13 +62,13 @@ describe('MemoryTaskBroker', () => { await expect(taskA).toEqual(expect.any(TaskAgent)); await expect(taskB).toEqual(expect.any(TaskAgent)); await expect(taskC).toEqual(expect.any(TaskAgent)); - await expect(taskA.spec.metadata).toBe('a'); - await expect(taskB.spec.metadata).toBe('b'); - await expect(taskC.spec.metadata).toBe('c'); + await expect(taskA.spec.values.owner).toBe('a'); + await expect(taskB.spec.values.owner).toBe('b'); + await expect(taskC.spec.values.owner).toBe('c'); }); it('should complete a task', async () => { - const dispatchResult = await broker.dispatch({ metadata: 'foo' }); + const dispatchResult = await broker.dispatch(taskSpec); const task = await broker.claim(); await task.complete('COMPLETED'); const taskRow = await storage.get(dispatchResult.taskId); @@ -62,7 +76,7 @@ describe('MemoryTaskBroker', () => { }); it('should fail a task', async () => { - const dispatchResult = await broker.dispatch({ metadata: 'foo' }); + const dispatchResult = await broker.dispatch(taskSpec); const task = await broker.claim(); await task.complete('FAILED'); const taskRow = await storage.get(dispatchResult.taskId); From 7d13f22c3db614d9be40b6652e42fdb07bb5185f Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 25 Jan 2021 13:16:28 +0100 Subject: [PATCH 022/110] Add eventType for closing observer stream Co-authored-by: Patrik Oldsberg --- .../src/scaffolder/tasks/MemoryDatabase.ts | 22 +++++++++++++------ .../scaffolder/tasks/MemoryTaskBroker.test.ts | 8 +++---- .../src/scaffolder/tasks/MemoryTaskBroker.ts | 12 +++++++--- .../src/scaffolder/tasks/TaskWorker.ts | 4 ++-- .../src/scaffolder/tasks/types.ts | 16 ++++++++------ .../scaffolder-backend/src/service/router.ts | 4 ++++ 6 files changed, 43 insertions(+), 23 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryDatabase.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryDatabase.ts index 69fb6a1567..b2d1cf667b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryDatabase.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryDatabase.ts @@ -14,7 +14,13 @@ * limitations under the License. */ -import { DbTaskRow, DbTaskEventRow, Status, TaskSpec } from './types'; +import { + DbTaskRow, + DbTaskEventRow, + Status, + TaskSpec, + TaskEventType, +} from './types'; import { v4 as uuid } from 'uuid'; export interface Database { @@ -28,7 +34,8 @@ export interface Database { type EmitOptions = { taskId: string; runId: string; - event: string; + body: string; + type: TaskEventType; }; type ReadOptions = { @@ -40,12 +47,13 @@ export class MemoryDatabase implements Database { private readonly store = new Map(); private readonly events = new Array(); - async emit({ taskId, runId, event }: EmitOptions) { + async emit({ taskId, runId, body, type }: EmitOptions) { this.events.push({ id: this.events.length, taskId, runId, - event, + body, + type, createdAt: new Date().toISOString(), }); } @@ -89,10 +97,10 @@ export class MemoryDatabase implements Database { async claimTask(): Promise { for (const t of this.store.values()) { - if (t.status === 'OPEN') { + if (t.status === 'open') { const task: DbTaskRow = { ...t, - status: 'PROCESSING', + status: 'processing', runId: uuid(), }; this.store.set(t.taskId, task); @@ -106,7 +114,7 @@ export class MemoryDatabase implements Database { const taskRow = { taskId: uuid(), spec, - status: 'OPEN' as Status, + status: 'open' as Status, retryCount: 0, createdAt: new Date().toISOString(), }; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.test.ts index aa23044749..1ac416ad4f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.test.ts @@ -70,16 +70,16 @@ describe('MemoryTaskBroker', () => { it('should complete a task', async () => { const dispatchResult = await broker.dispatch(taskSpec); const task = await broker.claim(); - await task.complete('COMPLETED'); + await task.complete('completed'); const taskRow = await storage.get(dispatchResult.taskId); - expect(taskRow.status).toBe('COMPLETED'); + expect(taskRow.status).toBe('completed'); }); it('should fail a task', async () => { const dispatchResult = await broker.dispatch(taskSpec); const task = await broker.claim(); - await task.complete('FAILED'); + await task.complete('failed'); const taskRow = await storage.get(dispatchResult.taskId); - expect(taskRow.status).toBe('FAILED'); + expect(taskRow.status).toBe('failed'); }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts index 2491c9a091..c22bb0bddb 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts @@ -47,16 +47,22 @@ export class TaskAgent implements Task { await this.storage.emit({ taskId: this.state.taskId, runId: this.state.runId, - event: message, + body: message, + type: 'log', }); } async complete(result: CompletedTaskState): Promise { await this.storage.setStatus( this.state.taskId, - result === 'FAILED' ? 'FAILED' : 'COMPLETED', + result === 'failed' ? 'failed' : 'completed', ); - + this.storage.emit({ + taskId: this.state.taskId, + runId: this.state.runId, + body: `Run completed with status: ${result}`, + type: 'completion', + }); if (this.heartbeartInterval) { clearInterval(this.heartbeartInterval); } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index dc15b3db65..641a8cd9b1 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -102,9 +102,9 @@ export class TaskWorker { task.emitLog(`Completely done now!`); - await task.complete('COMPLETED'); + await task.complete('completed'); } catch (error) { - await task.complete('FAILED'); + await task.complete('failed'); } } } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index 12cb9fee1f..47226dc6e9 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -18,13 +18,13 @@ import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { TemplaterValues } from '..'; export type Status = - | 'OPEN' - | 'PROCESSING' - | 'FAILED' - | 'CANCELLED' - | 'COMPLETED'; + | 'open' + | 'processing' + | 'failed' + | 'cancelled' + | 'completed'; -export type CompletedTaskState = 'FAILED' | 'COMPLETED'; +export type CompletedTaskState = 'failed' | 'completed'; export type DbTaskRow = { taskId: string; @@ -36,11 +36,13 @@ export type DbTaskRow = { runId?: string; }; +export type TaskEventType = 'completion' | 'log'; export type DbTaskEventRow = { id: number; runId: string; taskId: string; - event: string; + body: string; + type: TaskEventType; createdAt: string; }; diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 706027f62e..9514d215d0 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -150,6 +150,10 @@ export async function createRouter( ({ events }) => { for (const event of events) { res.write(`event:${JSON.stringify(event)}\n\n`); + if (event.type === 'completion') { + unsubscribe(); + res.end(); + } } }, ); From fef53431df1c3745bfb210eb6b8cf8a637b75213 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 25 Jan 2021 14:02:51 +0100 Subject: [PATCH 023/110] create logger and stream that emit events --- .../src/scaffolder/tasks/TaskWorker.ts | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 641a8cd9b1..d500fc8e5c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -14,9 +14,11 @@ * limitations under the License. */ -import { TaskBroker, Task } from './types'; +import { PassThrough } from 'stream'; import { Logger } from 'winston'; +import * as winston from 'winston'; import Docker from 'dockerode'; +import { TaskBroker, Task } from './types'; import { CatalogEntityClient } from '../../lib/catalog'; import { FilePreparer, @@ -59,6 +61,24 @@ export class TaskWorker { logger, } = this.options; + const taskLogger = winston.createLogger({ + level: process.env.LOG_LEVEL || 'info', + format: winston.format.combine( + winston.format.colorize(), + winston.format.timestamp(), + winston.format.simple(), + ), + defaultMeta: {}, + }); + + const stream = new PassThrough(); + stream.on('data', data => { + const message = data.toString().trim(); + if (message?.length > 1) task.emitLog(message); + }); + + taskLogger.add(new winston.transports.Stream({ stream })); + try { task.emitLog('Task claimed, waiting ...'); // Give us some time to curl observe @@ -76,7 +96,7 @@ export class TaskWorker { const publisher = publishers.get(values.storePath); const skeletonDir = await preparer.prepare(task.spec.template, { - logger, + logger: taskLogger, workingDirectory: workingDirectory, }); @@ -84,7 +104,7 @@ export class TaskWorker { const { resultDir } = await templater.run({ directory: skeletonDir, dockerClient, - logStream: process.stdout, // yay + logStream: stream, values: values, }); @@ -100,8 +120,6 @@ export class TaskWorker { // }); // task.emitLog(`Result: ${JSON.stringify(result)}`); - task.emitLog(`Completely done now!`); - await task.complete('completed'); } catch (error) { await task.complete('failed'); From 8623025c9b789b85bdf87b9cdf9cc94995f55859 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 26 Jan 2021 12:00:40 +0100 Subject: [PATCH 024/110] Wip wip --- .../src/scaffolder/stages/legacy.ts | 106 +++++++++++++++++ .../scaffolder/tasks/MemoryTaskBroker.test.ts | 2 +- .../src/scaffolder/tasks/TemplateConverter.ts | 110 ++++++++++++++++++ .../src/scaffolder/tasks/types.ts | 11 +- .../scaffolder-backend/src/service/router.ts | 2 + 5 files changed, 226 insertions(+), 5 deletions(-) create mode 100644 plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts b/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts new file mode 100644 index 0000000000..c9bd89c3dd --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts @@ -0,0 +1,106 @@ +/* + * Copyright 2021 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 { Config } from '@backstage/config'; +import { TemplateActionRegistry } from '../TemplateConverter'; +import { FilePreparer } from './prepare'; +import Docker from 'dockerode'; + +type Options = { + logger: Logger; + config: Config; + dockerClient: Docker; +}; + +export function registerLegacyActions( + registry: TemplateActionRegistry, + options: Options, +) { + registry.register({ + id: 'legacy:prepare', + async handler(ctx) { + const { logger } = ctx; + console.log(ctx); + logger.info('Task claimed, waiting ...'); + // Give us some time to curl observe + await new Promise(resolve => setTimeout(resolve, 5000)); + + logger.info('Prepare the skeleton'); + + const { protocol, pullPath } = ctx.parameters; + + const preparer = + protocol === 'file' + ? new FilePreparer() + : preparers.get(pullPath as string); + + await preparer.prepare(task.spec.template, { + logger, + ctx.workspaceDir, + }); + ctx.output('catalogInfoUrl', 'httpderp://asdasd'); + }, + }); + + // try { + // const { values, template } = task.spec; + // task.emitLog('Prepare the skeleton'); + // const { protocol, location: pullPath } = parseLocationAnnotation( + // task.spec.template, + // ); + // const preparer = + // protocol === 'file' ? new FilePreparer() : preparers.get(pullPath); + // const templater = templaters.get(template); + // const publisher = publishers.get(values.storePath); + + // const skeletonDir = await preparer.prepare(task.spec.template, { + // logger: taskLogger, + // workingDirectory: workingDirectory, + // }); + + registry.register({ + id: 'legacy:template', + async handler(ctx) { + const { logger } = ctx; + + const templater = templaters.get(ctx.parameters.templater as string); + + logger.info('Run the templater'); + const { resultDir } = await templater.run({ + directory: ctx.workspaceDir, + dockerClient, + logStream: ctx.logStream, + values: ctx.parameters.values as TemplaterValues, + }); + }, + }); + // task.emitLog('Publish template'); + // logger.info('Will now store the template'); + + // logger.info('Totally storing the template now'); + // await new Promise(resolve => setTimeout(resolve, 5000)); + // // const result = await publisher.publish({ + // // values: values, + // // directory: resultDir, + // // logger, + // // }); + // // task.emitLog(`Result: ${JSON.stringify(result)}`); + + // await task.complete('completed'); + // } catch (error) { + // await task.complete('failed'); + // } +} diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.test.ts index 1ac416ad4f..686a6d9132 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.test.ts @@ -15,7 +15,7 @@ */ import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; -import { TemplaterValues } from '../stages/templater/types'; +import { TemplaterValues } from './actions/templater/types'; import { MemoryDatabase } from './MemoryDatabase'; import { MemoryTaskBroker, TaskAgent } from './MemoryTaskBroker'; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts new file mode 100644 index 0000000000..e435812fe5 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts @@ -0,0 +1,110 @@ +/* + * Copyright 2021 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 { JsonValue } from '@backstage/config'; +import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; +import { Logger } from 'winston'; +import type { Writable } from 'stream'; +import { + getTemplaterKey, + parseLocationAnnotation, + TemplaterValues, +} from '../jobs/actions'; +import { TaskSpec } from './types'; +import { ConflictError, NotFoundError } from '@backstage/backend-common'; + +function templateEntityToSpec( + template: TemplateEntityV1alpha1, + values: TemplaterValues, +): TaskSpec { + const steps: TaskSpec['steps'] = []; + + const { protocol, location: pullPath } = parseLocationAnnotation(template); + const templater = getTemplaterKey(template); + + steps.push({ + id: 'prepare', + name: 'Prepare', + action: 'legacy:prepare', + parameters: { + protocol, + pullPath, + }, + }); + + steps.push({ + id: 'template', + name: 'Template', + action: 'legacy:template', + parameters: { + templater, + values, + }, + }); + + steps.push({ + id: 'publish', + name: 'Publishing', + action: 'publish', + parameters: { + values, + directory, + }, + }); + + return { steps }; +} + +type ActionContext = { + logger: Logger; + logStream: Writable; + + workspaceDir: string; + parameters: { [name: string]: JsonValue }; + output(name: string, value: JsonValue): void; +}; + +type TemplateAction = { + id: string; + handler: (ctx: ActionContext) => Promise; +}; + +export class TemplateActionRegistry { + private readonly actions = new Map(); + + register(action: TemplateAction) { + if (this.actions.has(action.id)) { + throw new ConflictError( + `Template action with id ${action.id} as already been registered`, + ); + } + this.actions.set(action.id, action); + } + + // validate + // ensure that action exist. + // template variables exist. + + get(actionId: string): TemplateAction { + const action = this.actions.get(actionId); + if (!action) { + throw new NotFoundError( + `Template action with id ${actionId} is not registered.`, + ); + } + return action; + } +} diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index 47226dc6e9..620b6e7243 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -14,8 +14,7 @@ * limitations under the License. */ -import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; -import { TemplaterValues } from '..'; +import { JsonObject } from '@backstage/config'; export type Status = | 'open' @@ -47,8 +46,12 @@ export type DbTaskEventRow = { }; export type TaskSpec = { - template: TemplateEntityV1alpha1; - values: TemplaterValues; + steps: Array<{ + id: string; + name: string; + action: string; + parameters?: JsonObject; + }>; }; export type DispatchResult = { diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 9514d215d0..fc40d45ce2 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -38,6 +38,8 @@ import { MemoryDatabase, TaskWorker, } from '../scaffolder/tasks'; +import { TemplateActionRegistry } from '../scaffolder/tasks/TemplateConverter'; +import { LOCATION_ANNOTATION } from '@backstage/catalog-model'; export interface RouterOptions { preparers: PreparerBuilder; From cf896972ef873b4a94f669d10b9a8f5a3e186e00 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 29 Jan 2021 13:16:50 +0100 Subject: [PATCH 025/110] Wire up task registry with legacy tasks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Patrik Oldsberg --- .../src/scaffolder/stages/legacy.ts | 105 ++++++------ .../src/scaffolder/tasks/MemoryTaskBroker.ts | 8 + .../src/scaffolder/tasks/TaskWorker.ts | 155 ++++++++++-------- .../src/scaffolder/tasks/TemplateConverter.ts | 31 ++-- .../src/scaffolder/tasks/types.ts | 6 +- .../scaffolder-backend/src/service/helpers.ts | 44 +++++ .../scaffolder-backend/src/service/router.ts | 23 ++- 7 files changed, 242 insertions(+), 130 deletions(-) create mode 100644 plugins/scaffolder-backend/src/service/helpers.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts b/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts index c9bd89c3dd..6244e40d68 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts @@ -14,21 +14,25 @@ * limitations under the License. */ -import { Config } from '@backstage/config'; -import { TemplateActionRegistry } from '../TemplateConverter'; -import { FilePreparer } from './prepare'; +import { TemplateActionRegistry } from '../tasks/TemplateConverter'; +import { FilePreparer, PreparerBuilder } from './prepare'; import Docker from 'dockerode'; +import { TemplaterBuilder, TemplaterValues } from './templater'; +import { PublisherBuilder } from './publish'; type Options = { - logger: Logger; - config: Config; dockerClient: Docker; + preparers: PreparerBuilder; + templaters: TemplaterBuilder; + publishers: PublisherBuilder; }; export function registerLegacyActions( registry: TemplateActionRegistry, options: Options, ) { + const { dockerClient, preparers, templaters, publishers } = options; + registry.register({ id: 'legacy:prepare', async handler(ctx) { @@ -41,36 +45,18 @@ export function registerLegacyActions( logger.info('Prepare the skeleton'); const { protocol, pullPath } = ctx.parameters; - + const url = pullPath as string; const preparer = - protocol === 'file' - ? new FilePreparer() - : preparers.get(pullPath as string); + protocol === 'file' ? new FilePreparer() : preparers.get(url); - await preparer.prepare(task.spec.template, { - logger, - ctx.workspaceDir, + await preparer.prepare({ + url, + logger: ctx.logger, + workspacePath: ctx.workspacePath, }); - ctx.output('catalogInfoUrl', 'httpderp://asdasd'); }, }); - // try { - // const { values, template } = task.spec; - // task.emitLog('Prepare the skeleton'); - // const { protocol, location: pullPath } = parseLocationAnnotation( - // task.spec.template, - // ); - // const preparer = - // protocol === 'file' ? new FilePreparer() : preparers.get(pullPath); - // const templater = templaters.get(template); - // const publisher = publishers.get(values.storePath); - - // const skeletonDir = await preparer.prepare(task.spec.template, { - // logger: taskLogger, - // workingDirectory: workingDirectory, - // }); - registry.register({ id: 'legacy:template', async handler(ctx) { @@ -79,28 +65,55 @@ export function registerLegacyActions( const templater = templaters.get(ctx.parameters.templater as string); logger.info('Run the templater'); - const { resultDir } = await templater.run({ - directory: ctx.workspaceDir, + await templater.run({ + workspacePath: ctx.workspacePath, dockerClient, logStream: ctx.logStream, values: ctx.parameters.values as TemplaterValues, }); }, }); - // task.emitLog('Publish template'); - // logger.info('Will now store the template'); - // logger.info('Totally storing the template now'); - // await new Promise(resolve => setTimeout(resolve, 5000)); - // // const result = await publisher.publish({ - // // values: values, - // // directory: resultDir, - // // logger, - // // }); - // // task.emitLog(`Result: ${JSON.stringify(result)}`); - - // await task.complete('completed'); - // } catch (error) { - // await task.complete('failed'); - // } + registry.register({ + id: 'legacy:publish', + async handler(ctx) { + const { values } = ctx.parameters; + if ( + typeof values !== 'object' || + values === null || + Array.isArray(values) + ) { + throw new Error( + `Invalid values passed to publish, got ${typeof values}`, + ); + } + const storePath = values.storePath as unknown; + if (typeof storePath !== 'string') { + throw new Error( + `Invalid store path passed to publish, got ${typeof storePath}`, + ); + } + const owner = values.owner as unknown; + if (typeof owner !== 'string') { + throw new Error( + `Invalid store path passed to publish, got ${typeof owner}`, + ); + } + const publisher = publishers.get(storePath); + ctx.logger.info('Will now store the template'); + const { remoteUrl, catalogInfoUrl } = await publisher.publish({ + values: { + ...values, + owner, + storePath, + }, + workspacePath: ctx.workspacePath, + logger: ctx.logger, + }); + ctx.output('remoteUrl', remoteUrl); + if (catalogInfoUrl) { + ctx.output('catalogInfoUrl', catalogInfoUrl); + } + }, + }); } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts index c22bb0bddb..c5920fe836 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts @@ -43,6 +43,14 @@ export class TaskAgent implements Task { return this.state.spec; } + get runId() { + return this.state.runId; + } + + get taskId() { + return this.state.taskId; + } + async emitLog(message: string): Promise { await this.storage.emit({ taskId: this.state.taskId, diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index d500fc8e5c..6eb1c4763c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -17,26 +17,17 @@ import { PassThrough } from 'stream'; import { Logger } from 'winston'; import * as winston from 'winston'; -import Docker from 'dockerode'; +import { JsonValue } from '@backstage/config'; import { TaskBroker, Task } from './types'; -import { CatalogEntityClient } from '../../lib/catalog'; -import { - FilePreparer, - parseLocationAnnotation, - PreparerBuilder, - TemplaterBuilder, - PublisherBuilder, -} from '../stages'; +import { TemplateActionRegistry } from './TemplateConverter'; +import fs from 'fs-extra'; +import path from 'path'; type Options = { logger: Logger; taskBroker: TaskBroker; workingDirectory: string; - dockerClient: Docker; - entityClient: CatalogEntityClient; - preparers: PreparerBuilder; - templaters: TemplaterBuilder; - publishers: PublisherBuilder; + actionRegistry: TemplateActionRegistry; }; export class TaskWorker { @@ -52,66 +43,90 @@ export class TaskWorker { } async runOneTask(task: Task) { - const { - dockerClient, - preparers, - templaters, - publishers, - workingDirectory, - logger, - } = this.options; - - const taskLogger = winston.createLogger({ - level: process.env.LOG_LEVEL || 'info', - format: winston.format.combine( - winston.format.colorize(), - winston.format.timestamp(), - winston.format.simple(), - ), - defaultMeta: {}, - }); - - const stream = new PassThrough(); - stream.on('data', data => { - const message = data.toString().trim(); - if (message?.length > 1) task.emitLog(message); - }); - - taskLogger.add(new winston.transports.Stream({ stream })); - try { - task.emitLog('Task claimed, waiting ...'); + const { actionRegistry, logger } = this.options; + + // bbl LUUUNCH + // taskID and runId not part of task? O_o + const workspacePath = await this.createWorkPath(task.taskId, task.runId); + + const taskLogger = winston.createLogger({ + level: process.env.LOG_LEVEL || 'info', + format: winston.format.combine( + winston.format.colorize(), + winston.format.timestamp(), + winston.format.simple(), + ), + defaultMeta: {}, + }); + + const stream = new PassThrough(); + stream.on('data', data => { + const message = data.toString().trim(); + if (message?.length > 1) task.emitLog(message); + }); + + taskLogger.add(new winston.transports.Stream({ stream })); + // Give us some time to curl observe + task.emitLog('Task claimed, waiting ...'); await new Promise(resolve => setTimeout(resolve, 5000)); + task.emitLog(`Starting up work with ${task.spec.steps.length} steps`); - const { values, template } = task.spec; - task.emitLog('Prepare the skeleton'); - const { protocol, location: pullPath } = parseLocationAnnotation( - task.spec.template, - ); + const outputs: { [name: string]: JsonValue } = {}; - const preparer = - protocol === 'file' ? new FilePreparer() : preparers.get(pullPath); - const templater = templaters.get(template); - const publisher = publishers.get(values.storePath); + for (const step of task.spec.steps) { + task.emitLog(`Beginning step ${step.name}`); - const skeletonDir = await preparer.prepare(task.spec.template, { - logger: taskLogger, - workingDirectory: workingDirectory, - }); + const action = actionRegistry.get(step.action); + if (!action) { + throw new Error(`Action '${step.action}' does not exist`); + } - task.emitLog('Run the templater'); - const { resultDir } = await templater.run({ - directory: skeletonDir, - dockerClient, - logStream: stream, - values: values, - }); + // TODO: substitute any placeholders with output from previous steps + const parameters = step.parameters!; - task.emitLog('Publish template'); - logger.info('Will now store the template'); + await action.handler({ + logger, + logStream: stream, + parameters, + workspacePath, + output(name: string, value: JsonValue) { + outputs[name] = value; + }, + }); - logger.info('Totally storing the template now'); + task.emitLog(`Finished step ${step.name}`); + } + + // const { values, template } = task.spec; + // task.emitLog('Prepare the skeleton'); + // const { protocol, location: pullPath } = parseLocationAnnotation( + // task.spec.template, + // ); + + // const preparer = + // protocol === 'file' ? new FilePreparer() : preparers.get(pullPath); + // const templater = templaters.get(template); + // const publisher = publishers.get(values.storePath); + + // const skeletonDir = await preparer.prepare(task.spec.template, { + // logger: taskLogger, + // workingDirectory: workingDirectory, + // }); + + // task.emitLog('Run the templater'); + // const { resultDir } = await templater.run({ + // directory: skeletonDir, + // dockerClient, + // logStream: stream, + // values: values, + // }); + + // task.emitLog('Publish template'); + // logger.info('Will now store the template'); + + logger.info('So done right now'); await new Promise(resolve => setTimeout(resolve, 5000)); // const result = await publisher.publish({ // values: values, @@ -125,4 +140,14 @@ export class TaskWorker { await task.complete('failed'); } } + + async createWorkPath(taskId: string, runId: string): Promise { + const workspacePath = path.join( + this.options.workingDirectory, + taskId, + runId, + ); + fs.ensureDir(workspacePath); + return workspacePath; + } } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts index e435812fe5..bd06dbaa91 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts @@ -14,25 +14,37 @@ * limitations under the License. */ +import { resolve as resolvePath } from 'path'; import { JsonValue } from '@backstage/config'; import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { Logger } from 'winston'; import type { Writable } from 'stream'; -import { - getTemplaterKey, - parseLocationAnnotation, - TemplaterValues, -} from '../jobs/actions'; + import { TaskSpec } from './types'; import { ConflictError, NotFoundError } from '@backstage/backend-common'; +import { + getTemplaterKey, + joinGitUrlPath, + parseLocationAnnotation, + TemplaterValues, +} from '../stages'; -function templateEntityToSpec( +export function templateEntityToSpec( template: TemplateEntityV1alpha1, values: TemplaterValues, ): TaskSpec { const steps: TaskSpec['steps'] = []; - const { protocol, location: pullPath } = parseLocationAnnotation(template); + const { protocol, location } = parseLocationAnnotation(template); + + let url: string; + if (protocol === 'file') { + const path = resolvePath(location, template.spec.path || '.'); + + url = `file://${path}`; + } else { + url = joinGitUrlPath(location, template.spec.path); + } const templater = getTemplaterKey(template); steps.push({ @@ -41,7 +53,7 @@ function templateEntityToSpec( action: 'legacy:prepare', parameters: { protocol, - pullPath, + url, }, }); @@ -61,7 +73,6 @@ function templateEntityToSpec( action: 'publish', parameters: { values, - directory, }, }); @@ -72,7 +83,7 @@ type ActionContext = { logger: Logger; logStream: Writable; - workspaceDir: string; + workspacePath: string; parameters: { [name: string]: JsonValue }; output(name: string, value: JsonValue): void; }; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index 620b6e7243..496385d4ea 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { JsonObject } from '@backstage/config'; +import { JsonValue } from '@backstage/config'; export type Status = | 'open' @@ -50,7 +50,7 @@ export type TaskSpec = { id: string; name: string; action: string; - parameters?: JsonObject; + parameters?: { [name: string]: JsonValue }; }>; }; @@ -60,6 +60,8 @@ export type DispatchResult = { export interface Task { spec: TaskSpec; + taskId: string; + runId: string; emitLog(message: string): Promise; complete(result: CompletedTaskState): Promise; } diff --git a/plugins/scaffolder-backend/src/service/helpers.ts b/plugins/scaffolder-backend/src/service/helpers.ts new file mode 100644 index 0000000000..dd3d43c6d1 --- /dev/null +++ b/plugins/scaffolder-backend/src/service/helpers.ts @@ -0,0 +1,44 @@ +/* + * 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 os from 'os'; +import fs from 'fs-extra'; +import { Logger } from 'winston'; +import { Config } from '@backstage/config'; + +export async function getWorkingDirectory( + config: Config, + logger: Logger, +): Promise { + if (!config.has('backend.workingDirectory')) { + return os.tmpdir(); + } + + const workingDirectory = config.getString('backend.workingDirectory'); + try { + // Check if working directory exists and is writable + await fs.access(workingDirectory, fs.constants.F_OK | fs.constants.W_OK); + logger.info(`using working directory: ${workingDirectory}`); + } catch (err) { + logger.error( + `working directory ${workingDirectory} ${ + err.code === 'ENOENT' ? 'does not exist' : 'is not writable' + }`, + ); + throw err; + } + return workingDirectory; +} diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index fc40d45ce2..a72a5c5d48 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -38,8 +38,13 @@ import { MemoryDatabase, TaskWorker, } from '../scaffolder/tasks'; -import { TemplateActionRegistry } from '../scaffolder/tasks/TemplateConverter'; +import { + TemplateActionRegistry, + templateEntityToSpec, +} from '../scaffolder/tasks/TemplateConverter'; import { LOCATION_ANNOTATION } from '@backstage/catalog-model'; +import { registerLegacyActions } from '../scaffolder/stages/legacy'; +import { getWorkingDirectory } from './helpers'; export interface RouterOptions { preparers: PreparerBuilder; @@ -69,18 +74,24 @@ export async function createRouter( } = options; const logger = parentLogger.child({ plugin: 'scaffolder' }); + const workingDirectory = await getWorkingDirectory(config, logger); const jobProcessor = await JobProcessor.fromConfig({ config, logger }); const taskBroker = new MemoryTaskBroker(new MemoryDatabase()); + const actionRegistry = new TemplateActionRegistry(); const worker = new TaskWorker({ logger, taskBroker, - workingDirectory: 'todo', + actionRegistry, + workingDirectory, + }); + + registerLegacyActions(actionRegistry, { dockerClient, - entityClient, preparers, publishers, templaters, }); + worker.start(); router @@ -127,10 +138,8 @@ export async function createRouter( res.status(400).json({ errors: validationResult.errors }); return; } - const result = await taskBroker.dispatch({ - template, - values, - }); + const taskSpec = templateEntityToSpec(template, values); + const result = await taskBroker.dispatch(taskSpec); res.status(201).json({ id: result.taskId }); }) From abff060e67dbacacdf3343a20df927390fd8b802 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 29 Jan 2021 15:56:29 +0100 Subject: [PATCH 026/110] Use url from parameters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Patrik Oldsberg --- .../scaffolder-backend/src/scaffolder/stages/legacy.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts b/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts index 6244e40d68..a3fe01c96d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts @@ -40,17 +40,15 @@ export function registerLegacyActions( console.log(ctx); logger.info('Task claimed, waiting ...'); // Give us some time to curl observe - await new Promise(resolve => setTimeout(resolve, 5000)); + await new Promise(resolve => setTimeout(resolve, 1000)); logger.info('Prepare the skeleton'); - - const { protocol, pullPath } = ctx.parameters; - const url = pullPath as string; + const { protocol, url } = ctx.parameters; const preparer = - protocol === 'file' ? new FilePreparer() : preparers.get(url); + protocol === 'file' ? new FilePreparer() : preparers.get(url as string); await preparer.prepare({ - url, + url: url as string, logger: ctx.logger, workspacePath: ctx.workspacePath, }); From 7b7230f75bdd1d7a4c7513f11e9451b58f1f41f1 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 29 Jan 2021 15:57:37 +0100 Subject: [PATCH 027/110] Add getWorkspaceName --- .../src/scaffolder/tasks/MemoryTaskBroker.ts | 8 +-- .../src/scaffolder/tasks/TaskWorker.ts | 54 +++---------------- .../src/scaffolder/tasks/types.ts | 3 +- 3 files changed, 10 insertions(+), 55 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts index c5920fe836..6937c535dc 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts @@ -43,12 +43,8 @@ export class TaskAgent implements Task { return this.state.spec; } - get runId() { - return this.state.runId; - } - - get taskId() { - return this.state.taskId; + async getWorkspaceName() { + return `${this.state.taskId}_${this.state.runId}`; } async emitLog(message: string): Promise { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 6eb1c4763c..c80a1b81bf 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -19,9 +19,9 @@ import { Logger } from 'winston'; import * as winston from 'winston'; import { JsonValue } from '@backstage/config'; import { TaskBroker, Task } from './types'; -import { TemplateActionRegistry } from './TemplateConverter'; import fs from 'fs-extra'; import path from 'path'; +import { TemplateActionRegistry } from './TemplateConverter'; type Options = { logger: Logger; @@ -46,9 +46,11 @@ export class TaskWorker { try { const { actionRegistry, logger } = this.options; - // bbl LUUUNCH - // taskID and runId not part of task? O_o - const workspacePath = await this.createWorkPath(task.taskId, task.runId); + const workspacePath = path.join( + this.options.workingDirectory, + await task.getWorkspaceName(), + ); + await fs.ensureDir(workspacePath); const taskLogger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', @@ -99,55 +101,13 @@ export class TaskWorker { task.emitLog(`Finished step ${step.name}`); } - // const { values, template } = task.spec; - // task.emitLog('Prepare the skeleton'); - // const { protocol, location: pullPath } = parseLocationAnnotation( - // task.spec.template, - // ); - - // const preparer = - // protocol === 'file' ? new FilePreparer() : preparers.get(pullPath); - // const templater = templaters.get(template); - // const publisher = publishers.get(values.storePath); - - // const skeletonDir = await preparer.prepare(task.spec.template, { - // logger: taskLogger, - // workingDirectory: workingDirectory, - // }); - - // task.emitLog('Run the templater'); - // const { resultDir } = await templater.run({ - // directory: skeletonDir, - // dockerClient, - // logStream: stream, - // values: values, - // }); - - // task.emitLog('Publish template'); - // logger.info('Will now store the template'); - logger.info('So done right now'); await new Promise(resolve => setTimeout(resolve, 5000)); - // const result = await publisher.publish({ - // values: values, - // directory: resultDir, - // logger, - // }); - // task.emitLog(`Result: ${JSON.stringify(result)}`); await task.complete('completed'); } catch (error) { + task.emitLog(error); await task.complete('failed'); } } - - async createWorkPath(taskId: string, runId: string): Promise { - const workspacePath = path.join( - this.options.workingDirectory, - taskId, - runId, - ); - fs.ensureDir(workspacePath); - return workspacePath; - } } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index 496385d4ea..d3c08085fd 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -60,10 +60,9 @@ export type DispatchResult = { export interface Task { spec: TaskSpec; - taskId: string; - runId: string; emitLog(message: string): Promise; complete(result: CompletedTaskState): Promise; + getWorkspaceName(): Promise; } export interface TaskBroker { From 95c2719809adec52fb1979757559e1172e1e3073 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 29 Jan 2021 15:58:20 +0100 Subject: [PATCH 028/110] Rename publish to legacy:publish --- .../src/scaffolder/tasks/TemplateConverter.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts index bd06dbaa91..86f722e475 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts @@ -70,7 +70,7 @@ export function templateEntityToSpec( steps.push({ id: 'publish', name: 'Publishing', - action: 'publish', + action: 'legacy:publish', parameters: { values, }, @@ -105,10 +105,6 @@ export class TemplateActionRegistry { this.actions.set(action.id, action); } - // validate - // ensure that action exist. - // template variables exist. - get(actionId: string): TemplateAction { const action = this.actions.get(actionId); if (!action) { From 605eb7f59401429fa5ab48ec83f4acf0a313dbfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 20 Jan 2021 15:36:36 +0100 Subject: [PATCH 029/110] scaffolder: add the bare minimum of a db abstraction --- plugins/scaffolder-backend/knexfile.js | 25 +++++ .../migrations/20210120143715_init.js | 100 ++++++++++++++++++ plugins/scaffolder-backend/package.json | 3 +- .../scaffolder-backend/src/tasks/Database.ts | 70 ++++++++++++ plugins/scaffolder-backend/src/tasks/types.ts | 37 +++++++ 5 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 plugins/scaffolder-backend/knexfile.js create mode 100644 plugins/scaffolder-backend/migrations/20210120143715_init.js create mode 100644 plugins/scaffolder-backend/src/tasks/Database.ts create mode 100644 plugins/scaffolder-backend/src/tasks/types.ts diff --git a/plugins/scaffolder-backend/knexfile.js b/plugins/scaffolder-backend/knexfile.js new file mode 100644 index 0000000000..f469df4c08 --- /dev/null +++ b/plugins/scaffolder-backend/knexfile.js @@ -0,0 +1,25 @@ +/* + * 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. + */ + +module.exports = { + development: { + client: 'sqlite3', + connection: ':memory:', + migrations: { + directory: 'migrations', + }, + }, +}; diff --git a/plugins/scaffolder-backend/migrations/20210120143715_init.js b/plugins/scaffolder-backend/migrations/20210120143715_init.js new file mode 100644 index 0000000000..d2535bfa86 --- /dev/null +++ b/plugins/scaffolder-backend/migrations/20210120143715_init.js @@ -0,0 +1,100 @@ +/* + * Copyright 2021 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. + */ + +// @ts-check + +/** + * @param {import('knex')} knex + */ +exports.up = async function up(knex) { + await knex.schema.createTable('tasks', table => { + table.comment('The table of scaffolder tasks'); + table.uuid('id').primary().notNullable().comment('The ID of the task'); + table + .text('context') + .notNullable() + .comment('A JSON object with task specific context information'); + table + .text('spec') + .notNullable() + .comment('A JSON encoded task specification'); + table + .text('status') + .notNullable() + .comment('The current status of the task'); + table + .integer('run_id') + .nullable() + .comment('The current run ID of the task'); + table + .dateTime('created_at') + .notNullable() + .comment('The timestamp when this task was created'); + table + .dateTime('last_heartbeat_at') + .nullable() + .comment('The last timestamp when a heartbeat was received'); + table + .integer('retry_count') + .notNullable() + .defaultTo(0) + .comment('The number of times that this task has been attempted'); + }); + await knex.schema.createTable('task_events', table => { + table.comment('The event stream a given task'); + table + .bigIncrements('id') + .primary() + .notNullable() + .comment('The ID of the event'); + table + .uuid('task_id') + .references('id') + .inTable('tasks') + .notNullable() + .onDelete('CASCADE') + .comment('The task that generated the event'); + table + .integer('run_id') + .nullable() + .comment('The run ID of the task that this event applies to'); + table + .text('stage_name') + .nullable() + .comment('The stage of the task that this event applies to'); + table.text('event_type').notNullable().comment('The type of event'); + table + .dateTime('created_at') + .defaultTo(knex.fn.now()) + .notNullable() + .comment('The timestamp when this event was generated'); + table.text('text').notNullable().comment('The text of the event'); + table.index(['task_id'], 'task_events_task_id_idx'); + }); +}; + +/** + * @param {import('knex')} knex + */ +exports.down = async function down(knex) { + if (knex.client.config.client !== 'sqlite3') { + await knex.schema.alterTable('task_events', table => { + table.dropIndex([], 'task_events_task_id_idx'); + }); + } + await knex.schema.dropTable('task_events'); + await knex.schema.dropTable('tasks'); +}; diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 97ab723e81..5d024c2798 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -53,7 +53,7 @@ "helmet": "^4.0.0", "isomorphic-git": "^1.8.0", "jsonschema": "^1.2.6", - "luxon": "^1.25.0", + "knex": "^0.21.6", "morgan": "^1.10.0", "p-queue": "^6.3.0", "uuid": "^8.2.0", @@ -73,6 +73,7 @@ }, "files": [ "dist", + "migrations", "config.d.ts" ], "configSchema": "config.d.ts" diff --git a/plugins/scaffolder-backend/src/tasks/Database.ts b/plugins/scaffolder-backend/src/tasks/Database.ts new file mode 100644 index 0000000000..c135380f87 --- /dev/null +++ b/plugins/scaffolder-backend/src/tasks/Database.ts @@ -0,0 +1,70 @@ +/* + * Copyright 2021 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 { ConflictError, resolvePackagePath } from '@backstage/backend-common'; +import Knex from 'knex'; +import { Logger } from 'winston'; +import { Database, Transaction } from './types'; + +const migrationsDir = resolvePackagePath( + '@backstage/plugin-scaffolder-backend', + 'migrations', +); + +export class CommonDatabase implements Database { + static async create(knex: Knex, logger: Logger): Promise { + await knex.migrate.latest({ + directory: migrationsDir, + }); + return new CommonDatabase(knex, logger); + } + + constructor( + private readonly database: Knex, + private readonly logger: Logger, + ) {} + + async transaction(fn: (tx: Transaction) => Promise): Promise { + try { + let result: T | undefined = undefined; + + await this.database.transaction( + async tx => { + // We can't return here, as knex swallows the return type in case the transaction is rolled back: + // https://github.com/knex/knex/blob/e37aeaa31c8ef9c1b07d2e4d3ec6607e557d800d/lib/transaction.js#L136 + result = await fn(tx); + }, + { + // If we explicitly trigger a rollback, don't fail. + doNotRejectOnRollback: true, + }, + ); + + return result!; + } catch (e) { + this.logger.debug(`Error during transaction, ${e}`); + + if ( + /SQLITE_CONSTRAINT: UNIQUE/.test(e.message) || + /unique constraint/.test(e.message) + ) { + throw new ConflictError(`Rejected due to a conflicting entity`, e); + } + + throw e; + } + } +} diff --git a/plugins/scaffolder-backend/src/tasks/types.ts b/plugins/scaffolder-backend/src/tasks/types.ts new file mode 100644 index 0000000000..27cbe7f7f1 --- /dev/null +++ b/plugins/scaffolder-backend/src/tasks/types.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2021 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. + */ + +/** + * The core database implementation. + */ +export interface Database { + /** + * Runs a transaction. + * + * The callback is expected to make calls back into this class. When it + * completes, the transaction is closed. + * + * @param fn The callback that implements the transaction + */ + transaction(fn: (tx: Transaction) => Promise): Promise; +} + +/** + * An abstraction for transactions of the underlying database technology. + */ +export type Transaction = { + rollback(): Promise; +}; From 71dc78b01ff6d64c644da18986be7b5d450b8042 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 1 Feb 2021 14:06:09 +0100 Subject: [PATCH 030/110] Wire up database and rename database classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Patrik Oldsberg --- packages/backend/src/plugins/scaffolder.ts | 2 + .../backend/src/plugins/scaffolder.ts | 2 + .../migrations/20210120143715_init.js | 14 +- .../src/scaffolder/tasks/DatabaseTaskStore.ts | 248 ++++++++++++++++++ ...tabase.test.ts => MemoryTaskStore.test.ts} | 0 .../{MemoryDatabase.ts => MemoryTaskStore.ts} | 44 +--- ...oker.test.ts => StorageTaskBroker.test.ts} | 12 +- ...moryTaskBroker.ts => StorageTaskBroker.ts} | 18 +- .../src/scaffolder/tasks/TaskWorker.ts | 3 +- .../src/scaffolder/tasks/index.ts | 5 +- .../src/scaffolder/tasks/types.ts | 30 ++- .../scaffolder-backend/src/service/router.ts | 14 +- 12 files changed, 328 insertions(+), 64 deletions(-) create mode 100644 plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts rename plugins/scaffolder-backend/src/scaffolder/tasks/{MemoryDatabase.test.ts => MemoryTaskStore.test.ts} (100%) rename plugins/scaffolder-backend/src/scaffolder/tasks/{MemoryDatabase.ts => MemoryTaskStore.ts} (73%) rename plugins/scaffolder-backend/src/scaffolder/tasks/{MemoryTaskBroker.test.ts => StorageTaskBroker.test.ts} (90%) rename plugins/scaffolder-backend/src/scaffolder/tasks/{MemoryTaskBroker.ts => StorageTaskBroker.ts} (90%) diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts index 4e2257a46c..723165ff82 100644 --- a/packages/backend/src/plugins/scaffolder.ts +++ b/packages/backend/src/plugins/scaffolder.ts @@ -30,6 +30,7 @@ import Docker from 'dockerode'; export default async function createPlugin({ logger, config, + database, }: PluginEnvironment) { const cookiecutterTemplater = new CookieCutter(); const craTemplater = new CreateReactAppTemplater(); @@ -54,5 +55,6 @@ export default async function createPlugin({ config, dockerClient, entityClient, + database, }); } diff --git a/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts b/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts index c8bd3e5012..d68f90ce08 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts @@ -14,6 +14,7 @@ import Docker from 'dockerode'; export default async function createPlugin({ logger, config, + database, }: PluginEnvironment) { const cookiecutterTemplater = new CookieCutter(); const craTemplater = new CreateReactAppTemplater(); @@ -38,5 +39,6 @@ export default async function createPlugin({ config, dockerClient, entityClient, + database, }); } diff --git a/plugins/scaffolder-backend/migrations/20210120143715_init.js b/plugins/scaffolder-backend/migrations/20210120143715_init.js index d2535bfa86..bb65e1e2fb 100644 --- a/plugins/scaffolder-backend/migrations/20210120143715_init.js +++ b/plugins/scaffolder-backend/migrations/20210120143715_init.js @@ -23,10 +23,6 @@ exports.up = async function up(knex) { await knex.schema.createTable('tasks', table => { table.comment('The table of scaffolder tasks'); table.uuid('id').primary().notNullable().comment('The ID of the task'); - table - .text('context') - .notNullable() - .comment('A JSON object with task specific context information'); table .text('spec') .notNullable() @@ -41,6 +37,7 @@ exports.up = async function up(knex) { .comment('The current run ID of the task'); table .dateTime('created_at') + .defaultTo(knex.fn.now()) .notNullable() .comment('The timestamp when this task was created'); table @@ -53,6 +50,7 @@ exports.up = async function up(knex) { .defaultTo(0) .comment('The number of times that this task has been attempted'); }); + await knex.schema.createTable('task_events', table => { table.comment('The event stream a given task'); table @@ -72,16 +70,16 @@ exports.up = async function up(knex) { .nullable() .comment('The run ID of the task that this event applies to'); table - .text('stage_name') - .nullable() - .comment('The stage of the task that this event applies to'); + .text('body') + .notNullable() + .comment('The JSON encoded body of the event'); table.text('event_type').notNullable().comment('The type of event'); table .dateTime('created_at') .defaultTo(knex.fn.now()) .notNullable() .comment('The timestamp when this event was generated'); - table.text('text').notNullable().comment('The text of the event'); + table.index(['task_id'], 'task_events_task_id_idx'); }); }; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts new file mode 100644 index 0000000000..e6de1735d8 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -0,0 +1,248 @@ +/* + * Copyright 2021 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 { JsonObject } from '@backstage/config'; +import { + ConflictError, + NotFoundError, + resolvePackagePath, +} from '@backstage/backend-common'; +import Knex, { Transaction } from 'knex'; +import { Logger } from 'winston'; +import { v4 as uuid } from 'uuid'; +import { + DbTaskEventRow, + DbTaskRow, + Status, + TaskEventType, + TaskSpec, + TaskStore, + TaskStoreEmitOptions, + TaskStoreGetEventsOptions, +} from './types'; + +const migrationsDir = resolvePackagePath( + '@backstage/plugin-scaffolder-backend', + 'migrations', +); + +export type RawDbTaskRow = { + id: string; + spec: string; + status: Status; + last_heartbeat_at?: string; + retry_count: number; + created_at: string; + run_id?: string; +}; + +export type RawDbTaskEventRow = { + id: number; + run_id: string; + task_id: string; + body: string; + event_type: TaskEventType; + created_at: string; +}; + +export class DatabaseTaskStore implements TaskStore { + static async create(knex: Knex): Promise { + await knex.migrate.latest({ + directory: migrationsDir, + }); + return new DatabaseTaskStore(knex); + } + + constructor(private readonly db: Knex) {} + + async get(taskId: string): Promise { + const [result] = await this.db('tasks') + .where({ id: taskId }) + .select(); + if (!result) { + throw new NotFoundError(`No task with id '${taskId}' found`); + } + try { + const spec = JSON.parse(result.spec); + return { + id: result.id, + spec, + status: result.status, + lastHeartbeat: result.last_heartbeat_at, + retryCount: result.retry_count, + createdAt: result.created_at, + runId: result.run_id, + }; + } catch (error) { + throw new Error(`Failed to parse spec of task '${taskId}', ${error}`); + } + } + + async createTask(spec: TaskSpec): Promise<{ taskId: string }> { + const taskId = uuid(); + await this.db('tasks').insert({ + id: taskId, + spec: JSON.stringify(spec), + status: 'open', + retry_count: 0, + }); + return { taskId }; + } + + async claimTask(): Promise { + return this.db.transaction(async tx => { + const [task] = await tx('tasks') + .where({ + status: 'open', + }) + .limit(1) + .select(); + + if (!task) { + return undefined; + } + + const runId = uuid(); + const updateCount = await tx('tasks') + .where({ id: task.id, status: 'open' }) + .update({ + status: 'processing', + run_id: runId, + }); + + if (updateCount < 1) { + return undefined; + } + + try { + const spec = JSON.parse(task.spec); + return { + id: task.id, + spec, + status: 'processing', + lastHeartbeat: task.last_heartbeat_at, + retryCount: task.retry_count, + createdAt: task.created_at, + runId: runId, + }; + } catch (error) { + throw new Error(`Failed to parse spec of task '${task.id}', ${error}`); + } + }); + } + + async heartbeat(runId: string): Promise { + const updateCount = await this.db('tasks') + .where({ run_id: runId, status: 'processing' }) + .update({ + last_heartbeat_at: this.db.fn.now(), + }); + if (updateCount === 0) { + throw new Error(`No running task with runId ${runId} found`); + } + } + + async setStatus(runId: string, status: Status): Promise { + let oldStatus: string; + if (status === 'failed' || status === 'completed') { + oldStatus = 'processing'; + } else { + throw new Error( + `Invalid status update of run '${runId}' to status '${status}'`, + ); + } + await this.db.transaction(async tx => { + const [task] = await tx('tasks') + .where({ + run_id: runId, + }) + .limit(1) + .select(); + + if (!task) { + throw new Error(`No task with runId ${runId} found`); + } + if (task.status !== oldStatus) { + throw new ConflictError( + `Refusing to update status of run '${runId}' to status '${status}' ` + + `as it is currently '${task.status}', expected '${oldStatus}'`, + ); + } + const updateCount = await tx('tasks') + .where({ + run_id: runId, + status: oldStatus, + }) + .update({ + status, + }); + if (updateCount !== 1) { + throw new Error( + `Failed to update status to '${status}' for runId ${runId}`, + ); + } + }); + } + + async emit({ + taskId, + runId, + body, + type, + }: TaskStoreEmitOptions): Promise { + const serliazedBody = JSON.stringify(body); + await this.db('task_events').insert({ + task_id: taskId, + run_id: runId, + event_type: type, + body: serliazedBody, + }); + } + + async getEvents({ + taskId, + after, + }: TaskStoreGetEventsOptions): Promise<{ events: DbTaskEventRow[] }> { + let query = this.db('task_events').where({ + task_id: taskId, + }); + if (typeof after === 'number') { + query = query + .where('task_events.id', '>', after) + .orWhere({ event_type: 'completion' }); + } + + const rawEvents = await query.select(); + const events = rawEvents.map(event => { + try { + const body = JSON.parse(event.body) as JsonObject; + return { + id: event.id, + runId: event.run_id, + taskId: event.task_id, + body, + type: event.event_type, + createdAt: event.created_at, + }; + } catch (error) { + throw new Error( + `Failed to parse event body from event taskId=${taskId} id=${event.id}, ${error}`, + ); + } + }); + return { events }; + } +} diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryDatabase.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskStore.test.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/tasks/MemoryDatabase.test.ts rename to plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskStore.test.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryDatabase.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskStore.ts similarity index 73% rename from plugins/scaffolder-backend/src/scaffolder/tasks/MemoryDatabase.ts rename to plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskStore.ts index b2d1cf667b..e181edf524 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryDatabase.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskStore.ts @@ -19,35 +19,17 @@ import { DbTaskEventRow, Status, TaskSpec, - TaskEventType, + TaskStore, + TaskStoreGetEventsOptions, + TaskStoreEmitOptions, } from './types'; import { v4 as uuid } from 'uuid'; -export interface Database { - get(taskId: string): Promise; - createTask(task: TaskSpec): Promise; - claimTask(): Promise; - heartbeat(runId: string): Promise; - setStatus(taskId: string, status: Status): Promise; -} - -type EmitOptions = { - taskId: string; - runId: string; - body: string; - type: TaskEventType; -}; - -type ReadOptions = { - taskId: string; - after?: number | undefined; -}; - -export class MemoryDatabase implements Database { +export class MemoryTaskStore implements TaskStore { private readonly store = new Map(); private readonly events = new Array(); - async emit({ taskId, runId, body, type }: EmitOptions) { + async emit({ taskId, runId, body, type }: TaskStoreEmitOptions) { this.events.push({ id: this.events.length, taskId, @@ -61,7 +43,7 @@ export class MemoryDatabase implements Database { async getEvents({ taskId, after, - }: ReadOptions): Promise<{ events: DbTaskEventRow[] }> { + }: TaskStoreGetEventsOptions): Promise<{ events: DbTaskEventRow[] }> { const events = this.events.filter(event => { if (event.taskId !== taskId) { return false; @@ -89,7 +71,7 @@ export class MemoryDatabase implements Database { throw new Error('No task with matching runId found'); } - this.store.set(task.taskId, { + this.store.set(task.id, { ...task, lastHeartbeat: new Date().toISOString(), }); @@ -103,23 +85,23 @@ export class MemoryDatabase implements Database { status: 'processing', runId: uuid(), }; - this.store.set(t.taskId, task); + this.store.set(t.id, task); return task; } } return undefined; } - async createTask(spec: TaskSpec): Promise { + async createTask(spec: TaskSpec): Promise<{ taskId: string }> { const taskRow = { - taskId: uuid(), + id: uuid(), spec, status: 'open' as Status, retryCount: 0, createdAt: new Date().toISOString(), }; - this.store.set(taskRow.taskId, taskRow); - return taskRow; + this.store.set(taskRow.id, taskRow); + return { taskId: taskRow.id }; } async get(taskId: string): Promise { @@ -135,6 +117,6 @@ export class MemoryDatabase implements Database { if (!task) { throw new Error(`no task found`); } - this.store.set(task.taskId, { ...task, status }); + this.store.set(task.id, { ...task, status }); } } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts similarity index 90% rename from plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.test.ts rename to plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts index 686a6d9132..5bbe91ea87 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts @@ -15,13 +15,13 @@ */ import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; -import { TemplaterValues } from './actions/templater/types'; -import { MemoryDatabase } from './MemoryDatabase'; -import { MemoryTaskBroker, TaskAgent } from './MemoryTaskBroker'; +import { TemplaterValues } from '../stages'; +import { MemoryTaskStore } from './MemoryTaskStore'; +import { StorageTaskBroker, TaskAgent } from './StorageTaskBroker'; -describe('MemoryTaskBroker', () => { - const storage = new MemoryDatabase(); - const broker = new MemoryTaskBroker(storage); +describe('StorageTaskBroker', () => { + const storage = new MemoryTaskStore(); + const broker = new StorageTaskBroker(storage); const taskSpec = { values: {} as TemplaterValues, diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts similarity index 90% rename from plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts rename to plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 6937c535dc..6eedc023f8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -18,16 +18,16 @@ import { CompletedTaskState, Task, TaskSpec, + TaskStore, TaskBroker, DispatchResult, DbTaskEventRow, } from './types'; -import { MemoryDatabase } from './MemoryDatabase'; export class TaskAgent implements Task { private heartbeartInterval?: ReturnType; - static create(state: TaskState, storage: MemoryDatabase) { + static create(state: TaskState, storage: TaskStore) { const agent = new TaskAgent(state, storage); agent.start(); return agent; @@ -36,7 +36,7 @@ export class TaskAgent implements Task { // Runs heartbeat internally private constructor( private readonly state: TaskState, - private readonly storage: MemoryDatabase, + private readonly storage: TaskStore, ) {} get spec() { @@ -51,20 +51,20 @@ export class TaskAgent implements Task { await this.storage.emit({ taskId: this.state.taskId, runId: this.state.runId, - body: message, + body: { message }, type: 'log', }); } async complete(result: CompletedTaskState): Promise { await this.storage.setStatus( - this.state.taskId, + this.state.runId, result === 'failed' ? 'failed' : 'completed', ); this.storage.emit({ taskId: this.state.taskId, runId: this.state.runId, - body: `Run completed with status: ${result}`, + body: { message: `Run completed with status: ${result}` }, type: 'completion', }); if (this.heartbeartInterval) { @@ -96,8 +96,8 @@ function defer() { return { promise, resolve }; } -export class MemoryTaskBroker implements TaskBroker { - constructor(private readonly storage: MemoryDatabase) {} +export class StorageTaskBroker implements TaskBroker { + constructor(private readonly storage: TaskStore) {} private deferredDispatch = defer(); async claim(): Promise { @@ -107,7 +107,7 @@ export class MemoryTaskBroker implements TaskBroker { return TaskAgent.create( { runId: pendingTask.runId!, - taskId: pendingTask.taskId, + taskId: pendingTask.id, spec: pendingTask.spec, }, this.storage, diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index c80a1b81bf..64cfdc6f96 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -73,6 +73,7 @@ export class TaskWorker { // Give us some time to curl observe task.emitLog('Task claimed, waiting ...'); await new Promise(resolve => setTimeout(resolve, 5000)); + console.log('DEBUG: task.spec =', JSON.stringify(task.spec, null, 2)); task.emitLog(`Starting up work with ${task.spec.steps.length} steps`); const outputs: { [name: string]: JsonValue } = {}; @@ -106,7 +107,7 @@ export class TaskWorker { await task.complete('completed'); } catch (error) { - task.emitLog(error); + task.emitLog(String(error.stack)); await task.complete('failed'); } } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts index 1cc9842fed..7b6552a3e0 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts @@ -14,6 +14,7 @@ * limitations under the License. */ -export { MemoryDatabase } from './MemoryDatabase'; -export { MemoryTaskBroker } from './MemoryTaskBroker'; +export { MemoryTaskStore } from './MemoryTaskStore'; +export { DatabaseTaskStore } from './DatabaseTaskStore'; +export { StorageTaskBroker } from './StorageTaskBroker'; export { TaskWorker } from './TaskWorker'; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index d3c08085fd..f9081af6b6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { JsonValue } from '@backstage/config'; +import { JsonValue, JsonObject } from '@backstage/config'; export type Status = | 'open' @@ -26,7 +26,7 @@ export type Status = export type CompletedTaskState = 'failed' | 'completed'; export type DbTaskRow = { - taskId: string; + id: string; spec: TaskSpec; status: Status; lastHeartbeat?: string; @@ -40,7 +40,7 @@ export type DbTaskEventRow = { id: number; runId: string; taskId: string; - body: string; + body: JsonObject; type: TaskEventType; createdAt: string; }; @@ -69,3 +69,27 @@ export interface TaskBroker { claim(): Promise; dispatch(spec: TaskSpec): Promise; } + +export type TaskStoreEmitOptions = { + taskId: string; + runId: string; + body: JsonObject; + type: TaskEventType; +}; + +export type TaskStoreGetEventsOptions = { + taskId: string; + after?: number | undefined; +}; +export interface TaskStore { + get(taskId: string): Promise; + createTask(task: TaskSpec): Promise<{ taskId: string }>; + claimTask(): Promise; + heartbeat(runId: string): Promise; + setStatus(runId: string, status: Status): Promise; + emit({ taskId, runId, body, type }: TaskStoreEmitOptions): Promise; + getEvents({ + taskId, + after, + }: TaskStoreGetEventsOptions): Promise<{ events: DbTaskEventRow[] }>; +} diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index a72a5c5d48..23af538df8 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -34,17 +34,17 @@ import { CatalogEntityClient } from '../lib/catalog'; import { validate, ValidatorResult } from 'jsonschema'; import parseGitUrl from 'git-url-parse'; import { - MemoryTaskBroker, - MemoryDatabase, + DatabaseTaskStore, + StorageTaskBroker, TaskWorker, } from '../scaffolder/tasks'; import { TemplateActionRegistry, templateEntityToSpec, } from '../scaffolder/tasks/TemplateConverter'; -import { LOCATION_ANNOTATION } from '@backstage/catalog-model'; import { registerLegacyActions } from '../scaffolder/stages/legacy'; import { getWorkingDirectory } from './helpers'; +import { PluginDatabaseManager } from '@backstage/backend-common'; export interface RouterOptions { preparers: PreparerBuilder; @@ -55,6 +55,7 @@ export interface RouterOptions { config: Config; dockerClient: Docker; entityClient: CatalogEntityClient; + database: PluginDatabaseManager; } export async function createRouter( @@ -71,12 +72,17 @@ export async function createRouter( config, dockerClient, entityClient, + database, } = options; const logger = parentLogger.child({ plugin: 'scaffolder' }); const workingDirectory = await getWorkingDirectory(config, logger); const jobProcessor = await JobProcessor.fromConfig({ config, logger }); - const taskBroker = new MemoryTaskBroker(new MemoryDatabase()); + + const databaseTaskStore = await DatabaseTaskStore.create( + await database.getClient(), + ); + const taskBroker = new StorageTaskBroker(databaseTaskStore); const actionRegistry = new TemplateActionRegistry(); const worker = new TaskWorker({ logger, From 5da5e6b2243516a448fbbe88fd806b550ba2f0ed Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 1 Feb 2021 14:07:19 +0100 Subject: [PATCH 031/110] Delete MemoryTaskStore This is replaced by running sqlite in memory. --- .../scaffolder/tasks/MemoryTaskStore.test.ts | 21 --- .../src/scaffolder/tasks/MemoryTaskStore.ts | 122 ------------------ .../src/scaffolder/tasks/index.ts | 1 - 3 files changed, 144 deletions(-) delete mode 100644 plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskStore.test.ts delete mode 100644 plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskStore.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskStore.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskStore.test.ts deleted file mode 100644 index c5522cb38a..0000000000 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskStore.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2021 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. - */ - -describe('MemoryDatabase', () => { - it('should be tested', async () => { - expect(1).toBe(2); - }); -}); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskStore.ts deleted file mode 100644 index e181edf524..0000000000 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/MemoryTaskStore.ts +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2021 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 { - DbTaskRow, - DbTaskEventRow, - Status, - TaskSpec, - TaskStore, - TaskStoreGetEventsOptions, - TaskStoreEmitOptions, -} from './types'; -import { v4 as uuid } from 'uuid'; - -export class MemoryTaskStore implements TaskStore { - private readonly store = new Map(); - private readonly events = new Array(); - - async emit({ taskId, runId, body, type }: TaskStoreEmitOptions) { - this.events.push({ - id: this.events.length, - taskId, - runId, - body, - type, - createdAt: new Date().toISOString(), - }); - } - - async getEvents({ - taskId, - after, - }: TaskStoreGetEventsOptions): Promise<{ events: DbTaskEventRow[] }> { - const events = this.events.filter(event => { - if (event.taskId !== taskId) { - return false; - } - if (after !== undefined) { - if (event.id <= after) { - return false; - } - } - return true; - }); - return { events }; - } - - async heartbeat(runId: string): Promise { - let task: DbTaskRow | undefined; - - for (const t of this.store.values()) { - if (t.runId === runId) { - task = t; - } - } - - if (!task) { - throw new Error('No task with matching runId found'); - } - - this.store.set(task.id, { - ...task, - lastHeartbeat: new Date().toISOString(), - }); - } - - async claimTask(): Promise { - for (const t of this.store.values()) { - if (t.status === 'open') { - const task: DbTaskRow = { - ...t, - status: 'processing', - runId: uuid(), - }; - this.store.set(t.id, task); - return task; - } - } - return undefined; - } - - async createTask(spec: TaskSpec): Promise<{ taskId: string }> { - const taskRow = { - id: uuid(), - spec, - status: 'open' as Status, - retryCount: 0, - createdAt: new Date().toISOString(), - }; - this.store.set(taskRow.id, taskRow); - return { taskId: taskRow.id }; - } - - async get(taskId: string): Promise { - const task = this.store.get(taskId); - if (task) { - return task; - } - throw new Error(`could not found task ${taskId}`); - } - - async setStatus(taskId: string, status: Status): Promise { - const task = this.store.get(taskId); - if (!task) { - throw new Error(`no task found`); - } - this.store.set(task.id, { ...task, status }); - } -} diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts index 7b6552a3e0..c85135426e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -export { MemoryTaskStore } from './MemoryTaskStore'; export { DatabaseTaskStore } from './DatabaseTaskStore'; export { StorageTaskBroker } from './StorageTaskBroker'; export { TaskWorker } from './TaskWorker'; From 88c31ab16ba778b43c5c0e6fb6aad4a3f68cf8c0 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 2 Feb 2021 09:27:18 +0100 Subject: [PATCH 032/110] Add tests. Extend storage with fetch staletasks method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Patrik Oldsberg --- plugins/scaffolder-backend/package.json | 3 +- .../src/scaffolder/tasks/DatabaseTaskStore.ts | 35 +++-- .../tasks/StorageTaskBroker.test.ts | 145 ++++++++++++++---- .../src/scaffolder/tasks/StorageTaskBroker.ts | 17 +- .../src/scaffolder/tasks/types.ts | 1 + .../src/service/router.test.ts | 25 ++- .../scaffolder-backend/src/service/router.ts | 6 +- 7 files changed, 182 insertions(+), 50 deletions(-) diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 5d024c2798..bdecf6196e 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -58,7 +58,8 @@ "p-queue": "^6.3.0", "uuid": "^8.2.0", "winston": "^3.2.1", - "yaml": "^1.10.0" + "yaml": "^1.10.0", + "luxon": "^1.25.0" }, "devDependencies": { "@backstage/cli": "^0.5.0", diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index e6de1735d8..1d99cd2958 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -20,8 +20,7 @@ import { NotFoundError, resolvePackagePath, } from '@backstage/backend-common'; -import Knex, { Transaction } from 'knex'; -import { Logger } from 'winston'; +import Knex from 'knex'; import { v4 as uuid } from 'uuid'; import { DbTaskEventRow, @@ -121,6 +120,7 @@ export class DatabaseTaskStore implements TaskStore { .update({ status: 'processing', run_id: runId, + last_heartbeat_at: this.db.fn.now(), }); if (updateCount < 1) { @@ -155,6 +155,18 @@ export class DatabaseTaskStore implements TaskStore { } } + async listStaleTasks(): Promise<{ tasks: DbTaskRow }> { + const rows = await this.db('tasks') + .where('status', 'processing') + .andWhere( + 'last_heartbeat_at', + '<', + this.db.type === 'sqlite' + ? this.db.raw("datetime('now', '-2 seconds')") + : this.db.raw("dateadd('second', -2, now())"), + ); + } + async setStatus(runId: string, status: Status): Promise { let oldStatus: string; if (status === 'failed' || status === 'completed') { @@ -216,16 +228,17 @@ export class DatabaseTaskStore implements TaskStore { taskId, after, }: TaskStoreGetEventsOptions): Promise<{ events: DbTaskEventRow[] }> { - let query = this.db('task_events').where({ - task_id: taskId, - }); - if (typeof after === 'number') { - query = query - .where('task_events.id', '>', after) - .orWhere({ event_type: 'completion' }); - } + const rawEvents = await this.db('task_events') + .where({ + task_id: taskId, + }) + .andWhere(builder => { + if (typeof after === 'number') { + builder.where('id', '>', after).orWhere('event_type', 'completion'); + } + }) + .select(); - const rawEvents = await query.select(); const events = rawEvents.map(event => { try { const body = JSON.parse(event.body) as JsonObject; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts index 5bbe91ea87..ebbac1db5f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts @@ -14,47 +14,54 @@ * limitations under the License. */ -import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; -import { TemplaterValues } from '../stages'; -import { MemoryTaskStore } from './MemoryTaskStore'; +import { SingleConnectionDatabaseManager } from '@backstage/backend-common'; +import { ConfigReader } from '@backstage/config'; +import { DatabaseTaskStore } from './DatabaseTaskStore'; import { StorageTaskBroker, TaskAgent } from './StorageTaskBroker'; +import { TaskStore, TaskSpec, DbTaskEventRow } from './types'; + +async function createStore(): Promise { + const manager = SingleConnectionDatabaseManager.fromConfig( + new ConfigReader({ + backend: { + database: { + client: 'sqlite3', + connection: ':memory:', + }, + }, + }), + ).forPlugin('scaffolder'); + return await DatabaseTaskStore.create(await manager.getClient()); +} describe('StorageTaskBroker', () => { - const storage = new MemoryTaskStore(); - const broker = new StorageTaskBroker(storage); + let storage: TaskStore; - const taskSpec = { - values: {} as TemplaterValues, - template: {} as TemplateEntityV1alpha1, - }; + beforeAll(async () => { + storage = await createStore(); + }); it('should claim a dispatched work item', async () => { - await broker.dispatch(taskSpec); + const broker = new StorageTaskBroker(storage); + await broker.dispatch({ steps: [] }); await expect(broker.claim()).resolves.toEqual(expect.any(TaskAgent)); }); it('should wait for a dispatched work item', async () => { + const broker = new StorageTaskBroker(storage); const promise = broker.claim(); await expect(Promise.race([promise, 'waiting'])).resolves.toBe('waiting'); - await broker.dispatch(taskSpec); + await broker.dispatch({ steps: [] }); await expect(promise).resolves.toEqual(expect.any(TaskAgent)); }); it('should dispatch multiple items and claim them in order', async () => { - await broker.dispatch({ - values: { owner: 'a' } as TemplaterValues, - template: {} as TemplateEntityV1alpha1, - }); - await broker.dispatch({ - values: { owner: 'b' } as TemplaterValues, - template: {} as TemplateEntityV1alpha1, - }); - await broker.dispatch({ - values: { owner: 'c' } as TemplaterValues, - template: {} as TemplateEntityV1alpha1, - }); + const broker = new StorageTaskBroker(storage); + await broker.dispatch({ steps: [{ id: 'a' }] } as TaskSpec); + await broker.dispatch({ steps: [{ id: 'b' }] } as TaskSpec); + await broker.dispatch({ steps: [{ id: 'c' }] } as TaskSpec); const taskA = await broker.claim(); const taskB = await broker.claim(); @@ -62,13 +69,14 @@ describe('StorageTaskBroker', () => { await expect(taskA).toEqual(expect.any(TaskAgent)); await expect(taskB).toEqual(expect.any(TaskAgent)); await expect(taskC).toEqual(expect.any(TaskAgent)); - await expect(taskA.spec.values.owner).toBe('a'); - await expect(taskB.spec.values.owner).toBe('b'); - await expect(taskC.spec.values.owner).toBe('c'); + await expect(taskA.spec.steps[0].id).toBe('a'); + await expect(taskB.spec.steps[0].id).toBe('b'); + await expect(taskC.spec.steps[0].id).toBe('c'); }); it('should complete a task', async () => { - const dispatchResult = await broker.dispatch(taskSpec); + const broker = new StorageTaskBroker(storage); + const dispatchResult = await broker.dispatch({ steps: [] }); const task = await broker.claim(); await task.complete('completed'); const taskRow = await storage.get(dispatchResult.taskId); @@ -76,10 +84,91 @@ describe('StorageTaskBroker', () => { }); it('should fail a task', async () => { - const dispatchResult = await broker.dispatch(taskSpec); + const broker = new StorageTaskBroker(storage); + const dispatchResult = await broker.dispatch({ steps: [] }); const task = await broker.claim(); await task.complete('failed'); const taskRow = await storage.get(dispatchResult.taskId); expect(taskRow.status).toBe('failed'); }); + + it('multiple brokers should be able to observe a single task', async () => { + const broker1 = new StorageTaskBroker(storage); + const broker2 = new StorageTaskBroker(storage); + + const { taskId } = await broker1.dispatch({ steps: [] }); + + const logPromise = new Promise(resolve => { + const observedEvents = new Array(); + + broker2.observe({ taskId, after: undefined }, (_err, { events }) => { + observedEvents.push(...events); + if (events.some(e => e.type === 'completion')) { + resolve(observedEvents); + } + }); + }); + const task = await broker1.claim(); + await task.emitLog('log 1'); + await task.emitLog('log 2'); + await task.emitLog('log 3'); + await task.complete('completed'); + + const logs = await logPromise; + expect(logs.map(l => l.body.message)).toEqual([ + 'log 1', + 'log 2', + 'log 3', + 'Run completed with status: completed', + ]); + + const afterLogs = await new Promise(resolve => { + broker2.observe({ taskId, after: logs[1].id }, (_err, { events }) => + resolve(events.map(e => e.body.message as string)), + ); + }); + expect(afterLogs).toEqual([ + 'log 3', + 'Run completed with status: completed', + ]); + }); + + it('should heartbeat', async () => { + const broker = new StorageTaskBroker(storage); + const { taskId } = await broker.dispatch({ steps: [] }); + const task = await broker.claim(); + + const initialTask = await storage.get(taskId); + + for (;;) { + const maybeTask = await storage.get(taskId); + if (maybeTask.lastHeartbeat !== initialTask.lastHeartbeat) { + break; + } + await new Promise(resolve => setTimeout(resolve, 50)); + } + await task.complete('completed'); + expect.assertions(0); + }); + + it('should be cancelled if heartbeat stops', async () => { + const broker = new StorageTaskBroker(storage); + const { taskId } = await broker.dispatch({ steps: [] }); + console.log('DEBUG: taskId =', taskId); + const task = await broker.claim(); + clearInterval((task as any).heartbeatInterval); + + setTimeout(() => { + storage.listStaleTasks(); + }, 4000); + + for (;;) { + const maybeTask = await storage.get(taskId); + if (maybeTask.status === 'cancelled') { + break; + } + await new Promise(resolve => setTimeout(resolve, 50)); + } + expect.assertions(0); + }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 6eedc023f8..8c0d63ee71 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -25,7 +25,7 @@ import { } from './types'; export class TaskAgent implements Task { - private heartbeartInterval?: ReturnType; + private heartbeatInterval?: ReturnType; static create(state: TaskState, storage: TaskStore) { const agent = new TaskAgent(state, storage); @@ -67,13 +67,13 @@ export class TaskAgent implements Task { body: { message: `Run completed with status: ${result}` }, type: 'completion', }); - if (this.heartbeartInterval) { - clearInterval(this.heartbeartInterval); + if (this.heartbeatInterval) { + clearInterval(this.heartbeatInterval); } } private start() { - this.heartbeartInterval = setInterval(() => { + this.heartbeatInterval = setInterval(() => { if (!this.state.runId) { throw new Error('no run id provided'); } @@ -131,7 +131,10 @@ export class StorageTaskBroker implements TaskBroker { taskId: string; after: number | undefined; }, - callback: (result: { events: DbTaskEventRow[] }) => void, + callback: ( + error: Error | undefined, + result: { events: DbTaskEventRow[] }, + ) => void, ): () => void { const { taskId } = options; @@ -148,9 +151,9 @@ export class StorageTaskBroker implements TaskBroker { if (events.length) { after = events[events.length - 1].id; try { - callback(result); + callback(undefined, result); } catch (error) { - console.log('DEBUG: error =', error); + callback(error, { events: [] }); } } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index f9081af6b6..dd9a1ad9ad 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -86,6 +86,7 @@ export interface TaskStore { createTask(task: TaskSpec): Promise<{ taskId: string }>; claimTask(): Promise; heartbeat(runId: string): Promise; + listStaleTasks(): Promise<{ tasks: DbTaskRow }>; setStatus(runId: string, status: Status): Promise; emit({ taskId, runId, body, type }: TaskStoreEmitOptions): Promise; getEvents({ diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index 31f62febd2..8a0fa6cba6 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -16,6 +16,7 @@ const mockAccess = jest.fn(); jest.doMock('fs-extra', () => ({ + access: mockAccess, promises: { access: mockAccess, }, @@ -27,7 +28,11 @@ jest.doMock('fs-extra', () => ({ remove: jest.fn(), })); -import { getVoidLogger } from '@backstage/backend-common'; +import { + SingleConnectionDatabaseManager, + PluginDatabaseManager, + getVoidLogger, +} from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import express from 'express'; import request from 'supertest'; @@ -41,6 +46,19 @@ const generateEntityClient: any = (template: any) => ({ findTemplate: () => Promise.resolve(template), }); +function createDatabase(): PluginDatabaseManager { + return SingleConnectionDatabaseManager.fromConfig( + new ConfigReader({ + backend: { + database: { + client: 'sqlite3', + connection: ':memory:', + }, + }, + }), + ).forPlugin('scaffolder'); +} + describe('createRouter - working directory', () => { const mockPrepare = jest.fn(); const mockPreparers = new Preparers(); @@ -78,7 +96,6 @@ describe('createRouter - working directory', () => { }; const mockedEntityClient = generateEntityClient(template); - it('should throw an error when working directory does not exist or is not writable', async () => { mockAccess.mockImplementation(() => { throw new Error('access error'); @@ -93,6 +110,7 @@ describe('createRouter - working directory', () => { config: new ConfigReader(workDirConfig('/path')), dockerClient: new Docker(), entityClient: mockedEntityClient, + database: createDatabase(), }), ).rejects.toThrow('access error'); }); @@ -106,6 +124,7 @@ describe('createRouter - working directory', () => { config: new ConfigReader(workDirConfig('/path')), dockerClient: new Docker(), entityClient: mockedEntityClient, + database: createDatabase(), }); const app = express().use(router); @@ -134,6 +153,7 @@ describe('createRouter - working directory', () => { config: new ConfigReader({}), dockerClient: new Docker(), entityClient: mockedEntityClient, + database: createDatabase(), }); const app = express().use(router); @@ -203,6 +223,7 @@ describe('createRouter', () => { config: new ConfigReader({}), dockerClient: new Docker(), entityClient: generateEntityClient(template), + database: createDatabase(), }); app = express().use(router); }); diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 23af538df8..6eb112fa3c 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -164,7 +164,11 @@ export async function createRouter( // After client opens connection send all nests as string const unsubscribe = taskBroker.observe( { taskId, after }, - ({ events }) => { + (error, { events }) => { + logger.error( + `Received error from event stream when observing task ${taskId}`, + error, + ); for (const event of events) { res.write(`event:${JSON.stringify(event)}\n\n`); if (event.type === 'completion') { From 6608f9f5ee4cab89e5f658924db7b3ed975ff712 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 2 Feb 2021 16:10:13 +0100 Subject: [PATCH 033/110] Drop runId, update TaskStore interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Patrik Oldsberg --- .../migrations/20210120143715_init.js | 17 +--- .../src/scaffolder/tasks/DatabaseTaskStore.ts | 85 +++++++++--------- .../tasks/StorageTaskBroker.test.ts | 57 ++++++------ .../src/scaffolder/tasks/StorageTaskBroker.ts | 86 +++++++++++++------ .../src/scaffolder/tasks/TaskWorker.ts | 1 - .../src/scaffolder/tasks/types.ts | 39 ++++++--- .../scaffolder-backend/src/service/router.ts | 4 +- 7 files changed, 168 insertions(+), 121 deletions(-) diff --git a/plugins/scaffolder-backend/migrations/20210120143715_init.js b/plugins/scaffolder-backend/migrations/20210120143715_init.js index bb65e1e2fb..248b4f932f 100644 --- a/plugins/scaffolder-backend/migrations/20210120143715_init.js +++ b/plugins/scaffolder-backend/migrations/20210120143715_init.js @@ -31,10 +31,6 @@ exports.up = async function up(knex) { .text('status') .notNullable() .comment('The current status of the task'); - table - .integer('run_id') - .nullable() - .comment('The current run ID of the task'); table .dateTime('created_at') .defaultTo(knex.fn.now()) @@ -44,11 +40,6 @@ exports.up = async function up(knex) { .dateTime('last_heartbeat_at') .nullable() .comment('The last timestamp when a heartbeat was received'); - table - .integer('retry_count') - .notNullable() - .defaultTo(0) - .comment('The number of times that this task has been attempted'); }); await knex.schema.createTable('task_events', table => { @@ -65,17 +56,13 @@ exports.up = async function up(knex) { .notNullable() .onDelete('CASCADE') .comment('The task that generated the event'); - table - .integer('run_id') - .nullable() - .comment('The run ID of the task that this event applies to'); table .text('body') .notNullable() .comment('The JSON encoded body of the event'); table.text('event_type').notNullable().comment('The type of event'); table - .dateTime('created_at') + .timestamp('created_at', { precision: 9 }) .defaultTo(knex.fn.now()) .notNullable() .comment('The timestamp when this event was generated'); @@ -90,7 +77,7 @@ exports.up = async function up(knex) { exports.down = async function down(knex) { if (knex.client.config.client !== 'sqlite3') { await knex.schema.alterTable('task_events', table => { - table.dropIndex([], 'task_events_task_id_idx'); + table.dropIndex([], 'ctask_events_task_id_idx'); }); } await knex.schema.dropTable('task_events'); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index 1d99cd2958..cf1bc18621 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -43,14 +43,11 @@ export type RawDbTaskRow = { spec: string; status: Status; last_heartbeat_at?: string; - retry_count: number; created_at: string; - run_id?: string; }; export type RawDbTaskEventRow = { id: number; - run_id: string; task_id: string; body: string; event_type: TaskEventType; @@ -80,10 +77,8 @@ export class DatabaseTaskStore implements TaskStore { id: result.id, spec, status: result.status, - lastHeartbeat: result.last_heartbeat_at, - retryCount: result.retry_count, + lastHeartbeatAt: result.last_heartbeat_at, createdAt: result.created_at, - runId: result.run_id, }; } catch (error) { throw new Error(`Failed to parse spec of task '${taskId}', ${error}`); @@ -96,7 +91,6 @@ export class DatabaseTaskStore implements TaskStore { id: taskId, spec: JSON.stringify(spec), status: 'open', - retry_count: 0, }); return { taskId }; } @@ -114,12 +108,10 @@ export class DatabaseTaskStore implements TaskStore { return undefined; } - const runId = uuid(); const updateCount = await tx('tasks') .where({ id: task.id, status: 'open' }) .update({ status: 'processing', - run_id: runId, last_heartbeat_at: this.db.fn.now(), }); @@ -133,10 +125,8 @@ export class DatabaseTaskStore implements TaskStore { id: task.id, spec, status: 'processing', - lastHeartbeat: task.last_heartbeat_at, - retryCount: task.retry_count, + lastHeartbeatAt: task.last_heartbeat_at, createdAt: task.created_at, - runId: runId, }; } catch (error) { throw new Error(`Failed to parse spec of task '${task.id}', ${error}`); @@ -144,58 +134,76 @@ export class DatabaseTaskStore implements TaskStore { }); } - async heartbeat(runId: string): Promise { + async heartbeatTask(taskId: string): Promise { const updateCount = await this.db('tasks') - .where({ run_id: runId, status: 'processing' }) + .where({ id: taskId, status: 'processing' }) .update({ last_heartbeat_at: this.db.fn.now(), }); if (updateCount === 0) { - throw new Error(`No running task with runId ${runId} found`); + throw new Error(`No running task with taskId ${taskId} found`); } } - async listStaleTasks(): Promise<{ tasks: DbTaskRow }> { - const rows = await this.db('tasks') + async listStaleTasks({ + timeoutS, + }: { + timeoutS: number; + }): Promise<{ + tasks: { taskId: string }[]; + }> { + const rawRows = await this.db('tasks') .where('status', 'processing') .andWhere( 'last_heartbeat_at', - '<', - this.db.type === 'sqlite' - ? this.db.raw("datetime('now', '-2 seconds')") - : this.db.raw("dateadd('second', -2, now())"), + '<=', + this.db.client.config.client === 'sqlite3' + ? this.db.raw(`datetime('now', '-${Number(timeoutS)} seconds')`) + : this.db.raw(`dateadd('second', -${Number(timeoutS)}, now())`), ); + const tasks = rawRows.map(row => ({ + taskId: row.id, + })); + return { tasks }; } - async setStatus(runId: string, status: Status): Promise { + async completeTask({ + taskId, + status, + eventBody, + }: { + taskId: string; + status: Status; + eventBody: JsonObject; + }): Promise { let oldStatus: string; if (status === 'failed' || status === 'completed') { oldStatus = 'processing'; } else { throw new Error( - `Invalid status update of run '${runId}' to status '${status}'`, + `Invalid status update of run '${taskId}' to status '${status}'`, ); } await this.db.transaction(async tx => { const [task] = await tx('tasks') .where({ - run_id: runId, + id: taskId, }) .limit(1) .select(); if (!task) { - throw new Error(`No task with runId ${runId} found`); + throw new Error(`No task with taskId ${taskId} found`); } if (task.status !== oldStatus) { throw new ConflictError( - `Refusing to update status of run '${runId}' to status '${status}' ` + + `Refusing to update status of run '${taskId}' to status '${status}' ` + `as it is currently '${task.status}', expected '${oldStatus}'`, ); } const updateCount = await tx('tasks') .where({ - run_id: runId, + id: taskId, status: oldStatus, }) .update({ @@ -203,28 +211,28 @@ export class DatabaseTaskStore implements TaskStore { }); if (updateCount !== 1) { throw new Error( - `Failed to update status to '${status}' for runId ${runId}`, + `Failed to update status to '${status}' for taskId ${taskId}`, ); } + + await tx('task_events').insert({ + task_id: taskId, + event_type: 'completion', + body: JSON.stringify(eventBody), + }); }); } - async emit({ - taskId, - runId, - body, - type, - }: TaskStoreEmitOptions): Promise { + async emitLogEvent({ taskId, body }: TaskStoreEmitOptions): Promise { const serliazedBody = JSON.stringify(body); await this.db('task_events').insert({ task_id: taskId, - run_id: runId, - event_type: type, + event_type: 'log', body: serliazedBody, }); } - async getEvents({ + async listEvents({ taskId, after, }: TaskStoreGetEventsOptions): Promise<{ events: DbTaskEventRow[] }> { @@ -244,8 +252,7 @@ export class DatabaseTaskStore implements TaskStore { const body = JSON.parse(event.body) as JsonObject; return { id: event.id, - runId: event.run_id, - taskId: event.task_id, + taskId, body, type: event.event_type, createdAt: event.created_at, diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts index ebbac1db5f..c45fa46335 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts @@ -14,13 +14,16 @@ * limitations under the License. */ -import { SingleConnectionDatabaseManager } from '@backstage/backend-common'; +import { + getVoidLogger, + SingleConnectionDatabaseManager, +} from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { DatabaseTaskStore } from './DatabaseTaskStore'; import { StorageTaskBroker, TaskAgent } from './StorageTaskBroker'; -import { TaskStore, TaskSpec, DbTaskEventRow } from './types'; +import { TaskSpec, DbTaskEventRow } from './types'; -async function createStore(): Promise { +async function createStore(): Promise { const manager = SingleConnectionDatabaseManager.fromConfig( new ConfigReader({ backend: { @@ -35,20 +38,21 @@ async function createStore(): Promise { } describe('StorageTaskBroker', () => { - let storage: TaskStore; + let storage: DatabaseTaskStore; beforeAll(async () => { storage = await createStore(); }); + const logger = getVoidLogger(); it('should claim a dispatched work item', async () => { - const broker = new StorageTaskBroker(storage); + const broker = new StorageTaskBroker(storage, logger); await broker.dispatch({ steps: [] }); await expect(broker.claim()).resolves.toEqual(expect.any(TaskAgent)); }); it('should wait for a dispatched work item', async () => { - const broker = new StorageTaskBroker(storage); + const broker = new StorageTaskBroker(storage, logger); const promise = broker.claim(); await expect(Promise.race([promise, 'waiting'])).resolves.toBe('waiting'); @@ -58,7 +62,7 @@ describe('StorageTaskBroker', () => { }); it('should dispatch multiple items and claim them in order', async () => { - const broker = new StorageTaskBroker(storage); + const broker = new StorageTaskBroker(storage, logger); await broker.dispatch({ steps: [{ id: 'a' }] } as TaskSpec); await broker.dispatch({ steps: [{ id: 'b' }] } as TaskSpec); await broker.dispatch({ steps: [{ id: 'c' }] } as TaskSpec); @@ -75,16 +79,16 @@ describe('StorageTaskBroker', () => { }); it('should complete a task', async () => { - const broker = new StorageTaskBroker(storage); + const broker = new StorageTaskBroker(storage, logger); const dispatchResult = await broker.dispatch({ steps: [] }); const task = await broker.claim(); await task.complete('completed'); const taskRow = await storage.get(dispatchResult.taskId); expect(taskRow.status).toBe('completed'); - }); + }, 10000); it('should fail a task', async () => { - const broker = new StorageTaskBroker(storage); + const broker = new StorageTaskBroker(storage, logger); const dispatchResult = await broker.dispatch({ steps: [] }); const task = await broker.claim(); await task.complete('failed'); @@ -93,8 +97,8 @@ describe('StorageTaskBroker', () => { }); it('multiple brokers should be able to observe a single task', async () => { - const broker1 = new StorageTaskBroker(storage); - const broker2 = new StorageTaskBroker(storage); + const broker1 = new StorageTaskBroker(storage, logger); + const broker2 = new StorageTaskBroker(storage, logger); const { taskId } = await broker1.dispatch({ steps: [] }); @@ -115,7 +119,7 @@ describe('StorageTaskBroker', () => { await task.complete('completed'); const logs = await logPromise; - expect(logs.map(l => l.body.message)).toEqual([ + expect(logs.map(l => l.body.message, logger)).toEqual([ 'log 1', 'log 2', 'log 3', @@ -134,7 +138,7 @@ describe('StorageTaskBroker', () => { }); it('should heartbeat', async () => { - const broker = new StorageTaskBroker(storage); + const broker = new StorageTaskBroker(storage, logger); const { taskId } = await broker.dispatch({ steps: [] }); const task = await broker.claim(); @@ -142,7 +146,7 @@ describe('StorageTaskBroker', () => { for (;;) { const maybeTask = await storage.get(taskId); - if (maybeTask.lastHeartbeat !== initialTask.lastHeartbeat) { + if (maybeTask.lastHeartbeatAt !== initialTask.lastHeartbeatAt) { break; } await new Promise(resolve => setTimeout(resolve, 50)); @@ -151,24 +155,29 @@ describe('StorageTaskBroker', () => { expect.assertions(0); }); - it('should be cancelled if heartbeat stops', async () => { - const broker = new StorageTaskBroker(storage); + it('should be update the status to failed if heartbeat fails', async () => { + const broker = new StorageTaskBroker(storage, logger); const { taskId } = await broker.dispatch({ steps: [] }); - console.log('DEBUG: taskId =', taskId); const task = await broker.claim(); - clearInterval((task as any).heartbeatInterval); - setTimeout(() => { - storage.listStaleTasks(); - }, 4000); + jest + .spyOn((task as any).storage, 'heartbeatTask') + .mockRejectedValue(new Error('nah m8')); + + const intervalId = setInterval(() => { + broker.vacuumTasks({ timeoutS: 2 }).catch(fail); + }, 500); for (;;) { const maybeTask = await storage.get(taskId); - if (maybeTask.status === 'cancelled') { + if (maybeTask.status === 'failed') { break; } await new Promise(resolve => setTimeout(resolve, 50)); } - expect.assertions(0); + + clearInterval(intervalId); + + expect(task.done).toBe(true); }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 8c0d63ee71..48c0d5e5ba 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +import { Logger } from 'winston'; import { CompletedTaskState, Task, @@ -25,11 +25,13 @@ import { } from './types'; export class TaskAgent implements Task { - private heartbeatInterval?: ReturnType; + private isDone = false; - static create(state: TaskState, storage: TaskStore) { - const agent = new TaskAgent(state, storage); - agent.start(); + private heartbeatTimeoutId?: ReturnType; + + static create(state: TaskState, storage: TaskStore, logger: Logger) { + const agent = new TaskAgent(state, storage, logger); + agent.startTimeout(); return agent; } @@ -37,6 +39,7 @@ export class TaskAgent implements Task { private constructor( private readonly state: TaskState, private readonly storage: TaskStore, + private readonly logger: Logger, ) {} get spec() { @@ -44,40 +47,45 @@ export class TaskAgent implements Task { } async getWorkspaceName() { - return `${this.state.taskId}_${this.state.runId}`; + return this.state.taskId; + } + + get done() { + return this.isDone; } async emitLog(message: string): Promise { - await this.storage.emit({ + await this.storage.emitLogEvent({ taskId: this.state.taskId, - runId: this.state.runId, body: { message }, - type: 'log', }); } async complete(result: CompletedTaskState): Promise { - await this.storage.setStatus( - this.state.runId, - result === 'failed' ? 'failed' : 'completed', - ); - this.storage.emit({ + await this.storage.completeTask({ taskId: this.state.taskId, - runId: this.state.runId, - body: { message: `Run completed with status: ${result}` }, - type: 'completion', + status: result === 'failed' ? 'failed' : 'completed', + eventBody: { message: `Run completed with status: ${result}` }, }); - if (this.heartbeatInterval) { - clearInterval(this.heartbeatInterval); + this.isDone = true; + if (this.heartbeatTimeoutId) { + clearTimeout(this.heartbeatTimeoutId); } } - private start() { - this.heartbeatInterval = setInterval(() => { - if (!this.state.runId) { - throw new Error('no run id provided'); + private startTimeout() { + this.heartbeatTimeoutId = setTimeout(async () => { + try { + await this.storage.heartbeatTask(this.state.taskId); + this.startTimeout(); + } catch (error) { + this.isDone = true; + + this.logger.error( + `Heartbeat for task ${this.state.taskId} failed`, + error, + ); } - this.storage.heartbeat(this.state.runId); }, 1000); } } @@ -85,7 +93,6 @@ export class TaskAgent implements Task { interface TaskState { spec: TaskSpec; taskId: string; - runId: string; } function defer() { @@ -97,7 +104,10 @@ function defer() { } export class StorageTaskBroker implements TaskBroker { - constructor(private readonly storage: TaskStore) {} + constructor( + private readonly storage: TaskStore, + private readonly logger: Logger, + ) {} private deferredDispatch = defer(); async claim(): Promise { @@ -106,11 +116,11 @@ export class StorageTaskBroker implements TaskBroker { if (pendingTask) { return TaskAgent.create( { - runId: pendingTask.runId!, taskId: pendingTask.id, spec: pendingTask.spec, }, this.storage, + this.logger, ); } @@ -146,7 +156,7 @@ export class StorageTaskBroker implements TaskBroker { (async () => { let after = options.after; while (!cancelled) { - const result = await this.storage.getEvents({ taskId, after: after }); + const result = await this.storage.listEvents({ taskId, after: after }); const { events } = result; if (events.length) { after = events[events.length - 1].id; @@ -164,6 +174,26 @@ export class StorageTaskBroker implements TaskBroker { return unsubscribe; } + async vacuumTasks(timeoutS: { timeoutS: number }): Promise { + const { tasks } = await this.storage.listStaleTasks(timeoutS); + await Promise.all( + tasks.map(async task => { + try { + await this.storage.completeTask({ + taskId: task.taskId, + status: 'failed', + eventBody: { + message: + 'The task was cancelled because the task worker lost connection to the task broker', + }, + }); + } catch (error) { + this.logger.warn(`Failed to cancel task '${task.taskId}', ${error}`); + } + }), + ); + } + private waitForDispatch() { return this.deferredDispatch.promise; } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 64cfdc6f96..f87d2df934 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -73,7 +73,6 @@ export class TaskWorker { // Give us some time to curl observe task.emitLog('Task claimed, waiting ...'); await new Promise(resolve => setTimeout(resolve, 5000)); - console.log('DEBUG: task.spec =', JSON.stringify(task.spec, null, 2)); task.emitLog(`Starting up work with ${task.spec.steps.length} steps`); const outputs: { [name: string]: JsonValue } = {}; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index dd9a1ad9ad..0c2592109b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -29,16 +29,13 @@ export type DbTaskRow = { id: string; spec: TaskSpec; status: Status; - lastHeartbeat?: string; - retryCount: number; createdAt: string; - runId?: string; + lastHeartbeatAt?: string; }; export type TaskEventType = 'completion' | 'log'; export type DbTaskEventRow = { id: number; - runId: string; taskId: string; body: JsonObject; type: TaskEventType; @@ -60,6 +57,7 @@ export type DispatchResult = { export interface Task { spec: TaskSpec; + done: boolean; emitLog(message: string): Promise; complete(result: CompletedTaskState): Promise; getWorkspaceName(): Promise; @@ -68,13 +66,22 @@ export interface Task { export interface TaskBroker { claim(): Promise; dispatch(spec: TaskSpec): Promise; + vacuumTasks(timeoutS: { timeoutS: number }): Promise; + observe( + options: { + taskId: string; + after: number | undefined; + }, + callback: ( + error: Error | undefined, + result: { events: DbTaskEventRow[] }, + ) => void, + ): () => void; } export type TaskStoreEmitOptions = { taskId: string; - runId: string; body: JsonObject; - type: TaskEventType; }; export type TaskStoreGetEventsOptions = { @@ -82,14 +89,22 @@ export type TaskStoreGetEventsOptions = { after?: number | undefined; }; export interface TaskStore { - get(taskId: string): Promise; createTask(task: TaskSpec): Promise<{ taskId: string }>; claimTask(): Promise; - heartbeat(runId: string): Promise; - listStaleTasks(): Promise<{ tasks: DbTaskRow }>; - setStatus(runId: string, status: Status): Promise; - emit({ taskId, runId, body, type }: TaskStoreEmitOptions): Promise; - getEvents({ + completeTask(options: { + taskId: string; + status: Status; + eventBody: JsonObject; + }): Promise; + heartbeatTask(taskId: string): Promise; + listStaleTasks(options: { + timeoutS: number; + }): Promise<{ + tasks: { taskId: string }[]; + }>; + + emitLogEvent({ taskId, body }: TaskStoreEmitOptions): Promise; + listEvents({ taskId, after, }: TaskStoreGetEventsOptions): Promise<{ events: DbTaskEventRow[] }>; diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 6eb112fa3c..8da6f02507 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -82,7 +82,7 @@ export async function createRouter( const databaseTaskStore = await DatabaseTaskStore.create( await database.getClient(), ); - const taskBroker = new StorageTaskBroker(databaseTaskStore); + const taskBroker = new StorageTaskBroker(databaseTaskStore, logger); const actionRegistry = new TemplateActionRegistry(); const worker = new TaskWorker({ logger, @@ -124,7 +124,7 @@ export async function createRouter( error: job.error, }); }) - // curl -X POST -d '{"templateName":"springboot-template","values": {"storePath":"https://github.com/jhaals/foo", "component_id":"woop", "description": "apa", "owner": "me" }}' -H 'Content-Type: application/json' localhost:7000/api/scaffolder/v2/tasks + .post('/v2/tasks', async (req, res) => { const templateName: string = req.body.templateName; const values: TemplaterValues = { From 9b0bba5439c7fe7383f897756b6394d807a62393 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 2 Feb 2021 16:25:45 +0100 Subject: [PATCH 034/110] Reshuffle api endpoints --- .../scaffolder-backend/src/service/router.ts | 124 +++++++++--------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 8da6f02507..16cefa5297 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -124,68 +124,6 @@ export async function createRouter( error: job.error, }); }) - - .post('/v2/tasks', async (req, res) => { - const templateName: string = req.body.templateName; - const values: TemplaterValues = { - ...req.body.values, - destination: { - git: parseGitUrl(req.body.values.storePath), - }, - }; - const template = await entityClient.findTemplate(templateName); - - const validationResult: ValidatorResult = validate( - values, - template.spec.schema, - ); - - if (!validationResult.valid) { - res.status(400).json({ errors: validationResult.errors }); - return; - } - const taskSpec = templateEntityToSpec(template, values); - const result = await taskBroker.dispatch(taskSpec); - - res.status(201).json({ id: result.taskId }); - }) - - .get('/v2/tasks/:taskId/eventstream', async (req, res) => { - const { taskId } = req.params; - const after = Number(req.query.after) || undefined; - logger.info('event stream opened'); - - // Mandatory headers and http status to keep connection open - res.writeHead(200, { - Connection: 'keep-alive', - 'Cache-Control': 'no-cache', - 'Content-Type': 'text/event-stream', - }); - // After client opens connection send all nests as string - const unsubscribe = taskBroker.observe( - { taskId, after }, - (error, { events }) => { - logger.error( - `Received error from event stream when observing task ${taskId}`, - error, - ); - for (const event of events) { - res.write(`event:${JSON.stringify(event)}\n\n`); - if (event.type === 'completion') { - unsubscribe(); - res.end(); - } - } - }, - ); - // When client closes connection we update the clients list - // avoiding the disconnected one - req.on('close', () => { - unsubscribe(); - logger.info('event stream closed'); - }); - }) - .post('/v1/jobs', async (req, res) => { const templateName: string = req.body.templateName; const values: TemplaterValues = { @@ -282,6 +220,68 @@ export async function createRouter( res.status(201).json({ id: job.id }); }); + // NOTE: The v2 API is unstable + router + .post('/v2/tasks', async (req, res) => { + const templateName: string = req.body.templateName; + const values: TemplaterValues = { + ...req.body.values, + destination: { + git: parseGitUrl(req.body.values.storePath), + }, + }; + const template = await entityClient.findTemplate(templateName); + + const validationResult: ValidatorResult = validate( + values, + template.spec.schema, + ); + + if (!validationResult.valid) { + res.status(400).json({ errors: validationResult.errors }); + return; + } + const taskSpec = templateEntityToSpec(template, values); + const result = await taskBroker.dispatch(taskSpec); + + res.status(201).json({ id: result.taskId }); + }) + .get('/v2/tasks/:taskId/eventstream', async (req, res) => { + const { taskId } = req.params; + const after = Number(req.query.after) || undefined; + logger.info('event stream opened'); + + // Mandatory headers and http status to keep connection open + res.writeHead(200, { + Connection: 'keep-alive', + 'Cache-Control': 'no-cache', + 'Content-Type': 'text/event-stream', + }); + // After client opens connection send all nests as string + const unsubscribe = taskBroker.observe( + { taskId, after }, + (error, { events }) => { + logger.error( + `Received error from event stream when observing task ${taskId}`, + error, + ); + for (const event of events) { + res.write(`event:${JSON.stringify(event)}\n\n`); + if (event.type === 'completion') { + unsubscribe(); + res.end(); + } + } + }, + ); + // When client closes connection we update the clients list + // avoiding the disconnected one + req.on('close', () => { + unsubscribe(); + logger.info('event stream closed'); + }); + }); + const app = express(); app.set('logger', logger); app.use('/', router); From 615103a631993ec21745e6b3d93652f74c48cd4d Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 2 Feb 2021 16:33:00 +0100 Subject: [PATCH 035/110] Add changesets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Patrik Oldsberg --- .changeset/afraid-teachers-cross.md | 29 +++++++++++++++++++++++++++++ .changeset/large-terms-invite.md | 7 +++++++ 2 files changed, 36 insertions(+) create mode 100644 .changeset/afraid-teachers-cross.md create mode 100644 .changeset/large-terms-invite.md diff --git a/.changeset/afraid-teachers-cross.md b/.changeset/afraid-teachers-cross.md new file mode 100644 index 0000000000..8fc376441f --- /dev/null +++ b/.changeset/afraid-teachers-cross.md @@ -0,0 +1,29 @@ +--- +'@backstage/create-app': patch +--- + +Pass on plugin database management instance that is now required by the scaffolder plugin. + +To apply this change to an existing application, add the following to `src/plugins/scaffolder.ts`: + +```diff +export default async function createPlugin({ + logger, + config, ++ database, +}: PluginEnvironment) { + +// ...omitted... + + return await createRouter({ + preparers, + templaters, + publishers, + logger, + config, + dockerClient, + entityClient, ++ database, + }); +} +``` diff --git a/.changeset/large-terms-invite.md b/.changeset/large-terms-invite.md new file mode 100644 index 0000000000..6653ac8e1e --- /dev/null +++ b/.changeset/large-terms-invite.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +Introduced `v2` Scaffolder REST API, which uses an implementation that is database backed, making the scaffolder instances stateless. The `createRouter` function now requires a `PluginDatabaseManager` instance to be passed in, commonly available as `database` in the plugin environment in the backend. + +This API should be considered unstable until used by the scaffolder frontend. From a173d47527b0ceb960c3a2a815b59edf2ebb6e2e Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 2 Feb 2021 16:45:28 +0100 Subject: [PATCH 036/110] Delete knexfile --- plugins/scaffolder-backend/knexfile.js | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 plugins/scaffolder-backend/knexfile.js diff --git a/plugins/scaffolder-backend/knexfile.js b/plugins/scaffolder-backend/knexfile.js deleted file mode 100644 index f469df4c08..0000000000 --- a/plugins/scaffolder-backend/knexfile.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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. - */ - -module.exports = { - development: { - client: 'sqlite3', - connection: ':memory:', - migrations: { - directory: 'migrations', - }, - }, -}; From c386de896d65dbe05db2a1c23d0bc2a68a4aa864 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 2 Feb 2021 16:46:11 +0100 Subject: [PATCH 037/110] Remove unused dependencies --- plugins/scaffolder-backend/package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index bdecf6196e..9f45c218dc 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -55,11 +55,9 @@ "jsonschema": "^1.2.6", "knex": "^0.21.6", "morgan": "^1.10.0", - "p-queue": "^6.3.0", "uuid": "^8.2.0", "winston": "^3.2.1", - "yaml": "^1.10.0", - "luxon": "^1.25.0" + "yaml": "^1.10.0" }, "devDependencies": { "@backstage/cli": "^0.5.0", From 0c00a0c14c979c61a59ba002ead4f90fceacf852 Mon Sep 17 00:00:00 2001 From: Debajyoti Halder Date: Tue, 2 Feb 2021 23:07:42 +0530 Subject: [PATCH 038/110] cost-insights: Add better type names from patch --- .../AlertInsights/AlertInsights.tsx | 20 ++++++------- .../src/components/BarChart/BarChartLabel.tsx | 4 +-- .../src/components/BarChart/BarChartSteps.tsx | 4 +-- .../ProductInsights/ProductInsights.tsx | 28 +++++++------------ 4 files changed, 24 insertions(+), 32 deletions(-) diff --git a/plugins/cost-insights/src/components/AlertInsights/AlertInsights.tsx b/plugins/cost-insights/src/components/AlertInsights/AlertInsights.tsx index 5204686118..7360c99cf7 100644 --- a/plugins/cost-insights/src/components/AlertInsights/AlertInsights.tsx +++ b/plugins/cost-insights/src/components/AlertInsights/AlertInsights.tsx @@ -124,18 +124,18 @@ export const AlertInsights = ({ setSnackbarOpen(!!error); }, [error]); - function onSnooze(alertProp: Alert) { - setAlert(alertProp); + function onSnooze(alertToSnooze: Alert) { + setAlert(alertToSnooze); setStatus(AlertStatus.Snoozed); } - function onAccept(alertProp: Alert) { - setAlert(alertProp); + function onAccept(alertToAccept: Alert) { + setAlert(alertToAccept); setStatus(AlertStatus.Accepted); } - function onDismiss(alertProp: Alert) { - setAlert(alertProp); + function onDismiss(alertToDismiss: Alert) { + setAlert(alertToDismiss); setStatus(AlertStatus.Dismissed); } @@ -148,8 +148,8 @@ export const AlertInsights = ({ setStatus(null); } - function onDialogFormSubmit(inputData: any) { - setData(inputData); + function onDialogFormSubmit(formData: any) { + setData(formData); } function onSummaryButtonClick() { @@ -175,10 +175,10 @@ export const AlertInsights = ({
{isAlertInsightSectionDisplayed && ( - {active.map((alertItem, index) => ( + {active.map((activeAlert, index) => ( ) => { +}: PropsWithChildren) => { const classes = useBarChartLabelStyles(); const translateX = width * -0.5; diff --git a/plugins/cost-insights/src/components/BarChart/BarChartSteps.tsx b/plugins/cost-insights/src/components/BarChart/BarChartSteps.tsx index ec573da68f..8157e05444 100644 --- a/plugins/cost-insights/src/components/BarChart/BarChartSteps.tsx +++ b/plugins/cost-insights/src/components/BarChart/BarChartSteps.tsx @@ -18,7 +18,7 @@ import React from 'react'; import { ButtonBase } from '@material-ui/core'; import { useBarChartStepperStyles as useStyles } from '../../utils/styles'; -export type BarChartStepsObject = { +export type BarChartStepsProps = { steps: number; activeStep: number; onClick: (index: number) => void; @@ -28,7 +28,7 @@ export const BarChartSteps = ({ steps, activeStep, onClick, -}: BarChartStepsObject) => { +}: BarChartStepsProps) => { const classes = useStyles(); const handleOnClick = (index: number) => ( event: React.MouseEvent, diff --git a/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx b/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx index b13376d24a..5702e67172 100644 --- a/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx +++ b/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx @@ -82,32 +82,24 @@ export const ProductInsights = ({ ); useEffect(() => { - async function getAllProductInsights( - groupId: string, - projectId: Maybe, - productIds: Product[], - lastCompleteBillingDateString: string, - ) { + async function getAllProductInsights() { try { dispatchLoadingProducts(true); const responses = await Promise.allSettled( - productIds.map(product => + products.map(product => client.getProductInsights({ - group: groupId, - project: projectId, + group: group, + project: project, product: product.kind, - intervals: intervalsOf( - DEFAULT_DURATION, - lastCompleteBillingDateString, - ), + intervals: intervalsOf(DEFAULT_DURATION, lastCompleteBillingDate), }), ), ).then(settledResponseOf); - const initialStatesNow = initialStatesOf(productIds, responses).sort( + const updatedInitialStates = initialStatesOf(products, responses).sort( totalAggregationSort, ); - setStates(initialStatesNow); + setStates(updatedInitialStates); } catch (e) { setError(e); } finally { @@ -115,7 +107,7 @@ export const ProductInsights = ({ } } - getAllProductInsights(group, project, products, lastCompleteBillingDate); + getAllProductInsights(); }, [ client, group, @@ -128,8 +120,8 @@ export const ProductInsights = ({ useEffect( function handleOnLoaded() { if (onceRef.current) { - const currentProducts = initialStates.map(state => state.product); - onLoaded(currentProducts); + const initialProducts = initialStates.map(state => state.product); + onLoaded(initialProducts); } else { onceRef.current = true; } From 6b26c9f41eb059968db05998d5f4bbf50d75fbb4 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 2 Feb 2021 21:57:41 +0100 Subject: [PATCH 039/110] circleci: migrate to new composability API --- .changeset/curly-crabs-compete.md | 5 +++++ plugins/circleci/dev/index.tsx | 4 ++-- plugins/circleci/src/components/Router.tsx | 19 +++++++++++++++---- plugins/circleci/src/index.ts | 12 ++++++++++-- plugins/circleci/src/plugin.test.ts | 4 ++-- plugins/circleci/src/plugin.ts | 11 ++++++++++- 6 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 .changeset/curly-crabs-compete.md diff --git a/.changeset/curly-crabs-compete.md b/.changeset/curly-crabs-compete.md new file mode 100644 index 0000000000..552a41a9f1 --- /dev/null +++ b/.changeset/curly-crabs-compete.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-circleci': patch +--- + +Migrated to new composability API, exporting the plugin instance as `circleCIPlugin`, the entity page content as `EntityCircleCIContent`, and entity conditional as `isCircleCIAvailable`. diff --git a/plugins/circleci/dev/index.tsx b/plugins/circleci/dev/index.tsx index 812a5585d4..19a32c0d04 100644 --- a/plugins/circleci/dev/index.tsx +++ b/plugins/circleci/dev/index.tsx @@ -15,6 +15,6 @@ */ import { createDevApp } from '@backstage/dev-utils'; -import { plugin } from '../src/plugin'; +import { circleCIPlugin } from '../src/plugin'; -createDevApp().registerPlugin(plugin).render(); +createDevApp().registerPlugin(circleCIPlugin).render(); diff --git a/plugins/circleci/src/components/Router.tsx b/plugins/circleci/src/components/Router.tsx index 5fe9a20eff..b3a0d2c3e2 100644 --- a/plugins/circleci/src/components/Router.tsx +++ b/plugins/circleci/src/components/Router.tsx @@ -21,15 +21,25 @@ import { BuildWithStepsPage } from './BuildWithStepsPage/'; import { BuildsPage } from './BuildsPage'; import { CIRCLECI_ANNOTATION } from '../constants'; import { Entity } from '@backstage/catalog-model'; +import { useEntity } from '@backstage/plugin-catalog-react'; import { MissingAnnotationEmptyState } from '@backstage/core'; export const isPluginApplicableToEntity = (entity: Entity) => Boolean(entity.metadata.annotations?.[CIRCLECI_ANNOTATION]); -export const Router = ({ entity }: { entity: Entity }) => - !isPluginApplicableToEntity(entity) ? ( - - ) : ( +type Props = { + /** @deprecated The entity is now grabbed from context instead */ + entity?: Entity; +}; + +export const Router = (_props: Props) => { + const { entity } = useEntity(); + + if (!isPluginApplicableToEntity(entity)) { + return ; + } + + return ( } /> /> ); +}; diff --git a/plugins/circleci/src/index.ts b/plugins/circleci/src/index.ts index c6cb140208..1e8b213279 100644 --- a/plugins/circleci/src/index.ts +++ b/plugins/circleci/src/index.ts @@ -14,8 +14,16 @@ * limitations under the License. */ -export { plugin } from './plugin'; +export { + circleCIPlugin, + circleCIPlugin as plugin, + EntityCircleCIContent, +} from './plugin'; export * from './api'; export * from './route-refs'; -export { Router, isPluginApplicableToEntity } from './components/Router'; +export { + Router, + isPluginApplicableToEntity, + isPluginApplicableToEntity as isCircleCIAvailable, +} from './components/Router'; export { CIRCLECI_ANNOTATION } from './constants'; diff --git a/plugins/circleci/src/plugin.test.ts b/plugins/circleci/src/plugin.test.ts index 821a503257..bdd637027c 100644 --- a/plugins/circleci/src/plugin.test.ts +++ b/plugins/circleci/src/plugin.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { plugin } from './plugin'; +import { circleCIPlugin } from './plugin'; describe('circleci', () => { it('should export plugin', () => { - expect(plugin).toBeDefined(); + expect(circleCIPlugin).toBeDefined(); }); }); diff --git a/plugins/circleci/src/plugin.ts b/plugins/circleci/src/plugin.ts index c5bb122ed3..4e0d485726 100644 --- a/plugins/circleci/src/plugin.ts +++ b/plugins/circleci/src/plugin.ts @@ -18,10 +18,12 @@ import { createPlugin, createApiFactory, discoveryApiRef, + createRoutableExtension, } from '@backstage/core'; import { circleCIApiRef, CircleCIApi } from './api'; +import { circleCIRouteRef } from './route-refs'; -export const plugin = createPlugin({ +export const circleCIPlugin = createPlugin({ id: 'circleci', apis: [ createApiFactory({ @@ -31,3 +33,10 @@ export const plugin = createPlugin({ }), ], }); + +export const EntityCircleCIContent = circleCIPlugin.provide( + createRoutableExtension({ + component: () => import('./components/Router').then(m => m.Router), + mountPoint: circleCIRouteRef, + }), +); From 302795d107c2a2dedd521bf644b2d85283311f76 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 2 Feb 2021 22:16:45 +0100 Subject: [PATCH 040/110] cloudbuild: migrate to now composability API --- .changeset/four-rings-push.md | 5 +++ plugins/cloudbuild/dev/index.tsx | 4 +-- plugins/cloudbuild/package.json | 1 + .../cloudbuild/src/components/Cards/Cards.tsx | 24 ++++++++------ plugins/cloudbuild/src/components/Router.tsx | 20 +++++++++--- plugins/cloudbuild/src/index.ts | 14 ++++++-- plugins/cloudbuild/src/plugin.test.ts | 4 +-- plugins/cloudbuild/src/plugin.ts | 32 ++++++++++++++++++- 8 files changed, 83 insertions(+), 21 deletions(-) create mode 100644 .changeset/four-rings-push.md diff --git a/.changeset/four-rings-push.md b/.changeset/four-rings-push.md new file mode 100644 index 0000000000..500dd0e812 --- /dev/null +++ b/.changeset/four-rings-push.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-cloudbuild': patch +--- + +Migrate to new composability API, exporting the plugin instance as `cloudbuildPlugin`, the entity content as `EntityCloudbuildContent`, the entity conditional as `isCloudbuildAvailable`, and entity cards as `EntityLatestCloudbuildRunCard` and `EntityLatestCloudbuildsForBranchCard`. diff --git a/plugins/cloudbuild/dev/index.tsx b/plugins/cloudbuild/dev/index.tsx index 264d6f801f..26fb65d151 100644 --- a/plugins/cloudbuild/dev/index.tsx +++ b/plugins/cloudbuild/dev/index.tsx @@ -14,6 +14,6 @@ * limitations under the License. */ import { createDevApp } from '@backstage/dev-utils'; -import { plugin } from '../src/plugin'; +import { cloudbuildPlugin } from '../src/plugin'; -createDevApp().registerPlugin(plugin).render(); +createDevApp().registerPlugin(cloudbuildPlugin).render(); diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index 3e5bbef3a9..7e9e13816b 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -31,6 +31,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.0", + "@backstage/plugin-catalog-react": "^0.0.1", "@backstage/core": "^0.5.0", "@backstage/theme": "^0.2.2", "@material-ui/core": "^4.11.0", diff --git a/plugins/cloudbuild/src/components/Cards/Cards.tsx b/plugins/cloudbuild/src/components/Cards/Cards.tsx index bbfb0b59c3..75ae33027e 100644 --- a/plugins/cloudbuild/src/components/Cards/Cards.tsx +++ b/plugins/cloudbuild/src/components/Cards/Cards.tsx @@ -17,6 +17,7 @@ import React, { useEffect } from 'react'; import { useWorkflowRuns } from '../useWorkflowRuns'; import { WorkflowRun, WorkflowRunsTable } from '../WorkflowRunsTable'; import { Entity } from '@backstage/catalog-model'; +import { useEntity } from '@backstage/plugin-catalog-react'; import { WorkflowRunStatus } from '../WorkflowRunStatus'; import { Link, Theme, makeStyles, LinearProgress } from '@material-ui/core'; import { @@ -72,12 +73,13 @@ const WidgetContent = ({ }; export const LatestWorkflowRunCard = ({ - entity, branch = 'master', }: { - entity: Entity; + /** @deprecated The entity is now grabbed from context instead */ + entity?: Entity; branch: string; }) => { + const { entity } = useEntity(); const errorApi = useApi(errorApiRef); const projectId = entity?.metadata.annotations?.[CLOUDBUILD_ANNOTATION] || ''; @@ -104,13 +106,17 @@ export const LatestWorkflowRunCard = ({ }; export const LatestWorkflowsForBranchCard = ({ - entity, branch = 'master', }: { - entity: Entity; + /** @deprecated The entity is now grabbed from context instead */ + entity?: Entity; branch: string; -}) => ( - - - -); +}) => { + const { entity } = useEntity(); + + return ( + + + + ); +}; diff --git a/plugins/cloudbuild/src/components/Router.tsx b/plugins/cloudbuild/src/components/Router.tsx index af4959f4be..c31947a531 100644 --- a/plugins/cloudbuild/src/components/Router.tsx +++ b/plugins/cloudbuild/src/components/Router.tsx @@ -15,6 +15,7 @@ */ import React from 'react'; import { Entity } from '@backstage/catalog-model'; +import { useEntity } from '@backstage/plugin-catalog-react'; import { Routes, Route } from 'react-router'; import { rootRouteRef, buildRouteRef } from '../plugin'; import { WorkflowRunDetails } from './WorkflowRunDetails'; @@ -25,11 +26,19 @@ import { MissingAnnotationEmptyState } from '@backstage/core'; export const isPluginApplicableToEntity = (entity: Entity) => Boolean(entity.metadata.annotations?.[CLOUDBUILD_ANNOTATION]); -export const Router = ({ entity }: { entity: Entity }) => - // TODO(shmidt-i): move warning to a separate standardized component - !isPluginApplicableToEntity(entity) ? ( - - ) : ( +type Props = { + /** @deprecated The entity is now grabbed from context instead */ + entity?: Entity; +}; + +export const Router = (_props: Props) => { + const { entity } = useEntity(); + + if (!isPluginApplicableToEntity(entity)) { + // TODO(shmidt-i): move warning to a separate standardized component + return ; + } + return ( ) ); +}; diff --git a/plugins/cloudbuild/src/index.ts b/plugins/cloudbuild/src/index.ts index 616bb6b4e7..a846792fa2 100644 --- a/plugins/cloudbuild/src/index.ts +++ b/plugins/cloudbuild/src/index.ts @@ -13,8 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { plugin } from './plugin'; +export { + cloudbuildPlugin, + cloudbuildPlugin as plugin, + EntityCloudbuildContent, + EntityLatestCloudbuildRunCard, + EntityLatestCloudbuildsForBranchCard, +} from './plugin'; export * from './api'; -export { Router, isPluginApplicableToEntity } from './components/Router'; +export { + Router, + isPluginApplicableToEntity, + isPluginApplicableToEntity as isCloudbuildAvailable, +} from './components/Router'; export * from './components/Cards'; export { CLOUDBUILD_ANNOTATION } from './components/useProjectName'; diff --git a/plugins/cloudbuild/src/plugin.test.ts b/plugins/cloudbuild/src/plugin.test.ts index dae16f1d1b..ea042a901a 100644 --- a/plugins/cloudbuild/src/plugin.test.ts +++ b/plugins/cloudbuild/src/plugin.test.ts @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { plugin } from './plugin'; +import { cloudbuildPlugin } from './plugin'; describe('cloudbuild', () => { it('should export plugin', () => { - expect(plugin).toBeDefined(); + expect(cloudbuildPlugin).toBeDefined(); }); }); diff --git a/plugins/cloudbuild/src/plugin.ts b/plugins/cloudbuild/src/plugin.ts index ee997cc7ec..f5aaf4696a 100644 --- a/plugins/cloudbuild/src/plugin.ts +++ b/plugins/cloudbuild/src/plugin.ts @@ -18,6 +18,8 @@ import { createRouteRef, createApiFactory, googleAuthApiRef, + createRoutableExtension, + createComponentExtension, } from '@backstage/core'; import { cloudbuildApiRef, CloudbuildClient } from './api'; @@ -31,7 +33,7 @@ export const buildRouteRef = createRouteRef({ title: 'Cloudbuild Run', }); -export const plugin = createPlugin({ +export const cloudbuildPlugin = createPlugin({ id: 'cloudbuild', apis: [ createApiFactory({ @@ -42,4 +44,32 @@ export const plugin = createPlugin({ }, }), ], + routes: { + entityContent: rootRouteRef, + }, }); + +export const EntityCloudbuildContent = cloudbuildPlugin.provide( + createRoutableExtension({ + component: () => import('./components/Router').then(m => m.Router), + mountPoint: rootRouteRef, + }), +); + +export const EntityLatestCloudbuildRunCard = cloudbuildPlugin.provide( + createComponentExtension({ + component: { + lazy: () => + import('./components/Cards').then(m => m.LatestWorkflowRunCard), + }, + }), +); + +export const EntityLatestCloudbuildsForBranchCard = cloudbuildPlugin.provide( + createComponentExtension({ + component: { + lazy: () => + import('./components/Cards').then(m => m.LatestWorkflowsForBranchCard), + }, + }), +); From f54025dde83bb62a251e5b5afa4c2a94a21f8c74 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 2 Feb 2021 22:18:45 +0100 Subject: [PATCH 041/110] lighthouse: grab entity from context --- plugins/lighthouse/src/Router.tsx | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/plugins/lighthouse/src/Router.tsx b/plugins/lighthouse/src/Router.tsx index 0634a0aaa0..643d30cdc2 100644 --- a/plugins/lighthouse/src/Router.tsx +++ b/plugins/lighthouse/src/Router.tsx @@ -16,6 +16,7 @@ import React from 'react'; import { Route, Routes } from 'react-router-dom'; +import { useEntity } from '@backstage/plugin-catalog-react'; import AuditList from './components/AuditList'; import AuditView, { AuditViewContent } from './components/AuditView'; import CreateAudit, { CreateAuditContent } from './components/CreateAudit'; @@ -35,15 +36,27 @@ export const Router = () => ( ); -export const EmbeddedRouter = ({ entity }: { entity: Entity }) => - !isLighthouseAvailable(entity) ? ( - - ) : ( +type Props = { + /** @deprecated The entity is now grabbed from context instead */ + entity?: Entity; +}; + +export const EmbeddedRouter = (_props: Props) => { + const { entity } = useEntity(); + + if (!isLighthouseAvailable(entity)) { + return ( + + ); + } + + return ( } /> } /> } /> ); +}; From b7fb8984c08a133b786c8fea46e26a172827f85b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 2 Feb 2021 22:24:42 +0100 Subject: [PATCH 042/110] org: update cards to grab entity from context --- .../Group/GroupProfile/GroupProfileCard.tsx | 42 ++++++++++--------- .../Group/MembersList/MembersListCard.tsx | 9 ++-- .../Cards/OwnershipCard/OwnershipCard.tsx | 11 +++-- .../User/UserProfileCard/UserProfileCard.tsx | 7 ++-- 4 files changed, 40 insertions(+), 29 deletions(-) diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx index f5a1acd5e6..3b8d93b121 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx @@ -21,7 +21,7 @@ import { RELATION_PARENT_OF, } from '@backstage/catalog-model'; import { Avatar, InfoCard } from '@backstage/core'; -import { entityRouteParams } from '@backstage/plugin-catalog-react'; +import { useEntity, entityRouteParams } from '@backstage/plugin-catalog-react'; import { Box, Grid, Link, Tooltip, Typography } from '@material-ui/core'; import AccountTreeIcon from '@material-ui/icons/AccountTree'; import EmailIcon from '@material-ui/icons/Email'; @@ -33,26 +33,29 @@ import { generatePath, Link as RouterLink } from 'react-router-dom'; const GroupLink = ({ groupName, index = 0, - entity, }: { groupName: string; index?: number; - entity: Entity; -}) => ( - <> - {index >= 1 ? ', ' : ''} - - [{groupName}] - - -); + /** @deprecated The entity is now grabbed from context instead */ + entity?: Entity; +}) => { + const { entity } = useEntity(); + return ( + <> + {index >= 1 ? ', ' : ''} + + [{groupName}] + + + ); +}; const CardTitle = ({ title }: { title: string }) => ( @@ -61,12 +64,13 @@ const CardTitle = ({ title }: { title: string }) => ( ); export const GroupProfileCard = ({ - entity: group, variant, }: { - entity: GroupEntity; + /** @deprecated The entity is now grabbed from context instead */ + entity?: GroupEntity; variant: string; }) => { + const group = useEntity().entity as GroupEntity; const { metadata: { name, description }, spec: { profile }, diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx index f644e7db7e..7f8ede2eb7 100644 --- a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx +++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx @@ -21,6 +21,7 @@ import { } from '@backstage/catalog-model'; import { Avatar, InfoCard, Progress, useApi } from '@backstage/core'; import { + useEntity, catalogApiRef, entityRouteParams, } from '@backstage/plugin-catalog-react'; @@ -105,11 +106,11 @@ const MemberComponent = ({ ); }; -export const MembersListCard = ({ - entity: groupEntity, -}: { - entity: GroupEntity; +export const MembersListCard = (_props: { + /** @deprecated The entity is now grabbed from context instead */ + entity?: GroupEntity; }) => { + const groupEntity = useEntity().entity as GroupEntity; const { metadata: { name: groupName }, spec: { profile }, diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index 60f06bd53a..80e80bddcc 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -16,7 +16,11 @@ import { Entity } from '@backstage/catalog-model'; import { InfoCard, Progress, useApi } from '@backstage/core'; -import { catalogApiRef, isOwnerOf } from '@backstage/plugin-catalog-react'; +import { + catalogApiRef, + isOwnerOf, + useEntity, +} from '@backstage/plugin-catalog-react'; import { pageTheme } from '@backstage/theme'; import { Box, @@ -113,12 +117,13 @@ const EntityCountTile = ({ }; export const OwnershipCard = ({ - entity, variant, }: { - entity: Entity; + /** @deprecated The entity is now grabbed from context instead */ + entity?: Entity; variant: string; }) => { + const { entity } = useEntity(); const catalogApi = useApi(catalogApiRef); const { loading, diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx index bdd0cd15f6..1cf7c968b3 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx @@ -19,7 +19,7 @@ import { UserEntity, } from '@backstage/catalog-model'; import { Avatar, InfoCard } from '@backstage/core'; -import { entityRouteParams } from '@backstage/plugin-catalog-react'; +import { useEntity, entityRouteParams } from '@backstage/plugin-catalog-react'; import { Box, Grid, Link, Tooltip, Typography } from '@material-ui/core'; import EmailIcon from '@material-ui/icons/Email'; import GroupIcon from '@material-ui/icons/Group'; @@ -60,12 +60,13 @@ const CardTitle = ({ title }: { title?: string }) => ) : null; export const UserProfileCard = ({ - entity: user, variant, }: { - entity: UserEntity; + /** @deprecated The entity is now grabbed from context instead */ + entity?: UserEntity; variant: string; }) => { + const user = useEntity().entity as UserEntity; const { metadata: { name: metaName }, spec: { profile }, From f5f45744e36e09b9ac21442292780bf9a6f59826 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 2 Feb 2021 23:11:22 +0100 Subject: [PATCH 043/110] github-actions: migrate to new composability API --- .changeset/selfish-years-pump.md | 5 +++ plugins/github-actions/dev/index.tsx | 4 +- plugins/github-actions/package.json | 1 + .../src/components/Cards/Cards.tsx | 21 ++++++---- .../Cards/RecentWorkflowRunsCard.tsx | 6 ++- .../github-actions/src/components/Router.tsx | 20 +++++++-- plugins/github-actions/src/index.ts | 15 ++++++- plugins/github-actions/src/plugin.test.ts | 4 +- plugins/github-actions/src/plugin.ts | 41 ++++++++++++++++++- 9 files changed, 96 insertions(+), 21 deletions(-) create mode 100644 .changeset/selfish-years-pump.md diff --git a/.changeset/selfish-years-pump.md b/.changeset/selfish-years-pump.md new file mode 100644 index 0000000000..48994a1d9e --- /dev/null +++ b/.changeset/selfish-years-pump.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-github-actions': patch +--- + +Migrate to new composability API, exporting the plugin instance as `githubActionsPlugin`, the entity content as `EntityGitHubActionsContent`, entity conditional as `isGitHubActionsAvailable`, and entity cards as `EntityLatestGitHubActionRunCard`, `EntityLatestGitHubActionsForBranchCard`, and `EntityRecentGitHubActionsRunsCard`. diff --git a/plugins/github-actions/dev/index.tsx b/plugins/github-actions/dev/index.tsx index 812a5585d4..9156224202 100644 --- a/plugins/github-actions/dev/index.tsx +++ b/plugins/github-actions/dev/index.tsx @@ -15,6 +15,6 @@ */ import { createDevApp } from '@backstage/dev-utils'; -import { plugin } from '../src/plugin'; +import { githubActionsPlugin } from '../src/plugin'; -createDevApp().registerPlugin(plugin).render(); +createDevApp().registerPlugin(githubActionsPlugin).render(); diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index beb35b3cb0..33378c2022 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -33,6 +33,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.0", + "@backstage/plugin-catalog-react": "^0.0.1", "@backstage/core": "^0.5.0", "@backstage/integration": "^0.3.1", "@backstage/theme": "^0.2.2", diff --git a/plugins/github-actions/src/components/Cards/Cards.tsx b/plugins/github-actions/src/components/Cards/Cards.tsx index f1c27cb666..202bf5aa92 100644 --- a/plugins/github-actions/src/components/Cards/Cards.tsx +++ b/plugins/github-actions/src/components/Cards/Cards.tsx @@ -17,6 +17,7 @@ import React, { useEffect } from 'react'; import { useWorkflowRuns } from '../useWorkflowRuns'; import { WorkflowRun, WorkflowRunsTable } from '../WorkflowRunsTable'; import { Entity } from '@backstage/catalog-model'; +import { useEntity } from '@backstage/plugin-catalog-react'; import { readGitHubIntegrationConfigs } from '@backstage/integration'; import { WorkflowRunStatus } from '../WorkflowRunStatus'; import { @@ -81,11 +82,11 @@ const WidgetContent = ({ }; export const LatestWorkflowRunCard = ({ - entity, branch = 'master', // Display the card full height suitable for variant, }: Props) => { + const { entity } = useEntity(); const config = useApi(configApiRef); const errorApi = useApi(errorApiRef); // TODO: Get github hostname from metadata annotation @@ -121,17 +122,21 @@ export const LatestWorkflowRunCard = ({ }; type Props = { - entity: Entity; + /** @deprecated The entity is now grabbed from context instead */ + entity?: Entity; branch: string; variant?: string; }; export const LatestWorkflowsForBranchCard = ({ - entity, branch = 'master', variant, -}: Props) => ( - - - -); +}: Props) => { + const { entity } = useEntity(); + + return ( + + + + ); +}; diff --git a/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx b/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx index aa30423c3e..4425bc8364 100644 --- a/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx +++ b/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx @@ -23,6 +23,7 @@ import { useApi, } from '@backstage/core'; import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { useEntity } from '@backstage/plugin-catalog-react'; import { Button, Link } from '@material-ui/core'; import React, { useEffect } from 'react'; import { generatePath, Link as RouterLink } from 'react-router-dom'; @@ -33,7 +34,8 @@ import { WorkflowRunStatus } from '../WorkflowRunStatus'; const firstLine = (message: string): string => message.split('\n')[0]; export type Props = { - entity: Entity; + /** @deprecated The entity is now grabbed from context instead */ + entity?: Entity; branch?: string; dense?: boolean; limit?: number; @@ -41,12 +43,12 @@ export type Props = { }; export const RecentWorkflowRunsCard = ({ - entity, branch, dense = false, limit = 5, variant, }: Props) => { + const { entity } = useEntity(); const config = useApi(configApiRef); const errorApi = useApi(errorApiRef); // TODO: Get github hostname from metadata annotation diff --git a/plugins/github-actions/src/components/Router.tsx b/plugins/github-actions/src/components/Router.tsx index e7430d40ed..e867b05b18 100644 --- a/plugins/github-actions/src/components/Router.tsx +++ b/plugins/github-actions/src/components/Router.tsx @@ -15,6 +15,7 @@ */ import React from 'react'; import { Entity } from '@backstage/catalog-model'; +import { useEntity } from '@backstage/plugin-catalog-react'; import { Routes, Route } from 'react-router'; import { rootRouteRef, buildRouteRef } from '../plugin'; import { WorkflowRunDetails } from './WorkflowRunDetails'; @@ -25,10 +26,20 @@ import { MissingAnnotationEmptyState } from '@backstage/core'; export const isPluginApplicableToEntity = (entity: Entity) => Boolean(entity.metadata.annotations?.[GITHUB_ACTIONS_ANNOTATION]); -export const Router = ({ entity }: { entity: Entity }) => - !isPluginApplicableToEntity(entity) ? ( - - ) : ( +type Props = { + /** @deprecated The entity is now grabbed from context instead */ + entity?: Entity; +}; + +export const Router = (_props: Props) => { + const { entity } = useEntity(); + + if (!isPluginApplicableToEntity(entity)) { + return ( + + ); + } + return ( ) ); +}; diff --git a/plugins/github-actions/src/index.ts b/plugins/github-actions/src/index.ts index 24fe6fc90d..6dd3b9724e 100644 --- a/plugins/github-actions/src/index.ts +++ b/plugins/github-actions/src/index.ts @@ -14,8 +14,19 @@ * limitations under the License. */ -export { plugin } from './plugin'; +export { + githubActionsPlugin, + githubActionsPlugin as plugin, + EntityGitHubActionsContent, + EntityLatestGitHubActionRunCard, + EntityLatestGitHubActionsForBranchCard, + EntityRecentGitHubActionsRunsCard, +} from './plugin'; export * from './api'; -export { Router, isPluginApplicableToEntity } from './components/Router'; +export { + Router, + isPluginApplicableToEntity, + isPluginApplicableToEntity as isGitHubActionsAvailable, +} from './components/Router'; export * from './components/Cards'; export { GITHUB_ACTIONS_ANNOTATION } from './components/useProjectName'; diff --git a/plugins/github-actions/src/plugin.test.ts b/plugins/github-actions/src/plugin.test.ts index fa1075cbc8..53bc96c77a 100644 --- a/plugins/github-actions/src/plugin.test.ts +++ b/plugins/github-actions/src/plugin.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { plugin } from './plugin'; +import { githubActionsPlugin } from './plugin'; describe('github-actions', () => { it('should export plugin', () => { - expect(plugin).toBeDefined(); + expect(githubActionsPlugin).toBeDefined(); }); }); diff --git a/plugins/github-actions/src/plugin.ts b/plugins/github-actions/src/plugin.ts index 39ffc468b1..71c05745d4 100644 --- a/plugins/github-actions/src/plugin.ts +++ b/plugins/github-actions/src/plugin.ts @@ -20,6 +20,8 @@ import { createRouteRef, createApiFactory, githubAuthApiRef, + createRoutableExtension, + createComponentExtension, } from '@backstage/core'; import { githubActionsApiRef, GithubActionsClient } from './api'; @@ -34,7 +36,7 @@ export const buildRouteRef = createRouteRef({ title: 'GitHub Actions Workflow Run', }); -export const plugin = createPlugin({ +export const githubActionsPlugin = createPlugin({ id: 'github-actions', apis: [ createApiFactory({ @@ -44,4 +46,41 @@ export const plugin = createPlugin({ new GithubActionsClient({ configApi, githubAuthApi }), }), ], + routes: { + entityContent: rootRouteRef, + }, }); + +export const EntityGitHubActionsContent = githubActionsPlugin.provide( + createRoutableExtension({ + component: () => import('./components/Router').then(m => m.Router), + mountPoint: rootRouteRef, + }), +); + +export const EntityLatestGitHubActionRunCard = githubActionsPlugin.provide( + createComponentExtension({ + component: { + lazy: () => + import('./components/Cards').then(m => m.LatestWorkflowRunCard), + }, + }), +); + +export const EntityLatestGitHubActionsForBranchCard = githubActionsPlugin.provide( + createComponentExtension({ + component: { + lazy: () => + import('./components/Cards').then(m => m.LatestWorkflowsForBranchCard), + }, + }), +); + +export const EntityRecentGitHubActionsRunsCard = githubActionsPlugin.provide( + createComponentExtension({ + component: { + lazy: () => + import('./components/Cards').then(m => m.RecentWorkflowRunsCard), + }, + }), +); From 025c0c7bf8b8ecfaf368243a8e21c52ed3bfe643 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 2 Feb 2021 23:30:10 +0100 Subject: [PATCH 044/110] jenkins: migrate to new composability API --- .changeset/popular-cars-eat.md | 5 +++++ plugins/jenkins/dev/index.tsx | 4 ++-- plugins/jenkins/src/components/Router.tsx | 18 ++++++++++++++---- plugins/jenkins/src/index.ts | 13 +++++++++++-- plugins/jenkins/src/plugin.test.ts | 4 ++-- plugins/jenkins/src/plugin.ts | 21 ++++++++++++++++++++- 6 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 .changeset/popular-cars-eat.md diff --git a/.changeset/popular-cars-eat.md b/.changeset/popular-cars-eat.md new file mode 100644 index 0000000000..6d3bcdfab2 --- /dev/null +++ b/.changeset/popular-cars-eat.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-jenkins': patch +--- + +Migrate to new composability API, exporting the plugin instance as `jenkinsPlugin`, the entity content as `EntityJenkinsContent`, the entity conditional as `isJenkinsAvailable`, and the entity card as `EntityLatestJenkinsRunCard`. diff --git a/plugins/jenkins/dev/index.tsx b/plugins/jenkins/dev/index.tsx index 812a5585d4..8beb6eabc1 100644 --- a/plugins/jenkins/dev/index.tsx +++ b/plugins/jenkins/dev/index.tsx @@ -15,6 +15,6 @@ */ import { createDevApp } from '@backstage/dev-utils'; -import { plugin } from '../src/plugin'; +import { jenkinsPlugin } from '../src/plugin'; -createDevApp().registerPlugin(plugin).render(); +createDevApp().registerPlugin(jenkinsPlugin).render(); diff --git a/plugins/jenkins/src/components/Router.tsx b/plugins/jenkins/src/components/Router.tsx index 91bcde2a8b..e310cd4193 100644 --- a/plugins/jenkins/src/components/Router.tsx +++ b/plugins/jenkins/src/components/Router.tsx @@ -15,6 +15,7 @@ */ import React from 'react'; import { Route, Routes } from 'react-router'; +import { useEntity } from '@backstage/plugin-catalog-react'; import { buildRouteRef, rootRouteRef } from '../plugin'; import { DetailedViewPage } from './BuildWithStepsPage/'; import { JENKINS_ANNOTATION } from '../constants'; @@ -25,10 +26,19 @@ import { CITable } from './BuildsPage/lib/CITable'; export const isPluginApplicableToEntity = (entity: Entity) => Boolean(entity.metadata.annotations?.[JENKINS_ANNOTATION]); -export const Router = ({ entity }: { entity: Entity }) => { - return !isPluginApplicableToEntity(entity) ? ( - - ) : ( +type Props = { + /** @deprecated The entity is now grabbed from context instead */ + entity?: Entity; +}; + +export const Router = (_props: Props) => { + const { entity } = useEntity(); + + if (!isPluginApplicableToEntity(entity)) { + return ; + } + + return ( } /> } /> diff --git a/plugins/jenkins/src/index.ts b/plugins/jenkins/src/index.ts index 30fa0c70a5..6d0e7837d3 100644 --- a/plugins/jenkins/src/index.ts +++ b/plugins/jenkins/src/index.ts @@ -14,8 +14,17 @@ * limitations under the License. */ -export { plugin } from './plugin'; +export { + jenkinsPlugin, + jenkinsPlugin as plugin, + EntityJenkinsContent, + EntityLatestJenkinsRunCard, +} from './plugin'; export { LatestRunCard } from './components/Cards'; -export { Router, isPluginApplicableToEntity } from './components/Router'; +export { + Router, + isPluginApplicableToEntity, + isPluginApplicableToEntity as isJenkinsAvailable, +} from './components/Router'; export { JENKINS_ANNOTATION } from './constants'; export * from './api'; diff --git a/plugins/jenkins/src/plugin.test.ts b/plugins/jenkins/src/plugin.test.ts index 8c3f057ecb..0498b80b90 100644 --- a/plugins/jenkins/src/plugin.test.ts +++ b/plugins/jenkins/src/plugin.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { plugin } from './plugin'; +import { jenkinsPlugin } from './plugin'; describe('jenkins', () => { it('should export plugin', () => { - expect(plugin).toBeDefined(); + expect(jenkinsPlugin).toBeDefined(); }); }); diff --git a/plugins/jenkins/src/plugin.ts b/plugins/jenkins/src/plugin.ts index 23c54743a3..85001d9bbc 100644 --- a/plugins/jenkins/src/plugin.ts +++ b/plugins/jenkins/src/plugin.ts @@ -19,6 +19,8 @@ import { createRouteRef, createApiFactory, discoveryApiRef, + createRoutableExtension, + createComponentExtension, } from '@backstage/core'; import { jenkinsApiRef, JenkinsApi } from './api'; @@ -32,7 +34,7 @@ export const buildRouteRef = createRouteRef({ title: 'Jenkins run', }); -export const plugin = createPlugin({ +export const jenkinsPlugin = createPlugin({ id: 'jenkins', apis: [ createApiFactory({ @@ -41,4 +43,21 @@ export const plugin = createPlugin({ factory: ({ discoveryApi }) => new JenkinsApi({ discoveryApi }), }), ], + routes: { + entityContent: rootRouteRef, + }, }); + +export const EntityJenkinsContent = jenkinsPlugin.provide( + createRoutableExtension({ + component: () => import('./components/Router').then(m => m.Router), + mountPoint: rootRouteRef, + }), +); +export const EntityLatestJenkinsRunCard = jenkinsPlugin.provide( + createComponentExtension({ + component: { + lazy: () => import('./components/Cards').then(m => m.LatestRunCard), + }, + }), +); From 7716d1d709d3258a86ca3f9cd1ca2d5536e7f805 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 2 Feb 2021 23:47:25 +0100 Subject: [PATCH 045/110] kafka: migrate to new composability API --- .changeset/sixty-lemons-agree.md | 5 +++++ plugins/kafka/dev/index.tsx | 4 ++-- plugins/kafka/src/Router.tsx | 23 ++++++++++++++++++----- plugins/kafka/src/index.ts | 12 ++++++++++-- plugins/kafka/src/plugin.test.ts | 4 ++-- plugins/kafka/src/plugin.ts | 13 ++++++++++++- 6 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 .changeset/sixty-lemons-agree.md diff --git a/.changeset/sixty-lemons-agree.md b/.changeset/sixty-lemons-agree.md new file mode 100644 index 0000000000..7fc438b433 --- /dev/null +++ b/.changeset/sixty-lemons-agree.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kafka': patch +--- + +Migrate to new composability API, exporting the plugin instance as `kafkaPlugin`, entity content as `EntityKafkaContent`, and entity conditional as `isKafkaAvailable`. diff --git a/plugins/kafka/dev/index.tsx b/plugins/kafka/dev/index.tsx index 264d6f801f..5506d47026 100644 --- a/plugins/kafka/dev/index.tsx +++ b/plugins/kafka/dev/index.tsx @@ -14,6 +14,6 @@ * limitations under the License. */ import { createDevApp } from '@backstage/dev-utils'; -import { plugin } from '../src/plugin'; +import { kafkaPlugin } from '../src/plugin'; -createDevApp().registerPlugin(plugin).render(); +createDevApp().registerPlugin(kafkaPlugin).render(); diff --git a/plugins/kafka/src/Router.tsx b/plugins/kafka/src/Router.tsx index b445f8f408..13503fefe8 100644 --- a/plugins/kafka/src/Router.tsx +++ b/plugins/kafka/src/Router.tsx @@ -17,7 +17,7 @@ import { Entity } from '@backstage/catalog-model'; import React from 'react'; import { Route, Routes } from 'react-router'; - +import { useEntity } from '@backstage/plugin-catalog-react'; import { rootCatalogKafkaRouteRef } from './plugin'; import { KAFKA_CONSUMER_GROUP_ANNOTATION } from './constants'; import { KafkaTopicsForConsumer } from './components/ConsumerGroupOffsets/ConsumerGroupOffsets'; @@ -26,10 +26,23 @@ import { MissingAnnotationEmptyState } from '@backstage/core'; export const isPluginApplicableToEntity = (entity: Entity) => Boolean(entity.metadata.annotations?.[KAFKA_CONSUMER_GROUP_ANNOTATION]); -export const Router = ({ entity }: { entity: Entity }) => { - return !isPluginApplicableToEntity(entity) ? ( - - ) : ( +type Props = { + /** @deprecated The entity is now grabbed from context instead */ + entity?: Entity; +}; + +export const Router = (_props: Props) => { + const { entity } = useEntity(); + + if (!isPluginApplicableToEntity(entity)) { + return ( + + ); + } + + return ( { it('should export plugin', () => { - expect(plugin).toBeDefined(); + expect(kafkaPlugin).toBeDefined(); }); }); diff --git a/plugins/kafka/src/plugin.ts b/plugins/kafka/src/plugin.ts index 878a7f56e9..832099a30c 100644 --- a/plugins/kafka/src/plugin.ts +++ b/plugins/kafka/src/plugin.ts @@ -16,6 +16,7 @@ import { createApiFactory, createPlugin, + createRoutableExtension, createRouteRef, discoveryApiRef, } from '@backstage/core'; @@ -27,7 +28,7 @@ export const rootCatalogKafkaRouteRef = createRouteRef({ title: 'Kafka', }); -export const plugin = createPlugin({ +export const kafkaPlugin = createPlugin({ id: 'kafka', apis: [ createApiFactory({ @@ -36,4 +37,14 @@ export const plugin = createPlugin({ factory: ({ discoveryApi }) => new KafkaBackendClient({ discoveryApi }), }), ], + routes: { + entityContent: rootCatalogKafkaRouteRef, + }, }); + +export const EntityKafkaContent = kafkaPlugin.provide( + createRoutableExtension({ + component: () => import('./Router').then(m => m.Router), + mountPoint: rootCatalogKafkaRouteRef, + }), +); From 64b9efac2e12a8523451e98f8ce12055beb61ef5 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 2 Feb 2021 23:51:30 +0100 Subject: [PATCH 046/110] kubernets: migrate to new composability API --- .changeset/shy-maps-guess.md | 5 +++++ plugins/kubernetes/dev/index.tsx | 4 ++-- plugins/kubernetes/package.json | 1 + plugins/kubernetes/src/Router.tsx | 11 +++++++++-- plugins/kubernetes/src/index.ts | 6 +++++- plugins/kubernetes/src/plugin.test.ts | 4 ++-- plugins/kubernetes/src/plugin.ts | 13 ++++++++++++- 7 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 .changeset/shy-maps-guess.md diff --git a/.changeset/shy-maps-guess.md b/.changeset/shy-maps-guess.md new file mode 100644 index 0000000000..2a50d3d461 --- /dev/null +++ b/.changeset/shy-maps-guess.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes': patch +--- + +Migrate to new composability API, exporting the plugin instance as `kubernetesPlugin` and entity content as `EntityKubernetesContent`. diff --git a/plugins/kubernetes/dev/index.tsx b/plugins/kubernetes/dev/index.tsx index f352fb9c34..de93d21348 100644 --- a/plugins/kubernetes/dev/index.tsx +++ b/plugins/kubernetes/dev/index.tsx @@ -14,6 +14,6 @@ * limitations under the License. */ import { createDevApp } from '@backstage/dev-utils'; -import { plugin } from '../src'; +import { kubernetesPlugin } from '../src'; -createDevApp().registerPlugin(plugin).render(); +createDevApp().registerPlugin(kubernetesPlugin).render(); diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 1d985c0d10..3dc53a9ea4 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -32,6 +32,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.0", + "@backstage/plugin-catalog-react": "^0.0.1", "@backstage/config": "^0.1.2", "@backstage/core": "^0.5.0", "@backstage/plugin-kubernetes-backend": "^0.2.6", diff --git a/plugins/kubernetes/src/Router.tsx b/plugins/kubernetes/src/Router.tsx index 20a4c04285..36851b3c46 100644 --- a/plugins/kubernetes/src/Router.tsx +++ b/plugins/kubernetes/src/Router.tsx @@ -16,15 +16,22 @@ import React from 'react'; import { Entity } from '@backstage/catalog-model'; +import { useEntity } from '@backstage/plugin-catalog-react'; import { Route, Routes } from 'react-router-dom'; - import { rootCatalogKubernetesRouteRef } from './plugin'; import { KubernetesContent } from './components/KubernetesContent'; import { MissingAnnotationEmptyState } from '@backstage/core'; const KUBERNETES_ANNOTATION = 'backstage.io/kubernetes-id'; -export const Router = ({ entity }: { entity: Entity }) => { +type Props = { + /** @deprecated The entity is now grabbed from context instead */ + entity?: Entity; +}; + +export const Router = (_props: Props) => { + const { entity } = useEntity(); + const kubernetesAnnotationValue = entity.metadata.annotations?.[KUBERNETES_ANNOTATION]; diff --git a/plugins/kubernetes/src/index.ts b/plugins/kubernetes/src/index.ts index 8b5969666c..eca6b068f1 100644 --- a/plugins/kubernetes/src/index.ts +++ b/plugins/kubernetes/src/index.ts @@ -13,5 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { plugin } from './plugin'; +export { + kubernetesPlugin, + kubernetesPlugin as plugin, + EntityKubernetesContent, +} from './plugin'; export { Router } from './Router'; diff --git a/plugins/kubernetes/src/plugin.test.ts b/plugins/kubernetes/src/plugin.test.ts index 20c50a813a..f176b7f451 100644 --- a/plugins/kubernetes/src/plugin.test.ts +++ b/plugins/kubernetes/src/plugin.test.ts @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { plugin } from './plugin'; +import { kubernetesPlugin } from './plugin'; describe('kubernetes', () => { it('should export plugin', () => { - expect(plugin).toBeDefined(); + expect(kubernetesPlugin).toBeDefined(); }); }); diff --git a/plugins/kubernetes/src/plugin.ts b/plugins/kubernetes/src/plugin.ts index cff9e1468b..af29df3dcc 100644 --- a/plugins/kubernetes/src/plugin.ts +++ b/plugins/kubernetes/src/plugin.ts @@ -19,6 +19,7 @@ import { createRouteRef, discoveryApiRef, googleAuthApiRef, + createRoutableExtension, } from '@backstage/core'; import { KubernetesBackendClient } from './api/KubernetesBackendClient'; import { kubernetesApiRef } from './api/types'; @@ -30,7 +31,7 @@ export const rootCatalogKubernetesRouteRef = createRouteRef({ title: 'Kubernetes', }); -export const plugin = createPlugin({ +export const kubernetesPlugin = createPlugin({ id: 'kubernetes', apis: [ createApiFactory({ @@ -47,4 +48,14 @@ export const plugin = createPlugin({ }, }), ], + routes: { + entityContent: rootCatalogKubernetesRouteRef, + }, }); + +export const EntityKubernetesContent = kubernetesPlugin.provide( + createRoutableExtension({ + component: () => import('./Router').then(m => m.Router), + mountPoint: rootCatalogKubernetesRouteRef, + }), +); From c5ab91ce3c697da8453123e64ad574c692356fe1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 2 Feb 2021 23:57:50 +0100 Subject: [PATCH 047/110] newrelic: migrate to new composability API --- .changeset/shiny-falcons-marry.md | 5 +++++ plugins/newrelic/dev/index.tsx | 4 ++-- plugins/newrelic/src/index.ts | 6 +++++- plugins/newrelic/src/plugin.test.ts | 4 ++-- plugins/newrelic/src/plugin.ts | 14 +++++++++++++- 5 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 .changeset/shiny-falcons-marry.md diff --git a/.changeset/shiny-falcons-marry.md b/.changeset/shiny-falcons-marry.md new file mode 100644 index 0000000000..4dda68d693 --- /dev/null +++ b/.changeset/shiny-falcons-marry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-newrelic': patch +--- + +Migrate to new composability API, exporting the plugin instance as `newRelicPlugin`, and the root page as `NewRelicPage`. diff --git a/plugins/newrelic/dev/index.tsx b/plugins/newrelic/dev/index.tsx index 812a5585d4..9ca421f94a 100644 --- a/plugins/newrelic/dev/index.tsx +++ b/plugins/newrelic/dev/index.tsx @@ -15,6 +15,6 @@ */ import { createDevApp } from '@backstage/dev-utils'; -import { plugin } from '../src/plugin'; +import { newRelicPlugin } from '../src/plugin'; -createDevApp().registerPlugin(plugin).render(); +createDevApp().registerPlugin(newRelicPlugin).render(); diff --git a/plugins/newrelic/src/index.ts b/plugins/newrelic/src/index.ts index 3a0a0fe2d3..aa4e990d6c 100644 --- a/plugins/newrelic/src/index.ts +++ b/plugins/newrelic/src/index.ts @@ -14,4 +14,8 @@ * limitations under the License. */ -export { plugin } from './plugin'; +export { + newRelicPlugin, + newRelicPlugin as plugin, + NewRelicPage, +} from './plugin'; diff --git a/plugins/newrelic/src/plugin.test.ts b/plugins/newrelic/src/plugin.test.ts index 17429a95e0..f2e8fde924 100644 --- a/plugins/newrelic/src/plugin.test.ts +++ b/plugins/newrelic/src/plugin.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { plugin } from './plugin'; +import { newRelicPlugin } from './plugin'; describe('newrelic', () => { it('should export plugin', () => { - expect(plugin).toBeDefined(); + expect(newRelicPlugin).toBeDefined(); }); }); diff --git a/plugins/newrelic/src/plugin.ts b/plugins/newrelic/src/plugin.ts index 5f5ba88617..d89aa813e9 100644 --- a/plugins/newrelic/src/plugin.ts +++ b/plugins/newrelic/src/plugin.ts @@ -19,6 +19,7 @@ import { createPlugin, createRouteRef, discoveryApiRef, + createRoutableExtension, } from '@backstage/core'; import { NewRelicClient, newRelicApiRef } from './api'; import NewRelicComponent from './components/NewRelicComponent'; @@ -28,7 +29,7 @@ export const rootRouteRef = createRouteRef({ title: 'newrelic', }); -export const plugin = createPlugin({ +export const newRelicPlugin = createPlugin({ id: 'newrelic', apis: [ createApiFactory({ @@ -40,4 +41,15 @@ export const plugin = createPlugin({ register({ router }) { router.addRoute(rootRouteRef, NewRelicComponent); }, + routes: { + root: rootRouteRef, + }, }); + +export const NewRelicPage = newRelicPlugin.provide( + createRoutableExtension({ + component: () => + import('./components/NewRelicComponent').then(m => m.default), + mountPoint: rootRouteRef, + }), +); From f20f0465141f676f42b39ed467f9eaac5439fe2f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 3 Feb 2021 00:03:07 +0100 Subject: [PATCH 048/110] github-actions: fix card tests --- .../src/components/Cards/RecentWorkflowRunsCard.test.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.test.tsx b/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.test.tsx index 0deb4ae39d..18cc59439b 100644 --- a/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.test.tsx +++ b/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.test.tsx @@ -22,6 +22,7 @@ import { ConfigApi, ConfigReader, } from '@backstage/core'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; import { lightTheme } from '@backstage/theme'; import { ThemeProvider } from '@material-ui/core'; import { render } from '@testing-library/react'; @@ -84,7 +85,9 @@ describe('', () => { configApi, )} > - + + + , From d8386f780a6c60371c5e213584e14683482930f2 Mon Sep 17 00:00:00 2001 From: Manoj - Date: Wed, 3 Feb 2021 16:54:44 +1100 Subject: [PATCH 049/110] added condition handling the case - bitbucket.org --- packages/integration/src/bitbucket/core.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/integration/src/bitbucket/core.ts b/packages/integration/src/bitbucket/core.ts index f22c727f63..4359762676 100644 --- a/packages/integration/src/bitbucket/core.ts +++ b/packages/integration/src/bitbucket/core.ts @@ -38,7 +38,7 @@ export async function getBitbucketDefaultBranch( var response = await fetch(branchUrl, getBitbucketRequestOptions(config)); - if (response.status === 404) { + if (response.status === 404 && !isHosted) { // First try the new format, and then if it gets specifically a 404 it should try the old format // (to support old Atlassian Bitbucket v5.11.1 format ) branchUrl = `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches/default`; From 2a3d7599e60870e292e949177773da40cd2bb1e9 Mon Sep 17 00:00:00 2001 From: Manoj - Date: Wed, 3 Feb 2021 18:59:25 +1100 Subject: [PATCH 050/110] added test cases to include both the endpoints --- .../integration/src/bitbucket/core.test.ts | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/integration/src/bitbucket/core.test.ts b/packages/integration/src/bitbucket/core.test.ts index 1287ab4d66..5bb06bcfa3 100644 --- a/packages/integration/src/bitbucket/core.test.ts +++ b/packages/integration/src/bitbucket/core.test.ts @@ -111,6 +111,7 @@ describe('bitbucket core', () => { describe('getBitbucketDownloadUrl', () => { it('add path param if a path is specified for Bitbucket Server', async () => { + const defaultBranchResponse = { displayId: 'main', }; @@ -125,6 +126,7 @@ describe('bitbucket core', () => { ), ), ); + const config: BitbucketIntegrationConfig = { host: 'bitbucket.mycompany.net', apiBaseUrl: 'https://api.bitbucket.mycompany.net/rest/api/1.0', @@ -249,6 +251,42 @@ describe('bitbucket core', () => { config, ); expect(defaultBranch).toEqual('main'); - }); + }); + + it('return default branch for Bitbucket Server for bitbucket version 5.11', async () => { + const defaultBranchResponse = { + displayId: 'main', + }; + worker.use( + rest.get( + 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/default-branch', + (_, res, ctx) => + res( + ctx.status(404), + ctx.set('Content-Type', 'application/json'), + ctx.json(defaultBranchResponse), + ), + ), + rest.get( + 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/branches/default', + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(defaultBranchResponse), + ), + ), + ); + const config: BitbucketIntegrationConfig = { + host: 'bitbucket.mycompany.net', + apiBaseUrl: 'https://api.bitbucket.mycompany.net/rest/api/1.0', + }; + const defaultBranch = await getBitbucketDefaultBranch( + 'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/README.md', + config, + ); + expect(defaultBranch).toEqual('main'); + }); + }); }); From 2e62aea6fbc190ca9f791030809d0891aadc5a0b Mon Sep 17 00:00:00 2001 From: Kaparwan Manoj Date: Wed, 3 Feb 2021 19:25:43 +1100 Subject: [PATCH 051/110] changeset created --- .changeset/wicked-boxes-taste.md | 5 ++ package.json | 2 +- yarn.lock | 96 +++++++++++++++++++++++++------- 3 files changed, 81 insertions(+), 22 deletions(-) create mode 100644 .changeset/wicked-boxes-taste.md diff --git a/.changeset/wicked-boxes-taste.md b/.changeset/wicked-boxes-taste.md new file mode 100644 index 0000000000..a875d1c50d --- /dev/null +++ b/.changeset/wicked-boxes-taste.md @@ -0,0 +1,5 @@ +--- +'@backstage/integration': major +--- + +#4322 bitbucket own hosted v5.11.1 branchUrl fix and enabled error tracing… #4347 diff --git a/package.json b/package.json index 758a8ba884..fd130fcbbd 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ }, "version": "1.0.0", "devDependencies": { - "@changesets/cli": "^2.11.0", + "@changesets/cli": "^2.14.0", "@octokit/openapi-types": "^2.2.0", "@spotify/eslint-config-oss": "^1.0.1", "@spotify/prettier-config": "^9.0.0", diff --git a/yarn.lock b/yarn.lock index 2ac7dffa6a..3626741854 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2655,21 +2655,22 @@ resolved "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-5.0.0.tgz#3ba791f37b90e7f6170d252b63aacfcae943c039" integrity sha512-WmKrB/575EJCzbeSJR3YQ5sET5FaizeljLRw1382qVUeGqzuWBgIS+AF5a0FO51uQTrDpoRgvuHC2IWVsgwkkA== -"@changesets/apply-release-plan@^4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-4.0.0.tgz#e78efb56a4e459a8dab814ba43045f2ace0f27c9" - integrity sha512-MrcUd8wIlQ4S/PznzqJVsmnEpUGfPEkCGF54iqt8G05GEqi/zuxpoTfebcScpj5zeiDyxFIcA9RbeZ3pvJJxoA== +"@changesets/apply-release-plan@^4.2.0": + version "4.2.0" + resolved "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-4.2.0.tgz#f1005815f27c3238f66507e90c6ae14d8fc62b41" + integrity sha512-/vt6UwgQldhOw93Gb8llI5OuYGlJt2+U45AfcXsoxzl8gZzCmChGm3vUaQJYbmtL8TbL8OOVXHRIKJJidMNPKw== dependencies: - "@babel/runtime" "^7.4.4" - "@changesets/config" "^1.2.0" + "@babel/runtime" "^7.10.4" + "@changesets/config" "^1.5.0" "@changesets/get-version-range-type" "^0.3.2" "@changesets/git" "^1.0.5" - "@changesets/types" "^3.1.0" + "@changesets/types" "^3.3.0" "@manypkg/get-packages" "^1.0.1" + detect-indent "^6.0.0" fs-extra "^7.0.1" lodash.startcase "^4.4.0" outdent "^0.5.0" - prettier "^1.18.2" + prettier "^1.19.1" resolve-from "^5.0.0" semver "^5.4.1" @@ -2685,23 +2686,35 @@ "@manypkg/get-packages" "^1.0.1" semver "^5.4.1" -"@changesets/cli@^2.11.0": - version "2.12.0" - resolved "https://registry.npmjs.org/@changesets/cli/-/cli-2.12.0.tgz#26124b051e6ce6dcc5aa4595588c8cb2ce3e4363" - integrity sha512-dGdFkg75zsaEObsG8gwMLglS6sJVjXWwgVTAzEIjqIoWVnKwqZqccTb4gn0noq47uCwy7SqxiAJqGibIy9UOKw== +"@changesets/assemble-release-plan@^4.1.0": + version "4.1.0" + resolved "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-4.1.0.tgz#091e5e768dfe51835937e71d1ebaca1c9d6de55b" + integrity sha512-dMqe2L5Pn4UGTW89kOuuCuZD3pQFZj1Sxv92ZW4S98sXGsxcb2PdW+PeHbQ7tawkCYCOvzhXxAlN4OdF2DlDKQ== dependencies: "@babel/runtime" "^7.10.4" - "@changesets/apply-release-plan" "^4.0.0" - "@changesets/assemble-release-plan" "^4.0.0" - "@changesets/config" "^1.4.0" "@changesets/errors" "^0.1.4" - "@changesets/get-dependents-graph" "^1.1.3" + "@changesets/get-dependents-graph" "^1.2.0" + "@changesets/types" "^3.3.0" + "@manypkg/get-packages" "^1.0.1" + semver "^5.4.1" + +"@changesets/cli@^2.14.0": + version "2.14.0" + resolved "https://registry.npmjs.org/@changesets/cli/-/cli-2.14.0.tgz#b8d1d33d832c640ce0b95333bbd8d5ac5b9c9824" + integrity sha512-rbQMRDXl1cXOglnjUvYyrFLlYBbS0YZdfxZfW3ZbGLzLoS4n50+B9fLSE9oW20hQuL3zAnyLyacb9cwNhF2lig== + dependencies: + "@babel/runtime" "^7.10.4" + "@changesets/apply-release-plan" "^4.2.0" + "@changesets/assemble-release-plan" "^4.1.0" + "@changesets/config" "^1.5.0" + "@changesets/errors" "^0.1.4" + "@changesets/get-dependents-graph" "^1.2.0" "@changesets/get-release-plan" "^2.0.1" - "@changesets/git" "^1.0.6" + "@changesets/git" "^1.1.0" "@changesets/logger" "^0.0.5" "@changesets/pre" "^1.0.4" "@changesets/read" "^0.4.6" - "@changesets/types" "^3.2.0" + "@changesets/types" "^3.3.0" "@changesets/write" "^0.1.3" "@manypkg/get-packages" "^1.0.1" "@types/semver" "^6.0.0" @@ -2721,7 +2734,7 @@ term-size "^2.1.0" tty-table "^2.8.10" -"@changesets/config@^1.2.0", "@changesets/config@^1.4.0": +"@changesets/config@^1.2.0": version "1.4.0" resolved "https://registry.npmjs.org/@changesets/config/-/config-1.4.0.tgz#c157a4121f198b749f2bbc2e9015b6e976ece7d6" integrity sha512-eoTOcJ6py7jBDY8rUXwEGxR5UtvUX+p//0NhkVpPGcnvIeITHq+DOIsuWyGzWcb+1FaYkof3CCr32/komZTu4Q== @@ -2734,6 +2747,19 @@ fs-extra "^7.0.1" micromatch "^4.0.2" +"@changesets/config@^1.5.0": + version "1.5.0" + resolved "https://registry.npmjs.org/@changesets/config/-/config-1.5.0.tgz#6d58b01e45916318d3f39e9cde86a5d69dc58ed8" + integrity sha512-Bl9nLVYcwFCpd9jpzcOsExZk1NuTYX20D2YWHCdS1xll3W0yOdSUlWLUCCfugN1l3+yTR6iDW6q9o6vpCevWvA== + dependencies: + "@changesets/errors" "^0.1.4" + "@changesets/get-dependents-graph" "^1.2.0" + "@changesets/logger" "^0.0.5" + "@changesets/types" "^3.3.0" + "@manypkg/get-packages" "^1.0.1" + fs-extra "^7.0.1" + micromatch "^4.0.2" + "@changesets/errors@^0.1.4": version "0.1.4" resolved "https://registry.npmjs.org/@changesets/errors/-/errors-0.1.4.tgz#f79851746c43679a66b383fdff4c012f480f480d" @@ -2752,6 +2778,17 @@ fs-extra "^7.0.1" semver "^5.4.1" +"@changesets/get-dependents-graph@^1.2.0": + version "1.2.0" + resolved "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-1.2.0.tgz#6986927a5fec60bc6fcc76f966efae2ac30c05ed" + integrity sha512-96NInEKpEZH8KvmXyh42PynXVAdq3kQ9VjAeswHtJ3umUCeTF42b/KVXaov+5P1RNnaUVtRuEwzs4syGuowDTw== + dependencies: + "@changesets/types" "^3.3.0" + "@manypkg/get-packages" "^1.0.1" + chalk "^2.1.0" + fs-extra "^7.0.1" + semver "^5.4.1" + "@changesets/get-release-plan@^2.0.1": version "2.0.1" resolved "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-2.0.1.tgz#b95d8f1a3cc719ff4b42b9b9aae72458d8787c13" @@ -2770,7 +2807,7 @@ resolved "https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.3.2.tgz#8131a99035edd11aa7a44c341cbb05e668618c67" integrity sha512-SVqwYs5pULYjYT4op21F2pVbcrca4qA/bAA3FmFXKMN7Y+HcO8sbZUTx3TAy2VXulP2FACd1aC7f2nTuqSPbqg== -"@changesets/git@^1.0.5", "@changesets/git@^1.0.6": +"@changesets/git@^1.0.5": version "1.0.6" resolved "https://registry.npmjs.org/@changesets/git/-/git-1.0.6.tgz#057e627e5d3fcb74bf6c18d7284e130ba5a7632e" integrity sha512-e0M06XuME3W5lGhz+CO0vLc60u+hLk/pYjOx/6GXEWuQrwtGgeycFIfRgRt8qTs664o1oKtVHBbd7ItpoWgFfA== @@ -2782,6 +2819,18 @@ is-subdir "^1.1.1" spawndamnit "^2.0.0" +"@changesets/git@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@changesets/git/-/git-1.1.0.tgz#ecaa8058ac87b450c09da0e74b68e6854cd0742c" + integrity sha512-f/2rQynT+JiAL/V0V/GQdXhLkcb86ELg3UwH3fQO4wVdfUbE9NHIHq9ohJdH1Ymh0Lv48F/b38aWZ5v2sKiF3w== + dependencies: + "@babel/runtime" "^7.10.4" + "@changesets/errors" "^0.1.4" + "@changesets/types" "^3.1.1" + "@manypkg/get-packages" "^1.0.1" + is-subdir "^1.1.1" + spawndamnit "^2.0.0" + "@changesets/logger@^0.0.5": version "0.0.5" resolved "https://registry.npmjs.org/@changesets/logger/-/logger-0.0.5.tgz#68305dd5a643e336be16a2369cb17cdd8ed37d4c" @@ -2827,6 +2876,11 @@ resolved "https://registry.npmjs.org/@changesets/types/-/types-3.2.0.tgz#d8306d7219c3b19b6d860ddeb9d7374e2dd6b035" integrity sha512-rAmPtOyXpisEEE25CchKNUAf2ApyAeuZ/h78YDoqKZaCk5tUD0lgYZGPIRV9WTPoqNjJULIym37ogc6pkax5jg== +"@changesets/types@^3.3.0": + version "3.3.0" + resolved "https://registry.npmjs.org/@changesets/types/-/types-3.3.0.tgz#04cd8184b2d2da760667bd89bf9b930938dbd96e" + integrity sha512-rJamRo+OD/MQekImfIk07JZwYSB18iU6fYL8xOg0gfAiTh1a1+OlR1fPIxm55I7RsWw812is2YcPPwXdIewrhA== + "@changesets/write@^0.1.3": version "0.1.3" resolved "https://registry.npmjs.org/@changesets/write/-/write-0.1.3.tgz#00ae575af50274773d7493e77fb96838a08ad8ad" @@ -21292,7 +21346,7 @@ prepend-http@^2.0.0: resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= -prettier@^1.18.2: +prettier@^1.18.2, prettier@^1.19.1: version "1.19.1" resolved "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== From b4358c46e0b13e3bfd318d5b345aa55e1acbad39 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 3 Feb 2021 10:03:50 +0100 Subject: [PATCH 052/110] org: fix entity card tests --- .../Cards/Group/MembersList/MembersListCard.test.tsx | 11 +++++++++-- .../User/UserProfileCard/UserProfileCard.test.tsx | 7 ++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx index 6815fd0e55..28c55b376b 100644 --- a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx +++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx @@ -16,7 +16,11 @@ import { Entity, GroupEntity } from '@backstage/catalog-model'; import { ApiProvider, ApiRegistry } from '@backstage/core'; -import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; +import { + CatalogApi, + catalogApiRef, + EntityProvider, +} from '@backstage/plugin-catalog-react'; import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import React from 'react'; import { MembersListCard } from './MembersListCard'; @@ -78,7 +82,10 @@ describe('MemberTab Test', () => { const rendered = await renderWithEffects( wrapInTestApp( - + + + + , , ), ); diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx index 77708e6f27..7c7d26c228 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx @@ -15,6 +15,7 @@ */ import { UserEntity } from '@backstage/catalog-model'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import React from 'react'; import { UserProfileCard } from './UserProfileCard'; @@ -48,7 +49,11 @@ describe('UserSummary Test', () => { it('Display Profile Card', async () => { const rendered = await renderWithEffects( - wrapInTestApp(), + wrapInTestApp( + + + , + ), ); expect(rendered.getByText('calum-leavy@example.com')).toBeInTheDocument(); From 693ae8c6258d5cd923e89a981e8f2fae2a07eaff Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 3 Feb 2021 10:41:06 +0100 Subject: [PATCH 053/110] Remove date precision --- plugins/scaffolder-backend/migrations/20210120143715_init.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/migrations/20210120143715_init.js b/plugins/scaffolder-backend/migrations/20210120143715_init.js index 248b4f932f..256ec7d423 100644 --- a/plugins/scaffolder-backend/migrations/20210120143715_init.js +++ b/plugins/scaffolder-backend/migrations/20210120143715_init.js @@ -62,7 +62,7 @@ exports.up = async function up(knex) { .comment('The JSON encoded body of the event'); table.text('event_type').notNullable().comment('The type of event'); table - .timestamp('created_at', { precision: 9 }) + .timestamp('created_at') .defaultTo(knex.fn.now()) .notNullable() .comment('The timestamp when this event was generated'); From 1955c62398793a3c385de98e8667fb9e2b0d3ffe Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 3 Feb 2021 10:47:44 +0100 Subject: [PATCH 054/110] Delete unused database code --- .../scaffolder-backend/src/tasks/Database.ts | 70 ------------------- plugins/scaffolder-backend/src/tasks/types.ts | 37 ---------- 2 files changed, 107 deletions(-) delete mode 100644 plugins/scaffolder-backend/src/tasks/Database.ts delete mode 100644 plugins/scaffolder-backend/src/tasks/types.ts diff --git a/plugins/scaffolder-backend/src/tasks/Database.ts b/plugins/scaffolder-backend/src/tasks/Database.ts deleted file mode 100644 index c135380f87..0000000000 --- a/plugins/scaffolder-backend/src/tasks/Database.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2021 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 { ConflictError, resolvePackagePath } from '@backstage/backend-common'; -import Knex from 'knex'; -import { Logger } from 'winston'; -import { Database, Transaction } from './types'; - -const migrationsDir = resolvePackagePath( - '@backstage/plugin-scaffolder-backend', - 'migrations', -); - -export class CommonDatabase implements Database { - static async create(knex: Knex, logger: Logger): Promise { - await knex.migrate.latest({ - directory: migrationsDir, - }); - return new CommonDatabase(knex, logger); - } - - constructor( - private readonly database: Knex, - private readonly logger: Logger, - ) {} - - async transaction(fn: (tx: Transaction) => Promise): Promise { - try { - let result: T | undefined = undefined; - - await this.database.transaction( - async tx => { - // We can't return here, as knex swallows the return type in case the transaction is rolled back: - // https://github.com/knex/knex/blob/e37aeaa31c8ef9c1b07d2e4d3ec6607e557d800d/lib/transaction.js#L136 - result = await fn(tx); - }, - { - // If we explicitly trigger a rollback, don't fail. - doNotRejectOnRollback: true, - }, - ); - - return result!; - } catch (e) { - this.logger.debug(`Error during transaction, ${e}`); - - if ( - /SQLITE_CONSTRAINT: UNIQUE/.test(e.message) || - /unique constraint/.test(e.message) - ) { - throw new ConflictError(`Rejected due to a conflicting entity`, e); - } - - throw e; - } - } -} diff --git a/plugins/scaffolder-backend/src/tasks/types.ts b/plugins/scaffolder-backend/src/tasks/types.ts deleted file mode 100644 index 27cbe7f7f1..0000000000 --- a/plugins/scaffolder-backend/src/tasks/types.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2021 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. - */ - -/** - * The core database implementation. - */ -export interface Database { - /** - * Runs a transaction. - * - * The callback is expected to make calls back into this class. When it - * completes, the transaction is closed. - * - * @param fn The callback that implements the transaction - */ - transaction(fn: (tx: Transaction) => Promise): Promise; -} - -/** - * An abstraction for transactions of the underlying database technology. - */ -export type Transaction = { - rollback(): Promise; -}; From e5520064eabd725c038c878220522bd1aa2b3db9 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 3 Feb 2021 10:48:34 +0100 Subject: [PATCH 055/110] Drop console log --- plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts b/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts index a3fe01c96d..cb06f5a1e7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts @@ -37,7 +37,6 @@ export function registerLegacyActions( id: 'legacy:prepare', async handler(ctx) { const { logger } = ctx; - console.log(ctx); logger.info('Task claimed, waiting ...'); // Give us some time to curl observe await new Promise(resolve => setTimeout(resolve, 1000)); From ff0eed8200e0a5886f8201bf0358a1e0ded3a9bf Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 3 Feb 2021 10:51:48 +0100 Subject: [PATCH 056/110] Remove sleep from prepare step --- plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts b/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts index cb06f5a1e7..6daf1ba4f6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts @@ -36,16 +36,11 @@ export function registerLegacyActions( registry.register({ id: 'legacy:prepare', async handler(ctx) { - const { logger } = ctx; - logger.info('Task claimed, waiting ...'); - // Give us some time to curl observe - await new Promise(resolve => setTimeout(resolve, 1000)); - - logger.info('Prepare the skeleton'); const { protocol, url } = ctx.parameters; const preparer = protocol === 'file' ? new FilePreparer() : preparers.get(url as string); + ctx.logger.info('Prepare the skeleton'); await preparer.prepare({ url: url as string, logger: ctx.logger, From 1e2e86cec6025dd2e98be401c51ed2c90e963bfa Mon Sep 17 00:00:00 2001 From: Kaparwan Manoj Date: Wed, 3 Feb 2021 21:16:04 +1100 Subject: [PATCH 057/110] prettier done. --- .../integration/src/bitbucket/core.test.ts | 74 +++++++++---------- packages/integration/src/bitbucket/core.ts | 12 +-- 2 files changed, 42 insertions(+), 44 deletions(-) diff --git a/packages/integration/src/bitbucket/core.test.ts b/packages/integration/src/bitbucket/core.test.ts index 5bb06bcfa3..22b17d51fa 100644 --- a/packages/integration/src/bitbucket/core.test.ts +++ b/packages/integration/src/bitbucket/core.test.ts @@ -111,7 +111,6 @@ describe('bitbucket core', () => { describe('getBitbucketDownloadUrl', () => { it('add path param if a path is specified for Bitbucket Server', async () => { - const defaultBranchResponse = { displayId: 'main', }; @@ -126,7 +125,7 @@ describe('bitbucket core', () => { ), ), ); - + const config: BitbucketIntegrationConfig = { host: 'bitbucket.mycompany.net', apiBaseUrl: 'https://api.bitbucket.mycompany.net/rest/api/1.0', @@ -251,42 +250,41 @@ describe('bitbucket core', () => { config, ); expect(defaultBranch).toEqual('main'); - }); + }); - it('return default branch for Bitbucket Server for bitbucket version 5.11', async () => { - const defaultBranchResponse = { - displayId: 'main', - }; - worker.use( - rest.get( - 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/default-branch', - (_, res, ctx) => - res( - ctx.status(404), - ctx.set('Content-Type', 'application/json'), - ctx.json(defaultBranchResponse), - ), - ), - rest.get( - 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/branches/default', - (_, res, ctx) => - res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json(defaultBranchResponse), - ), - ), - ); - const config: BitbucketIntegrationConfig = { - host: 'bitbucket.mycompany.net', - apiBaseUrl: 'https://api.bitbucket.mycompany.net/rest/api/1.0', - }; - const defaultBranch = await getBitbucketDefaultBranch( - 'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/README.md', - config, - ); - expect(defaultBranch).toEqual('main'); - }); - + it('return default branch for Bitbucket Server for bitbucket version 5.11', async () => { + const defaultBranchResponse = { + displayId: 'main', + }; + worker.use( + rest.get( + 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/default-branch', + (_, res, ctx) => + res( + ctx.status(404), + ctx.set('Content-Type', 'application/json'), + ctx.json(defaultBranchResponse), + ), + ), + rest.get( + 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/branches/default', + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(defaultBranchResponse), + ), + ), + ); + const config: BitbucketIntegrationConfig = { + host: 'bitbucket.mycompany.net', + apiBaseUrl: 'https://api.bitbucket.mycompany.net/rest/api/1.0', + }; + const defaultBranch = await getBitbucketDefaultBranch( + 'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/README.md', + config, + ); + expect(defaultBranch).toEqual('main'); + }); }); }); diff --git a/packages/integration/src/bitbucket/core.ts b/packages/integration/src/bitbucket/core.ts index 4359762676..139e699fcb 100644 --- a/packages/integration/src/bitbucket/core.ts +++ b/packages/integration/src/bitbucket/core.ts @@ -35,16 +35,16 @@ export async function getBitbucketDefaultBranch( const branchUrl = isHosted ? `${config.apiBaseUrl}/repositories/${project}/${repoName}` : `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/default-branch`; - + var response = await fetch(branchUrl, getBitbucketRequestOptions(config)); - - if (response.status === 404 && !isHosted) { - // First try the new format, and then if it gets specifically a 404 it should try the old format - // (to support old Atlassian Bitbucket v5.11.1 format ) + + if (response.status === 404 && !isHosted) { + // First try the new format, and then if it gets specifically a 404 it should try the old format + // (to support old Atlassian Bitbucket v5.11.1 format ) branchUrl = `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches/default`; response = await fetch(branchUrl, getBitbucketRequestOptions(config)); } - + if (!response.ok) { const message = `Failed to retrieve default branch from ${branchUrl}, ${response.status} ${response.statusText}`; throw new Error(message); From 0fe62dce31496d639c619fdb058d39d271c35b33 Mon Sep 17 00:00:00 2001 From: Kaparwan Manoj Date: Wed, 3 Feb 2021 22:18:49 +1100 Subject: [PATCH 058/110] fixed lint errors --- packages/integration/src/bitbucket/core.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/integration/src/bitbucket/core.ts b/packages/integration/src/bitbucket/core.ts index 139e699fcb..8b1d7d6b18 100644 --- a/packages/integration/src/bitbucket/core.ts +++ b/packages/integration/src/bitbucket/core.ts @@ -32,11 +32,11 @@ export async function getBitbucketDefaultBranch( const isHosted = resource === 'bitbucket.org'; // Bitbucket Server https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp184 - const branchUrl = isHosted + let branchUrl = isHosted ? `${config.apiBaseUrl}/repositories/${project}/${repoName}` : `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/default-branch`; - var response = await fetch(branchUrl, getBitbucketRequestOptions(config)); + let response = await fetch(branchUrl, getBitbucketRequestOptions(config)); if (response.status === 404 && !isHosted) { // First try the new format, and then if it gets specifically a 404 it should try the old format From f620d3b698948fc8edabf9e121f68fec37bccfb6 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Wed, 3 Feb 2021 12:52:42 +0100 Subject: [PATCH 059/110] Update .changeset/wicked-boxes-taste.md --- .changeset/wicked-boxes-taste.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/wicked-boxes-taste.md b/.changeset/wicked-boxes-taste.md index a875d1c50d..3eef04825c 100644 --- a/.changeset/wicked-boxes-taste.md +++ b/.changeset/wicked-boxes-taste.md @@ -2,4 +2,4 @@ '@backstage/integration': major --- -#4322 bitbucket own hosted v5.11.1 branchUrl fix and enabled error tracing… #4347 +#4322 Bitbucket own hosted v5.11.1 branchUrl fix and enabled error tracing… #4347 From 0ec1cfe23dae7601f2703b52c8610fbd1452818d Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Wed, 3 Feb 2021 12:36:45 -0700 Subject: [PATCH 060/110] Fix incorrect dates from UTC abuse --- .../cost-insights/src/utils/change.test.ts | 6 +++--- plugins/cost-insights/src/utils/change.ts | 6 +----- plugins/cost-insights/src/utils/duration.ts | 19 ++++++------------- plugins/cost-insights/src/utils/formatters.ts | 7 ++----- plugins/cost-insights/src/utils/mockData.ts | 7 ++++--- 5 files changed, 16 insertions(+), 29 deletions(-) diff --git a/plugins/cost-insights/src/utils/change.test.ts b/plugins/cost-insights/src/utils/change.test.ts index 7cc03caa0b..9516dfe270 100644 --- a/plugins/cost-insights/src/utils/change.test.ts +++ b/plugins/cost-insights/src/utils/change.test.ts @@ -76,13 +76,13 @@ describe('getPreviousPeriodTotalCost', () => { change: changeOf(MockAggregatedDailyCosts), trendline: trendlineOf(MockAggregatedDailyCosts), }; - const exclusiveEndDate = '2020-09-30'; + const inclusiveEndDate = '2020-09-30'; expect( getPreviousPeriodTotalCost( mockGroupDailyCost.aggregation, Duration.P30D, - exclusiveEndDate, + inclusiveEndDate, ), - ).toEqual(100_000); + ).toEqual(96_600); }); }); diff --git a/plugins/cost-insights/src/utils/change.ts b/plugins/cost-insights/src/utils/change.ts index fc50439aad..92057a560e 100644 --- a/plugins/cost-insights/src/utils/change.ts +++ b/plugins/cost-insights/src/utils/change.ts @@ -22,7 +22,6 @@ import { GrowthType, MetricData, Duration, - DEFAULT_DATE_FORMAT, DateAggregation, } from '../types'; import dayjs, { OpUnitType } from 'dayjs'; @@ -73,10 +72,7 @@ export function getPreviousPeriodTotalCost( inclusiveEndDate: string, ): number { const dayjsDuration = dayjs.duration(duration); - const startDate = inclusiveStartDateOf( - duration, - dayjs(inclusiveEndDate).add(1, 'day').format(DEFAULT_DATE_FORMAT), - ); + const startDate = inclusiveStartDateOf(duration, inclusiveEndDate); // dayjs doesn't allow adding an ISO 8601 period to dates. const [amount, type]: [number, OpUnitType] = dayjsDuration.days() ? [dayjsDuration.days(), 'day'] diff --git a/plugins/cost-insights/src/utils/duration.ts b/plugins/cost-insights/src/utils/duration.ts index 79ea150fc5..27447537cc 100644 --- a/plugins/cost-insights/src/utils/duration.ts +++ b/plugins/cost-insights/src/utils/duration.ts @@ -24,23 +24,21 @@ export const DEFAULT_DURATION = Duration.P30D; * Derive the start date of a given period, assuming two repeating intervals. * * @param duration see comment on Duration enum - * @param exclusiveEndDate from CostInsightsApi.getLastCompleteBillingDate + 1 day + * @param inclusiveEndDate from CostInsightsApi.getLastCompleteBillingDate */ export function inclusiveStartDateOf( duration: Duration, - exclusiveEndDate: string, + inclusiveEndDate: string, ): string { switch (duration) { case Duration.P7D: case Duration.P30D: case Duration.P90D: - return moment(exclusiveEndDate) - .utc() + return moment(inclusiveEndDate) .subtract(moment.duration(duration).add(moment.duration(duration))) .format(DEFAULT_DATE_FORMAT); case Duration.P3M: - return moment(exclusiveEndDate) - .utc() + return moment(inclusiveEndDate) .startOf('quarter') .subtract(moment.duration(duration).add(moment.duration(duration))) .format(DEFAULT_DATE_FORMAT); @@ -57,13 +55,9 @@ export function exclusiveEndDateOf( case Duration.P7D: case Duration.P30D: case Duration.P90D: - return moment(inclusiveEndDate) - .utc() - .add(1, 'day') - .format(DEFAULT_DATE_FORMAT); + return moment(inclusiveEndDate).add(1, 'day').format(DEFAULT_DATE_FORMAT); case Duration.P3M: return moment(quarterEndDate(inclusiveEndDate)) - .utc() .add(1, 'day') .format(DEFAULT_DATE_FORMAT); default: @@ -76,7 +70,6 @@ export function inclusiveEndDateOf( inclusiveEndDate: string, ): string { return moment(exclusiveEndDateOf(duration, inclusiveEndDate)) - .utc() .subtract(1, 'day') .format(DEFAULT_DATE_FORMAT); } @@ -94,7 +87,7 @@ export function intervalsOf( } export function quarterEndDate(inclusiveEndDate: string): string { - const endDate = moment(inclusiveEndDate).utc(); + const endDate = moment(inclusiveEndDate); const endOfQuarter = endDate.endOf('quarter').format(DEFAULT_DATE_FORMAT); if (endOfQuarter === inclusiveEndDate) { return endDate.format(DEFAULT_DATE_FORMAT); diff --git a/plugins/cost-insights/src/utils/formatters.ts b/plugins/cost-insights/src/utils/formatters.ts index 182c567644..8f2b94f997 100644 --- a/plugins/cost-insights/src/utils/formatters.ts +++ b/plugins/cost-insights/src/utils/formatters.ts @@ -16,7 +16,7 @@ import moment from 'moment'; import pluralize from 'pluralize'; -import { Duration, DEFAULT_DATE_FORMAT } from '../types'; +import { Duration } from '../types'; import { inclusiveEndDateOf, inclusiveStartDateOf } from '../utils/duration'; export type Period = { @@ -92,11 +92,8 @@ export function formatPercent(n: number): string { } export function formatLastTwoLookaheadQuarters(inclusiveEndDate: string) { - const exclusiveEndDate = moment(inclusiveEndDate) - .add(1, 'day') - .format(DEFAULT_DATE_FORMAT); const start = moment( - inclusiveStartDateOf(Duration.P3M, exclusiveEndDate), + inclusiveStartDateOf(Duration.P3M, inclusiveEndDate), ).format('[Q]Q YYYY'); const end = moment(inclusiveEndDateOf(Duration.P3M, inclusiveEndDate)).format( '[Q]Q YYYY', diff --git a/plugins/cost-insights/src/utils/mockData.ts b/plugins/cost-insights/src/utils/mockData.ts index 14ff9aecd1..d4ac4cd2df 100644 --- a/plugins/cost-insights/src/utils/mockData.ts +++ b/plugins/cost-insights/src/utils/mockData.ts @@ -36,7 +36,7 @@ import { getDefaultState as getDefaultLoadingState, } from '../utils/loading'; import { findAlways } from '../utils/assert'; -import { inclusiveStartDateOf } from './duration'; +import { inclusiveEndDateOf, inclusiveStartDateOf } from './duration'; type mockAlertRenderer = (alert: T) => T; type mockEntityRenderer = (entity: T) => T; @@ -228,8 +228,9 @@ export function aggregationFor( baseline: number, ): DateAggregation[] { const { duration, endDate } = parseIntervals(intervals); + const inclusiveEndDate = inclusiveEndDateOf(duration, endDate); const days = dayjs(endDate).diff( - inclusiveStartDateOf(duration, endDate), + inclusiveStartDateOf(duration, inclusiveEndDate), 'day', ); @@ -244,7 +245,7 @@ export function aggregationFor( return [...Array(days).keys()].reduce( (values: DateAggregation[], i: number): DateAggregation[] => { const last = values.length ? values[values.length - 1].amount : baseline; - const date = dayjs(inclusiveStartDateOf(duration, endDate)) + const date = dayjs(inclusiveStartDateOf(duration, inclusiveEndDate)) .add(i, 'day') .format(DEFAULT_DATE_FORMAT); const amount = Math.max(0, last + nextDelta()); From 4c6a6dddddf1a5bd0768c2c1f30933e803e43ae9 Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Wed, 3 Feb 2021 13:30:57 -0700 Subject: [PATCH 061/110] changeset --- .changeset/cost-insights-wise-moons-crash.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cost-insights-wise-moons-crash.md diff --git a/.changeset/cost-insights-wise-moons-crash.md b/.changeset/cost-insights-wise-moons-crash.md new file mode 100644 index 0000000000..1277911059 --- /dev/null +++ b/.changeset/cost-insights-wise-moons-crash.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-cost-insights': patch +--- + +Fixed date calculations incorrectly converting to UTC in some cases. This should be a transparent change. From 11cb5ef94eb53c485893336a4690d39a185b2164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 3 Feb 2021 11:40:25 +0100 Subject: [PATCH 062/110] catalog-model: implement matchEntityWithRef for client side filtering --- .changeset/wise-lies-listen.md | 5 + packages/catalog-model/src/entity/index.ts | 1 + packages/catalog-model/src/entity/ref.test.ts | 323 +++++++++++++++++- packages/catalog-model/src/entity/ref.ts | 85 ++++- 4 files changed, 403 insertions(+), 11 deletions(-) create mode 100644 .changeset/wise-lies-listen.md diff --git a/.changeset/wise-lies-listen.md b/.changeset/wise-lies-listen.md new file mode 100644 index 0000000000..53f1edea02 --- /dev/null +++ b/.changeset/wise-lies-listen.md @@ -0,0 +1,5 @@ +--- +'@backstage/catalog-model': patch +--- + +Implement matchEntityWithRef for client side filtering of entities by ref matching diff --git a/packages/catalog-model/src/entity/index.ts b/packages/catalog-model/src/entity/index.ts index 4afb8f5c96..e80e14f7a4 100644 --- a/packages/catalog-model/src/entity/index.ts +++ b/packages/catalog-model/src/entity/index.ts @@ -27,6 +27,7 @@ export type { } from './Entity'; export * from './policies'; export { + compareEntityToRef, getEntityName, parseEntityName, parseEntityRef, diff --git a/packages/catalog-model/src/entity/ref.test.ts b/packages/catalog-model/src/entity/ref.test.ts index bd7cc9477d..5ca511ee03 100644 --- a/packages/catalog-model/src/entity/ref.test.ts +++ b/packages/catalog-model/src/entity/ref.test.ts @@ -16,7 +16,12 @@ import { ENTITY_DEFAULT_NAMESPACE } from './constants'; import { Entity } from './Entity'; -import { parseEntityName, parseEntityRef, serializeEntityRef } from './ref'; +import { + compareEntityToRef, + parseEntityName, + parseEntityRef, + serializeEntityRef, +} from './ref'; describe('ref', () => { describe('parseEntityName', () => { @@ -381,4 +386,320 @@ describe('ref', () => { ).toEqual({ kind: 'a', namespace: 'b', name: 'c/x' }); }); }); + + describe('compareEntityToRef', () => { + const entityWithNamespace: Entity = { + apiVersion: 'a', + kind: 'K', + metadata: { + name: 'n', + namespace: 'ns', + }, + }; + const entityWithoutNamespace: Entity = { + apiVersion: 'a', + kind: 'K', + metadata: { + name: 'n', + }, + }; + + it('handles matching string refs', () => { + expect(compareEntityToRef(entityWithNamespace, 'K:ns/n')).toBe(true); + expect(compareEntityToRef(entityWithNamespace, 'k:nS/N')).toBe(true); + expect( + compareEntityToRef(entityWithNamespace, 'K:n', { + defaultNamespace: 'ns', + }), + ).toBe(true); + expect( + compareEntityToRef(entityWithNamespace, 'K:n', { + defaultNamespace: 'Ns', + }), + ).toBe(true); + expect( + compareEntityToRef(entityWithNamespace, 'ns/n', { defaultKind: 'K' }), + ).toBe(true); + expect( + compareEntityToRef(entityWithNamespace, 'n', { + defaultKind: 'K', + defaultNamespace: 'ns', + }), + ).toBe(true); + expect( + compareEntityToRef(entityWithNamespace, 'N', { + defaultKind: 'k', + defaultNamespace: 'nS', + }), + ).toBe(true); + + expect(compareEntityToRef(entityWithoutNamespace, 'K:default/n')).toBe( + true, + ); + expect(compareEntityToRef(entityWithoutNamespace, 'K:deFault/n')).toBe( + true, + ); + expect( + compareEntityToRef(entityWithoutNamespace, 'K:n', { + defaultNamespace: 'default', + }), + ).toBe(true); + expect( + compareEntityToRef(entityWithoutNamespace, 'K:n', { + defaultNamespace: 'deFault', + }), + ).toBe(true); + expect(compareEntityToRef(entityWithoutNamespace, 'K:default/n')).toBe( + true, + ); + expect(compareEntityToRef(entityWithoutNamespace, 'K:n')).toBe(true); + expect( + compareEntityToRef(entityWithoutNamespace, 'default/n', { + defaultKind: 'K', + }), + ).toBe(true); + expect( + compareEntityToRef(entityWithoutNamespace, 'n', { + defaultKind: 'K', + defaultNamespace: 'default', + }), + ).toBe(true); + expect( + compareEntityToRef(entityWithoutNamespace, 'n', { + defaultKind: 'K', + }), + ).toBe(true); + }); + + it('handles mismatching string refs', () => { + expect(compareEntityToRef(entityWithNamespace, 'X:ns/n')).toBe(false); + expect( + compareEntityToRef(entityWithoutNamespace, 'ns/n', { + defaultKind: 'X', + }), + ).toBe(false); + + expect(compareEntityToRef(entityWithNamespace, 'K:xx/n')).toBe(false); + expect( + compareEntityToRef(entityWithoutNamespace, 'K:n', { + defaultNamespace: 'xx', + }), + ).toBe(false); + + expect(compareEntityToRef(entityWithNamespace, 'K:ns/x')).toBe(false); + expect( + compareEntityToRef(entityWithoutNamespace, 'x', { + defaultKind: 'K', + defaultNamespace: 'ns', + }), + ).toBe(false); + }); + + it('handles matching compound refs', () => { + expect( + compareEntityToRef(entityWithNamespace, { + kind: 'K', + namespace: 'ns', + name: 'n', + }), + ).toBe(true); + expect( + compareEntityToRef(entityWithNamespace, { + kind: 'k', + namespace: 'Ns', + name: 'N', + }), + ).toBe(true); + expect( + compareEntityToRef( + entityWithNamespace, + { kind: 'K', name: 'n' }, + { + defaultNamespace: 'ns', + }, + ), + ).toBe(true); + expect( + compareEntityToRef( + entityWithNamespace, + { namespace: 'ns', name: 'n' }, + { defaultKind: 'K' }, + ), + ).toBe(true); + expect( + compareEntityToRef(entityWithNamespace, 'n', { + defaultKind: 'K', + defaultNamespace: 'ns', + }), + ).toBe(true); + expect( + compareEntityToRef(entityWithNamespace, 'N', { + defaultKind: 'k', + defaultNamespace: 'nS', + }), + ).toBe(true); + + expect( + compareEntityToRef(entityWithoutNamespace, { + kind: 'K', + namespace: 'default', + name: 'n', + }), + ).toBe(true); + expect( + compareEntityToRef(entityWithoutNamespace, { + kind: 'k', + namespace: 'deFault', + name: 'N', + }), + ).toBe(true); + expect( + compareEntityToRef( + entityWithoutNamespace, + { kind: 'K', name: 'n' }, + { + defaultNamespace: 'default', + }, + ), + ).toBe(true); + expect( + compareEntityToRef(entityWithoutNamespace, { kind: 'K', name: 'n' }), + ).toBe(true); + expect( + compareEntityToRef( + entityWithoutNamespace, + { namespace: 'default', name: 'n' }, + { + defaultKind: 'K', + }, + ), + ).toBe(true); + expect( + compareEntityToRef( + entityWithoutNamespace, + { name: 'n' }, + { + defaultKind: 'K', + defaultNamespace: 'default', + }, + ), + ).toBe(true); + expect( + compareEntityToRef( + entityWithoutNamespace, + { name: 'N' }, + { + defaultKind: 'k', + defaultNamespace: 'defAult', + }, + ), + ).toBe(true); + expect( + compareEntityToRef( + entityWithoutNamespace, + { name: 'n' }, + { + defaultKind: 'K', + }, + ), + ).toBe(true); + }); + + it('handles mismatching compound refs', () => { + expect( + compareEntityToRef(entityWithNamespace, { + kind: 'X', + namespace: 'ns', + name: 'n', + }), + ).toBe(false); + expect( + compareEntityToRef( + entityWithNamespace, + { + namespace: 'ns', + name: 'n', + }, + { defaultKind: 'X' }, + ), + ).toBe(false); + expect( + compareEntityToRef(entityWithoutNamespace, { + kind: 'X', + namespace: 'default', + name: 'n', + }), + ).toBe(false); + expect( + compareEntityToRef( + entityWithoutNamespace, + { + namespace: 'default', + name: 'n', + }, + { defaultKind: 'X' }, + ), + ).toBe(false); + + expect( + compareEntityToRef(entityWithNamespace, { + kind: 'K', + namespace: 'xx', + name: 'n', + }), + ).toBe(false); + expect( + compareEntityToRef( + entityWithNamespace, + { + kind: 'K', + name: 'n', + }, + { defaultNamespace: 'xx' }, + ), + ).toBe(false); + expect( + compareEntityToRef(entityWithoutNamespace, { + kind: 'K', + namespace: 'xx', + name: 'n', + }), + ).toBe(false); + expect( + compareEntityToRef( + entityWithoutNamespace, + { + kind: 'K', + name: 'n', + }, + { defaultNamespace: 'xx' }, + ), + ).toBe(false); + + expect( + compareEntityToRef(entityWithNamespace, { + kind: 'K', + namespace: 'ns', + name: 'x', + }), + ).toBe(false); + expect( + compareEntityToRef(entityWithoutNamespace, { + kind: 'K', + namespace: 'default', + name: 'x', + }), + ).toBe(false); + expect( + compareEntityToRef( + entityWithoutNamespace, + { + kind: 'K', + name: 'x', + }, + { defaultNamespace: 'default' }, + ), + ).toBe(false); + }); + }); }); diff --git a/packages/catalog-model/src/entity/ref.ts b/packages/catalog-model/src/entity/ref.ts index 9f6f175d8d..bf34962d01 100644 --- a/packages/catalog-model/src/entity/ref.ts +++ b/packages/catalog-model/src/entity/ref.ts @@ -18,6 +18,27 @@ import { EntityName, EntityRef } from '../types'; import { ENTITY_DEFAULT_NAMESPACE } from './constants'; import { Entity } from './Entity'; +function parseRefString( + ref: string, +): { + kind?: string; + namespace?: string; + name: string; +} { + const match = /^([^:/]+:)?([^:/]+\/)?([^:/]+)$/.exec(ref.trim()); + if (!match) { + throw new TypeError( + `Entity reference "${ref}" was not on the form [:][/]`, + ); + } + + return { + kind: match[1]?.slice(0, -1), + namespace: match[2]?.slice(0, -1), + name: match[3], + }; +} + /** * Extracts the kind, namespace and name that form the name triplet of the * given entity. @@ -121,17 +142,11 @@ export function parseEntityRef( } if (typeof ref === 'string') { - const match = /^([^:/]+:)?([^:/]+\/)?([^:/]+)$/.exec(ref.trim()); - if (!match) { - throw new Error( - `Entity reference "${ref}" was not on the form [:][/]`, - ); - } - + const parsed = parseRefString(ref); return { - kind: match[1]?.slice(0, -1) ?? context.defaultKind, - namespace: match[2]?.slice(0, -1) ?? context.defaultNamespace, - name: match[3], + kind: parsed.kind ?? context.defaultKind, + namespace: parsed.namespace ?? context.defaultNamespace, + name: parsed.name, }; } @@ -196,3 +211,53 @@ export function serializeEntityRef( return `${kind ? `${kind}:` : ''}${namespace ? `${namespace}/` : ''}${name}`; } + +/** + * Compares an entity to either a string reference or a compound reference. + * + * The comparison is case insensitive, and all of kind, namespace, and name + * must match (after applying the optional context to the ref). + * + * @param entity The entity to match + * @param ref A string or compound entity ref + * @param context An optional context of default kind and namespace, that apply + * to the ref if given + * @returns True if matching, false otherwise + */ +export function compareEntityToRef( + entity: Entity, + ref: EntityRef | EntityName, + context?: EntityRefContext, +): boolean { + const entityKind = entity.kind; + const entityNamespace = entity.metadata.namespace || ENTITY_DEFAULT_NAMESPACE; + const entityName = entity.metadata.name; + + let refKind: string | undefined; + let refNamespace: string | undefined; + let refName: string; + if (typeof ref === 'string') { + const parsed = parseRefString(ref); + refKind = parsed.kind || context?.defaultKind; + refNamespace = + parsed.namespace || context?.defaultNamespace || ENTITY_DEFAULT_NAMESPACE; + refName = parsed.name; + } else { + refKind = ref.kind || context?.defaultKind; + refNamespace = + ref.namespace || context?.defaultNamespace || ENTITY_DEFAULT_NAMESPACE; + refName = ref.name; + } + + if (!refKind || !refNamespace) { + throw new Error( + `Entity reference or context did not contain kind and namespace`, + ); + } + + return ( + entityKind.toLowerCase() === refKind.toLowerCase() && + entityNamespace.toLowerCase() === refNamespace.toLowerCase() && + entityName.toLowerCase() === refName.toLowerCase() + ); +} From 99076c25e17b9b230f5e13e1db769233dbd0c81b Mon Sep 17 00:00:00 2001 From: "Chongyang Adrian, Ke" Date: Thu, 4 Feb 2021 11:11:05 +0800 Subject: [PATCH 063/110] integration: fix gitlab spaces encoding --- packages/integration/src/gitlab/core.test.ts | 12 ++++++------ packages/integration/src/gitlab/core.ts | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/integration/src/gitlab/core.test.ts b/packages/integration/src/gitlab/core.test.ts index 43fea72e0b..2b4713604c 100644 --- a/packages/integration/src/gitlab/core.test.ts +++ b/packages/integration/src/gitlab/core.test.ts @@ -49,23 +49,23 @@ describe('gitlab core', () => { { config: configWithNoToken, url: - 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml', + 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path%20with%20spaces/to/file.yaml', result: - 'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch', + 'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%20with%20spaces%2Fto%2Ffile.yaml/raw?ref=branch', }, { config: configWithToken, url: - 'https://gitlab.example.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml', + 'https://gitlab.example.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path%20with%20spaces/to/file.yaml', result: - 'https://gitlab.example.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch', + 'https://gitlab.example.com/api/v4/projects/12345/repository/files/my%2Fpath%20with%20spaces%2Fto%2Ffile.yaml/raw?ref=branch', }, { config: configWithNoToken, url: - 'https://gitlab.com/groupA/teams/teamA/repoA/-/blob/branch/my/path/to/file.yaml', // Repo not in subgroup + 'https://gitlab.com/groupA/teams/teamA/repoA/-/blob/branch/my/path%20with%20spaces/to/file.yaml', // Repo not in subgroup result: - 'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch', + 'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%20with%20spaces%2Fto%2Ffile.yaml/raw?ref=branch', }, // Raw URLs { diff --git a/packages/integration/src/gitlab/core.ts b/packages/integration/src/gitlab/core.ts index c1d43c3a46..66b6733f56 100644 --- a/packages/integration/src/gitlab/core.ts +++ b/packages/integration/src/gitlab/core.ts @@ -109,7 +109,7 @@ export function buildProjectUrl(target: string, projectID: Number): URL { '/api/v4/projects', projectID, 'repository/files', - encodeURIComponent(filePath.join('/')), + encodeURIComponent(decodeURIComponent(filePath.join('/'))), 'raw', ].join('/'); url.search = `?ref=${branch}`; From c4abcdb609908faa0a4640ce5387d3da73bdfcb6 Mon Sep 17 00:00:00 2001 From: "Chongyang Adrian, Ke" Date: Thu, 4 Feb 2021 11:11:05 +0800 Subject: [PATCH 064/110] integration: fix gitlab spaces encoding --- .changeset/dry-kings-hear.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/dry-kings-hear.md diff --git a/.changeset/dry-kings-hear.md b/.changeset/dry-kings-hear.md new file mode 100644 index 0000000000..c877785329 --- /dev/null +++ b/.changeset/dry-kings-hear.md @@ -0,0 +1,5 @@ +--- +'@backstage/integration': patch +--- + +Fix gitlab handling of paths with spaces From a2709a51ee0b1333465bb36b8154458c2370f829 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Feb 2021 05:03:04 +0000 Subject: [PATCH 065/110] chore(deps-dev): bump @types/zen-observable from 0.8.0 to 0.8.2 Bumps [@types/zen-observable](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/zen-observable) from 0.8.0 to 0.8.2. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/zen-observable) Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index b7502a9740..206d1f2705 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7740,9 +7740,9 @@ integrity sha512-MBSp62AjB1KrSOI3gX9GekddXU5YYQAVA93+aSl78biBqoSzxg876aQY2KJK5Gnfbpqq7O2cadVX5kPAtBqIXw== "@types/zen-observable@^0.8.0": - version "0.8.0" - resolved "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" - integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== + version "0.8.2" + resolved "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.2.tgz#808c9fa7e4517274ed555fa158f2de4b4f468e71" + integrity sha512-HrCIVMLjE1MOozVoD86622S7aunluLb2PJdPfb3nYiEtohm8mIB/vyv0Fd37AdeMFrTUQXEunw78YloMA3Qilg== "@typescript-eslint/eslint-plugin@^v4.14.0": version "4.14.0" From 586607e417877aaa34d92845731c969aab0ac4c3 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 4 Feb 2021 09:39:05 +0100 Subject: [PATCH 066/110] Order events by id Co-authored-by: blam --- .../scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index cf1bc18621..22697f5fdb 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -245,6 +245,7 @@ export class DatabaseTaskStore implements TaskStore { builder.where('id', '>', after).orWhere('event_type', 'completion'); } }) + .orderBy('id') .select(); const events = rawEvents.map(event => { From 6ca31fb834ea620e2c4de23d26efd076ffe6646c Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 4 Feb 2021 09:42:04 +0100 Subject: [PATCH 067/110] scaffolder: Flush events to eventstream Co-authored-by: blam --- .../scaffolder-backend/src/service/router.ts | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 16cefa5297..a8a61972af 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -249,7 +249,7 @@ export async function createRouter( .get('/v2/tasks/:taskId/eventstream', async (req, res) => { const { taskId } = req.params; const after = Number(req.query.after) || undefined; - logger.info('event stream opened'); + logger.debug(`Event stream observing taskId '${taskId}' opened`); // Mandatory headers and http status to keep connection open res.writeHead(200, { @@ -257,28 +257,35 @@ export async function createRouter( 'Cache-Control': 'no-cache', 'Content-Type': 'text/event-stream', }); + // After client opens connection send all nests as string const unsubscribe = taskBroker.observe( { taskId, after }, (error, { events }) => { - logger.error( - `Received error from event stream when observing task ${taskId}`, - error, - ); + if (error) { + logger.error( + `Received error from event stream when observing taskId '${taskId}', ${error}`, + ); + } + for (const event of events) { - res.write(`event:${JSON.stringify(event)}\n\n`); + res.write( + `event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`, + ); if (event.type === 'completion') { unsubscribe(); - res.end(); + // Closing the event stream here would cause the frontend + // to automatically reconnect because it lost connection. } } + res.flush(); }, ); // When client closes connection we update the clients list // avoiding the disconnected one req.on('close', () => { unsubscribe(); - logger.info('event stream closed'); + logger.debug(`Event stream observing taskId '${taskId}' closed`); }); }); From 19fe61c27a20e43fb82587c5f5176b9fd577372e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 4 Feb 2021 10:47:56 +0100 Subject: [PATCH 068/110] provide a changeset for the eslint update --- .changeset/dry-kings-hear.md | 2 +- .changeset/nervous-pens-destroy.md | 33 ++++++++++++++++++++++++++++++ .github/workflows/e2e.yml | 1 + 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 .changeset/nervous-pens-destroy.md diff --git a/.changeset/dry-kings-hear.md b/.changeset/dry-kings-hear.md index c877785329..06ac1ef877 100644 --- a/.changeset/dry-kings-hear.md +++ b/.changeset/dry-kings-hear.md @@ -2,4 +2,4 @@ '@backstage/integration': patch --- -Fix gitlab handling of paths with spaces +Fix GitLab handling of paths with spaces diff --git a/.changeset/nervous-pens-destroy.md b/.changeset/nervous-pens-destroy.md new file mode 100644 index 0000000000..da8ebc162d --- /dev/null +++ b/.changeset/nervous-pens-destroy.md @@ -0,0 +1,33 @@ +--- +'@backstage/cli': minor +--- + +We have updated the default `eslint` rules in the `@backstage/cli` package. + +```diff +-'@typescript-eslint/no-shadow': 'off', +-'@typescript-eslint/no-redeclare': 'off', ++'no-shadow': 'off', ++'no-redeclare': 'off', ++'@typescript-eslint/no-shadow': 'error', ++'@typescript-eslint/no-redeclare': 'error', +``` + +The rules are documented [here](https://eslint.org/docs/rules/no-shadow) and [here](https://eslint.org/docs/rules/no-redeclare). + +This involved a large number of small changes to the code base. When you compile your own code using the CLI, you may also be +affected. We consider these rules important, and the primary recommendation is to try to update your code according to the +documentation above. But those that prefer to not enable the rules, or need time to perform the updates, may update their +local `.eslintrc.js` file(s) in the repo root and/or in individual plugins as they see fit: + +```js +module.exports = { + // ... other declarations + rules: { + '@typescript-eslint/no-shadow': 'off', + '@typescript-eslint/no-redeclare': 'off', + }, +}; +``` + +Because of the nature of this change, we're unable to provide a grace period for the update :( diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 62be0a6cea..0fe1642394 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -3,6 +3,7 @@ name: E2E Test Linux on: pull_request: paths-ignore: + - '.changeset/**' - 'contrib/**' - 'docs/**' - 'microsite/**' From ea155ca8c9811315507a5dad2829dadab2ed15d5 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 4 Feb 2021 12:35:43 +0100 Subject: [PATCH 069/110] yarn.lock: sync changeset versions --- yarn.lock | 67 ++++++------------------------------------------------- 1 file changed, 7 insertions(+), 60 deletions(-) diff --git a/yarn.lock b/yarn.lock index 206d1f2705..482ce1f737 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2674,19 +2674,7 @@ resolve-from "^5.0.0" semver "^5.4.1" -"@changesets/assemble-release-plan@^4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-4.0.0.tgz#60c2392c0e2c99f24778ab3a5c8e8c80ddaaaa59" - integrity sha512-3Kv21FNvysTQvZs3fHr6aZeDibhZHtgI1++fMZplzVtwNVmpjow3zv9lcZmJP26LthbpVH3I8+nqlU7M43lfWA== - dependencies: - "@babel/runtime" "^7.10.4" - "@changesets/errors" "^0.1.4" - "@changesets/get-dependents-graph" "^1.1.3" - "@changesets/types" "^3.1.0" - "@manypkg/get-packages" "^1.0.1" - semver "^5.4.1" - -"@changesets/assemble-release-plan@^4.1.0": +"@changesets/assemble-release-plan@^4.0.0", "@changesets/assemble-release-plan@^4.1.0": version "4.1.0" resolved "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-4.1.0.tgz#091e5e768dfe51835937e71d1ebaca1c9d6de55b" integrity sha512-dMqe2L5Pn4UGTW89kOuuCuZD3pQFZj1Sxv92ZW4S98sXGsxcb2PdW+PeHbQ7tawkCYCOvzhXxAlN4OdF2DlDKQ== @@ -2734,20 +2722,7 @@ term-size "^2.1.0" tty-table "^2.8.10" -"@changesets/config@^1.2.0": - version "1.4.0" - resolved "https://registry.npmjs.org/@changesets/config/-/config-1.4.0.tgz#c157a4121f198b749f2bbc2e9015b6e976ece7d6" - integrity sha512-eoTOcJ6py7jBDY8rUXwEGxR5UtvUX+p//0NhkVpPGcnvIeITHq+DOIsuWyGzWcb+1FaYkof3CCr32/komZTu4Q== - dependencies: - "@changesets/errors" "^0.1.4" - "@changesets/get-dependents-graph" "^1.1.3" - "@changesets/logger" "^0.0.5" - "@changesets/types" "^3.2.0" - "@manypkg/get-packages" "^1.0.1" - fs-extra "^7.0.1" - micromatch "^4.0.2" - -"@changesets/config@^1.5.0": +"@changesets/config@^1.2.0", "@changesets/config@^1.5.0": version "1.5.0" resolved "https://registry.npmjs.org/@changesets/config/-/config-1.5.0.tgz#6d58b01e45916318d3f39e9cde86a5d69dc58ed8" integrity sha512-Bl9nLVYcwFCpd9jpzcOsExZk1NuTYX20D2YWHCdS1xll3W0yOdSUlWLUCCfugN1l3+yTR6iDW6q9o6vpCevWvA== @@ -2767,17 +2742,6 @@ dependencies: extendable-error "^0.1.5" -"@changesets/get-dependents-graph@^1.1.3": - version "1.1.3" - resolved "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-1.1.3.tgz#da959c43ce98f3a990a6b8d9c1f894bcc1b629c7" - integrity sha512-cTbySXwSv9yWp4Pp5R/b5Qv23wJgFaFCqUbsI3IJ2pyPl0vMaODAZS8NI1nNK2XSxGIg1tw+dWNSR4PlrKBSVQ== - dependencies: - "@changesets/types" "^3.0.0" - "@manypkg/get-packages" "^1.0.1" - chalk "^2.1.0" - fs-extra "^7.0.1" - semver "^5.4.1" - "@changesets/get-dependents-graph@^1.2.0": version "1.2.0" resolved "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-1.2.0.tgz#6986927a5fec60bc6fcc76f966efae2ac30c05ed" @@ -2807,19 +2771,7 @@ resolved "https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.3.2.tgz#8131a99035edd11aa7a44c341cbb05e668618c67" integrity sha512-SVqwYs5pULYjYT4op21F2pVbcrca4qA/bAA3FmFXKMN7Y+HcO8sbZUTx3TAy2VXulP2FACd1aC7f2nTuqSPbqg== -"@changesets/git@^1.0.5": - version "1.0.6" - resolved "https://registry.npmjs.org/@changesets/git/-/git-1.0.6.tgz#057e627e5d3fcb74bf6c18d7284e130ba5a7632e" - integrity sha512-e0M06XuME3W5lGhz+CO0vLc60u+hLk/pYjOx/6GXEWuQrwtGgeycFIfRgRt8qTs664o1oKtVHBbd7ItpoWgFfA== - dependencies: - "@babel/runtime" "^7.10.4" - "@changesets/errors" "^0.1.4" - "@changesets/types" "^3.1.1" - "@manypkg/get-packages" "^1.0.1" - is-subdir "^1.1.1" - spawndamnit "^2.0.0" - -"@changesets/git@^1.1.0": +"@changesets/git@^1.0.5", "@changesets/git@^1.1.0": version "1.1.0" resolved "https://registry.npmjs.org/@changesets/git/-/git-1.1.0.tgz#ecaa8058ac87b450c09da0e74b68e6854cd0742c" integrity sha512-f/2rQynT+JiAL/V0V/GQdXhLkcb86ELg3UwH3fQO4wVdfUbE9NHIHq9ohJdH1Ymh0Lv48F/b38aWZ5v2sKiF3w== @@ -2839,9 +2791,9 @@ chalk "^2.1.0" "@changesets/parse@^0.3.6": - version "0.3.6" - resolved "https://registry.npmjs.org/@changesets/parse/-/parse-0.3.6.tgz#8c2c8480fc07d2db2c37469d4a8df10906a989c6" - integrity sha512-0XPd/es9CfogI7XIqDr7I2mWzm++xX2s9GZsij3GajPYd7ouEsgJyNatPooxNtqj6ZepkiD6uqlqbeBUyj/A0Q== + version "0.3.7" + resolved "https://registry.npmjs.org/@changesets/parse/-/parse-0.3.7.tgz#1368136e2b83d5cff11b4d383a3032723530db99" + integrity sha512-8yqKulslq/7V2VRBsJqPgjnZMoehYqhJm5lEOXJPZ2rcuSdyj8+p/2vq2vRDBJT2m0rP+C9G8DujsGYQIFZezw== dependencies: "@changesets/types" "^3.0.0" js-yaml "^3.13.1" @@ -2871,12 +2823,7 @@ fs-extra "^7.0.1" p-filter "^2.1.0" -"@changesets/types@^3.0.0", "@changesets/types@^3.1.0", "@changesets/types@^3.1.1", "@changesets/types@^3.2.0": - version "3.2.0" - resolved "https://registry.npmjs.org/@changesets/types/-/types-3.2.0.tgz#d8306d7219c3b19b6d860ddeb9d7374e2dd6b035" - integrity sha512-rAmPtOyXpisEEE25CchKNUAf2ApyAeuZ/h78YDoqKZaCk5tUD0lgYZGPIRV9WTPoqNjJULIym37ogc6pkax5jg== - -"@changesets/types@^3.3.0": +"@changesets/types@^3.0.0", "@changesets/types@^3.1.0", "@changesets/types@^3.1.1", "@changesets/types@^3.3.0": version "3.3.0" resolved "https://registry.npmjs.org/@changesets/types/-/types-3.3.0.tgz#04cd8184b2d2da760667bd89bf9b930938dbd96e" integrity sha512-rJamRo+OD/MQekImfIk07JZwYSB18iU6fYL8xOg0gfAiTh1a1+OlR1fPIxm55I7RsWw812is2YcPPwXdIewrhA== From c6381c07981a3c91f6ee7e6693b1a7b411c5a031 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 4 Feb 2021 13:43:12 +0100 Subject: [PATCH 070/110] changesets: fix changeset bump level for integrations package --- .changeset/wicked-boxes-taste.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/wicked-boxes-taste.md b/.changeset/wicked-boxes-taste.md index 3eef04825c..d5272260c6 100644 --- a/.changeset/wicked-boxes-taste.md +++ b/.changeset/wicked-boxes-taste.md @@ -1,5 +1,5 @@ --- -'@backstage/integration': major +'@backstage/integration': patch --- #4322 Bitbucket own hosted v5.11.1 branchUrl fix and enabled error tracing… #4347 From 6794967d20ba580b8f284405f8a096a6a8dd21c4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 4 Feb 2021 13:02:30 +0000 Subject: [PATCH 071/110] Version Packages --- .../catalog-import-export-api-snow-fight.md | 5 - .changeset/chilled-toys-raise.md | 5 - .changeset/chilly-dodos-drop.md | 6 -- .changeset/cost-insights-tricky-moles-grin.md | 5 - .changeset/cost-insights-wise-moons-crash.md | 5 - .changeset/curly-ghosts-laugh.md | 5 - .changeset/cyan-kiwis-suffer.md | 5 - .changeset/cyan-lions-float.md | 5 - .changeset/dirty-carrots-invent.md | 5 - .changeset/dry-kings-hear.md | 5 - .changeset/eight-carrots-talk.md | 5 - .changeset/empty-hairs-fetch.md | 13 --- .changeset/fair-kids-laugh.md | 5 - .changeset/fast-dots-camp.md | 5 - .changeset/fifty-dolls-doubt.md | 5 - .changeset/fluffy-nails-sort.md | 24 ----- .changeset/four-paws-pull.md | 7 -- .changeset/four-shrimps-flash.md | 7 -- .changeset/fresh-eels-compare.md | 5 - .changeset/fuzzy-pumpkins-tie.md | 5 - .changeset/gentle-scissors-compare.md | 7 -- .changeset/hot-rules-shout.md | 5 - .changeset/late-crews-train.md | 6 -- .changeset/little-cherries-hug.md | 5 - .changeset/little-pets-cross.md | 5 - .changeset/loud-walls-collect.md | 5 - .changeset/lovely-panthers-peel.md | 8 -- .changeset/metal-insects-compete.md | 5 - .changeset/metal-pans-leave.md | 5 - .changeset/moody-apricots-warn.md | 5 - .changeset/moody-buckets-visit.md | 5 - .changeset/neat-brooms-allow.md | 5 - .changeset/nervous-pens-destroy.md | 33 ------- .changeset/nice-bottles-battle.md | 5 - .changeset/ninety-keys-serve.md | 5 - .changeset/orange-pets-whisper.md | 5 - .changeset/plenty-steaks-confess.md | 5 - .changeset/poor-ligers-flow.md | 5 - .changeset/pretty-melons-prove.md | 8 -- .changeset/quick-apes-shop.md | 5 - .changeset/selfish-kids-know.md | 5 - .changeset/shaggy-dingos-suffer.md | 6 -- .changeset/shiny-rabbits-unite.md | 5 - .changeset/silent-readers-worry.md | 5 - .changeset/six-ravens-heal.md | 5 - .changeset/sour-gorillas-fail.md | 23 ----- .changeset/stale-zebras-warn.md | 5 - .changeset/tame-points-search.md | 10 -- .changeset/techdocs-chilly-steaks-brush.md | 5 - .changeset/thirty-cobras-tickle.md | 15 --- .changeset/tidy-news-perform.md | 5 - .changeset/two-foxes-exercise.md | 6 -- .changeset/wet-suits-live.md | 20 ---- .changeset/wicked-beds-buy.md | 61 ------------ .changeset/wicked-boxes-taste.md | 5 - .changeset/wild-cows-exercise.md | 8 -- .changeset/wise-lies-listen.md | 5 - packages/app/CHANGELOG.md | 76 ++++++++++++++ packages/app/package.json | 66 ++++++------- packages/backend-common/CHANGELOG.md | 34 +++++++ packages/backend-common/package.json | 8 +- packages/backend/CHANGELOG.md | 26 +++++ packages/backend/package.json | 18 ++-- packages/catalog-client/package.json | 4 +- packages/catalog-model/CHANGELOG.md | 9 ++ packages/catalog-model/package.json | 4 +- packages/cli/CHANGELOG.md | 43 ++++++++ packages/cli/package.json | 12 +-- packages/config-loader/CHANGELOG.md | 7 ++ packages/config-loader/package.json | 2 +- packages/core-api/package.json | 4 +- packages/core/CHANGELOG.md | 30 ++++++ packages/core/package.json | 6 +- packages/create-app/CHANGELOG.md | 76 ++++++++++++++ packages/create-app/package.json | 44 ++++----- packages/dev-utils/CHANGELOG.md | 24 +++++ packages/dev-utils/package.json | 12 +-- packages/integration/CHANGELOG.md | 15 +++ packages/integration/package.json | 4 +- packages/techdocs-common/CHANGELOG.md | 20 ++++ packages/techdocs-common/package.json | 10 +- packages/test-utils/package.json | 4 +- packages/theme/CHANGELOG.md | 6 ++ packages/theme/package.json | 4 +- plugins/api-docs/CHANGELOG.md | 31 ++++++ plugins/api-docs/package.json | 14 +-- plugins/app-backend/CHANGELOG.md | 11 +++ plugins/app-backend/package.json | 8 +- plugins/auth-backend/package.json | 6 +- plugins/catalog-backend/CHANGELOG.md | 24 +++++ plugins/catalog-backend/package.json | 10 +- plugins/catalog-graphql/package.json | 6 +- plugins/catalog-import/CHANGELOG.md | 30 ++++++ plugins/catalog-import/package.json | 16 +-- plugins/catalog-react/CHANGELOG.md | 26 +++++ plugins/catalog-react/package.json | 10 +- plugins/catalog/CHANGELOG.md | 50 ++++++++++ plugins/catalog/package.json | 16 +-- plugins/circleci/CHANGELOG.md | 23 +++++ plugins/circleci/package.json | 14 +-- plugins/cloudbuild/CHANGELOG.md | 21 ++++ plugins/cloudbuild/package.json | 12 +-- plugins/cost-insights/CHANGELOG.md | 20 ++++ plugins/cost-insights/package.json | 10 +- plugins/explore-react/CHANGELOG.md | 14 +++ plugins/explore-react/package.json | 8 +- plugins/explore/CHANGELOG.md | 35 +++++++ plugins/explore/package.json | 16 +-- plugins/fossa/CHANGELOG.md | 29 ++++++ plugins/fossa/package.json | 14 +-- plugins/gcp-projects/CHANGELOG.md | 16 +++ plugins/gcp-projects/package.json | 10 +- plugins/github-actions/CHANGELOG.md | 26 +++++ plugins/github-actions/package.json | 14 +-- plugins/gitops-profiles/CHANGELOG.md | 15 +++ plugins/gitops-profiles/package.json | 10 +- plugins/graphiql/CHANGELOG.md | 16 +++ plugins/graphiql/package.json | 10 +- plugins/graphql/package.json | 4 +- plugins/jenkins/CHANGELOG.md | 23 +++++ plugins/jenkins/package.json | 14 +-- plugins/kafka-backend/package.json | 6 +- plugins/kafka/CHANGELOG.md | 23 +++++ plugins/kafka/package.json | 14 +-- plugins/kubernetes-backend/package.json | 6 +- plugins/kubernetes/CHANGELOG.md | 20 ++++ plugins/kubernetes/package.json | 12 +-- plugins/lighthouse/CHANGELOG.md | 24 +++++ plugins/lighthouse/package.json | 14 +-- plugins/newrelic/CHANGELOG.md | 15 +++ plugins/newrelic/package.json | 10 +- plugins/org/CHANGELOG.md | 31 ++++++ plugins/org/package.json | 14 +-- plugins/pagerduty/CHANGELOG.md | 20 ++++ plugins/pagerduty/package.json | 12 +-- plugins/proxy-backend/package.json | 4 +- plugins/register-component/CHANGELOG.md | 23 +++++ plugins/register-component/package.json | 14 +-- plugins/rollbar-backend/package.json | 4 +- plugins/rollbar/CHANGELOG.md | 24 +++++ plugins/rollbar/package.json | 14 +-- plugins/scaffolder-backend/CHANGELOG.md | 98 +++++++++++++++++++ plugins/scaffolder-backend/package.json | 10 +- plugins/scaffolder/CHANGELOG.md | 24 +++++ plugins/scaffolder/package.json | 14 +-- plugins/search/CHANGELOG.md | 23 +++++ plugins/search/package.json | 14 +-- plugins/sentry/CHANGELOG.md | 23 +++++ plugins/sentry/package.json | 14 +-- plugins/sonarqube/CHANGELOG.md | 23 +++++ plugins/sonarqube/package.json | 14 +-- plugins/tech-radar/CHANGELOG.md | 17 ++++ plugins/tech-radar/package.json | 10 +- plugins/techdocs-backend/CHANGELOG.md | 18 ++++ plugins/techdocs-backend/package.json | 10 +- plugins/techdocs/CHANGELOG.md | 30 ++++++ plugins/techdocs/package.json | 16 +-- plugins/user-settings/CHANGELOG.md | 17 ++++ plugins/user-settings/package.json | 10 +- plugins/welcome/CHANGELOG.md | 16 +++ plugins/welcome/package.json | 10 +- 161 files changed, 1580 insertions(+), 798 deletions(-) delete mode 100644 .changeset/catalog-import-export-api-snow-fight.md delete mode 100644 .changeset/chilled-toys-raise.md delete mode 100644 .changeset/chilly-dodos-drop.md delete mode 100644 .changeset/cost-insights-tricky-moles-grin.md delete mode 100644 .changeset/cost-insights-wise-moons-crash.md delete mode 100644 .changeset/curly-ghosts-laugh.md delete mode 100644 .changeset/cyan-kiwis-suffer.md delete mode 100644 .changeset/cyan-lions-float.md delete mode 100644 .changeset/dirty-carrots-invent.md delete mode 100644 .changeset/dry-kings-hear.md delete mode 100644 .changeset/eight-carrots-talk.md delete mode 100644 .changeset/empty-hairs-fetch.md delete mode 100644 .changeset/fair-kids-laugh.md delete mode 100644 .changeset/fast-dots-camp.md delete mode 100644 .changeset/fifty-dolls-doubt.md delete mode 100644 .changeset/fluffy-nails-sort.md delete mode 100644 .changeset/four-paws-pull.md delete mode 100644 .changeset/four-shrimps-flash.md delete mode 100644 .changeset/fresh-eels-compare.md delete mode 100644 .changeset/fuzzy-pumpkins-tie.md delete mode 100644 .changeset/gentle-scissors-compare.md delete mode 100644 .changeset/hot-rules-shout.md delete mode 100644 .changeset/late-crews-train.md delete mode 100644 .changeset/little-cherries-hug.md delete mode 100644 .changeset/little-pets-cross.md delete mode 100644 .changeset/loud-walls-collect.md delete mode 100644 .changeset/lovely-panthers-peel.md delete mode 100644 .changeset/metal-insects-compete.md delete mode 100644 .changeset/metal-pans-leave.md delete mode 100644 .changeset/moody-apricots-warn.md delete mode 100644 .changeset/moody-buckets-visit.md delete mode 100644 .changeset/neat-brooms-allow.md delete mode 100644 .changeset/nervous-pens-destroy.md delete mode 100644 .changeset/nice-bottles-battle.md delete mode 100644 .changeset/ninety-keys-serve.md delete mode 100644 .changeset/orange-pets-whisper.md delete mode 100644 .changeset/plenty-steaks-confess.md delete mode 100644 .changeset/poor-ligers-flow.md delete mode 100644 .changeset/pretty-melons-prove.md delete mode 100644 .changeset/quick-apes-shop.md delete mode 100644 .changeset/selfish-kids-know.md delete mode 100644 .changeset/shaggy-dingos-suffer.md delete mode 100644 .changeset/shiny-rabbits-unite.md delete mode 100644 .changeset/silent-readers-worry.md delete mode 100644 .changeset/six-ravens-heal.md delete mode 100644 .changeset/sour-gorillas-fail.md delete mode 100644 .changeset/stale-zebras-warn.md delete mode 100644 .changeset/tame-points-search.md delete mode 100644 .changeset/techdocs-chilly-steaks-brush.md delete mode 100644 .changeset/thirty-cobras-tickle.md delete mode 100644 .changeset/tidy-news-perform.md delete mode 100644 .changeset/two-foxes-exercise.md delete mode 100644 .changeset/wet-suits-live.md delete mode 100644 .changeset/wicked-beds-buy.md delete mode 100644 .changeset/wicked-boxes-taste.md delete mode 100644 .changeset/wild-cows-exercise.md delete mode 100644 .changeset/wise-lies-listen.md create mode 100644 plugins/catalog-react/CHANGELOG.md create mode 100644 plugins/explore-react/CHANGELOG.md diff --git a/.changeset/catalog-import-export-api-snow-fight.md b/.changeset/catalog-import-export-api-snow-fight.md deleted file mode 100644 index dce9811f10..0000000000 --- a/.changeset/catalog-import-export-api-snow-fight.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-import': patch ---- - -Export _api_ (Client, API, ref) from the catalog import plugin. diff --git a/.changeset/chilled-toys-raise.md b/.changeset/chilled-toys-raise.md deleted file mode 100644 index 282d5492ec..0000000000 --- a/.changeset/chilled-toys-raise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Migrated to new composability API, exporting the plugin as `scaffolderPlugin`. The template list page (`/create`) is exported as the `TemplateIndexPage` extension, and the templating page itself is exported as `TemplatePage`. diff --git a/.changeset/chilly-dodos-drop.md b/.changeset/chilly-dodos-drop.md deleted file mode 100644 index 2af5255e70..0000000000 --- a/.changeset/chilly-dodos-drop.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/techdocs-common': patch -'@backstage/plugin-techdocs-backend': patch ---- - -1. Added option to use Azure Blob Storage as a choice to store the static generated files for TechDocs. diff --git a/.changeset/cost-insights-tricky-moles-grin.md b/.changeset/cost-insights-tricky-moles-grin.md deleted file mode 100644 index c35769e03c..0000000000 --- a/.changeset/cost-insights-tricky-moles-grin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-cost-insights': minor ---- - -add alert hooks diff --git a/.changeset/cost-insights-wise-moons-crash.md b/.changeset/cost-insights-wise-moons-crash.md deleted file mode 100644 index 1277911059..0000000000 --- a/.changeset/cost-insights-wise-moons-crash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-cost-insights': patch ---- - -Fixed date calculations incorrectly converting to UTC in some cases. This should be a transparent change. diff --git a/.changeset/curly-ghosts-laugh.md b/.changeset/curly-ghosts-laugh.md deleted file mode 100644 index 8c55e93cfc..0000000000 --- a/.changeset/curly-ghosts-laugh.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Remove techdocs.requestUrl and techdocs.storageUrl from app-config.yaml diff --git a/.changeset/cyan-kiwis-suffer.md b/.changeset/cyan-kiwis-suffer.md deleted file mode 100644 index b59b0e9a32..0000000000 --- a/.changeset/cyan-kiwis-suffer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Update `create-plugin` template to use the new composability API, by switching to exporting a single routable extension component. diff --git a/.changeset/cyan-lions-float.md b/.changeset/cyan-lions-float.md deleted file mode 100644 index f5fa064b71..0000000000 --- a/.changeset/cyan-lions-float.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-graphiql': patch ---- - -Finalized composability API migration, now exporting the plugin as `graphiqlPlugin`. diff --git a/.changeset/dirty-carrots-invent.md b/.changeset/dirty-carrots-invent.md deleted file mode 100644 index ceb4e4d2bb..0000000000 --- a/.changeset/dirty-carrots-invent.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core': patch ---- - -Add className to the SidebarItem diff --git a/.changeset/dry-kings-hear.md b/.changeset/dry-kings-hear.md deleted file mode 100644 index 06ac1ef877..0000000000 --- a/.changeset/dry-kings-hear.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/integration': patch ---- - -Fix GitLab handling of paths with spaces diff --git a/.changeset/eight-carrots-talk.md b/.changeset/eight-carrots-talk.md deleted file mode 100644 index 01e38b845d..0000000000 --- a/.changeset/eight-carrots-talk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core': patch ---- - -Update `WarningPanel` component to use accordion-style expansion diff --git a/.changeset/empty-hairs-fetch.md b/.changeset/empty-hairs-fetch.md deleted file mode 100644 index aedeb8a853..0000000000 --- a/.changeset/empty-hairs-fetch.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch -'@backstage/plugin-catalog': patch -'@backstage/plugin-catalog-react': patch -'@backstage/plugin-org': patch ---- - -Display owner and system as entity page links in the tables of the `api-docs` -plugin. - -Move `isOwnerOf` and `getEntityRelations` from `@backstage/plugin-catalog` to -`@backstage/plugin-catalog-react` and export it from there to use it by other -plugins. diff --git a/.changeset/fair-kids-laugh.md b/.changeset/fair-kids-laugh.md deleted file mode 100644 index d478508ed9..0000000000 --- a/.changeset/fair-kids-laugh.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/theme': patch ---- - -Updates warning text color to align to updated `WarningPanel` styling diff --git a/.changeset/fast-dots-camp.md b/.changeset/fast-dots-camp.md deleted file mode 100644 index 7a1f510f47..0000000000 --- a/.changeset/fast-dots-camp.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-import': patch ---- - -Migrated to new composability API, exporting the plugin instance as `catalogImportPlugin`, and the page as `CatalogImportPage`. diff --git a/.changeset/fifty-dolls-doubt.md b/.changeset/fifty-dolls-doubt.md deleted file mode 100644 index 1abd7a16b9..0000000000 --- a/.changeset/fifty-dolls-doubt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-lighthouse': patch ---- - -Migrate to new composability API, exporting the plugin instance as `lighthousePlugin`, the top-level page as `LighthousePage`, the entity card as `EntityLastLighthouseAuditCard`, and the entity content as `EntityLighthouseContent`. diff --git a/.changeset/fluffy-nails-sort.md b/.changeset/fluffy-nails-sort.md deleted file mode 100644 index 0aa87b532f..0000000000 --- a/.changeset/fluffy-nails-sort.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Updated the `rootLogger` in `@backstage/backend-common` to support custom logging options. This is useful when you want to make some changes without re-implementing the entire logger and calling `setRootLogger` or `logger.configure`. For example you can add additional `defaultMeta` tags to each log entry. The following changes are included: - -- Added `createRootLogger` which accepts winston `LoggerOptions`. These options allow overriding the default keys. - -Example Usage: - -```ts -// Create the logger -const logger = createRootLogger({ - defaultMeta: { appName: 'backstage', appEnv: 'prod' }, -}); - -// Add a custom logger transport -logger.add(new MyCustomTransport()); - -const config = await loadBackendConfig({ - argv: process.argv, - logger: getRootLogger(), // already set to new logger instance -}); -``` diff --git a/.changeset/four-paws-pull.md b/.changeset/four-paws-pull.md deleted file mode 100644 index 183b3dc5d7..0000000000 --- a/.changeset/four-paws-pull.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': minor ---- - -Make use of the `resolveUrl` facility of the `integration` package. - -Also rename the `LocationRefProcessor` to `LocationEntityProcessor`, to match the file name. This constitutes an interface change since the class is exported, but it is unlikely to be consumed outside of the package since it sits comfortably with the other default processors inside the catalog builder. diff --git a/.changeset/four-shrimps-flash.md b/.changeset/four-shrimps-flash.md deleted file mode 100644 index fb155bafd7..0000000000 --- a/.changeset/four-shrimps-flash.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/core': patch -'@backstage/plugin-techdocs': patch ---- - -Add `href` in addition to `onClick` to `ItemCard`. Ensure that the height of a -`ItemCard` with and without tags is equal. diff --git a/.changeset/fresh-eels-compare.md b/.changeset/fresh-eels-compare.md deleted file mode 100644 index 5d17556f7f..0000000000 --- a/.changeset/fresh-eels-compare.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-user-settings': patch ---- - -Migrate to new composability API, exporting the plugin as `userSettingsPlugin` and the page as `UserSettingsPage`. diff --git a/.changeset/fuzzy-pumpkins-tie.md b/.changeset/fuzzy-pumpkins-tie.md deleted file mode 100644 index 1e87972ce1..0000000000 --- a/.changeset/fuzzy-pumpkins-tie.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-model': patch ---- - -Adds a new optional `links` metadata field to the Entity class within the `catalog-model` package (as discussed in [[RFC] Entity Links](https://github.com/backstage/backstage/issues/3787)). This PR adds support for the entity links only. Follow up PR's will introduce the UI component to display them. diff --git a/.changeset/gentle-scissors-compare.md b/.changeset/gentle-scissors-compare.md deleted file mode 100644 index bb615987b2..0000000000 --- a/.changeset/gentle-scissors-compare.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/techdocs-common': patch -'@backstage/plugin-techdocs': patch -'@backstage/plugin-techdocs-backend': patch ---- - -`techdocs.requestUrl` and `techdocs.storageUrl` are now optional configs and the discovery API will be used to get the URL where techdocs plugin is hosted. diff --git a/.changeset/hot-rules-shout.md b/.changeset/hot-rules-shout.md deleted file mode 100644 index c88f4b01dc..0000000000 --- a/.changeset/hot-rules-shout.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Add `children` option to `addPage`, which will be rendered as the children of the `Route`. diff --git a/.changeset/late-crews-train.md b/.changeset/late-crews-train.md deleted file mode 100644 index 00f8f8b6ee..0000000000 --- a/.changeset/late-crews-train.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'example-app': patch -'@backstage/plugin-rollbar': patch ---- - -Migrated to new composability API, exporting the plugin instance as `rollbarPlugin`, the entity page content as `EntityRollbarContent`, and entity conditional as `isRollbarAvailable`. Updated the `EntityPage` for the `example-app` to include a composite `ErrorsSwitcher` component that works with both `Sentry` & `Rollbar`. Also removed the unused and undocumented `RollbarHome` related components. diff --git a/.changeset/little-cherries-hug.md b/.changeset/little-cherries-hug.md deleted file mode 100644 index 5fb42cbdad..0000000000 --- a/.changeset/little-cherries-hug.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Attempt to fix windows test errors in master diff --git a/.changeset/little-pets-cross.md b/.changeset/little-pets-cross.md deleted file mode 100644 index 0f4e2605cd..0000000000 --- a/.changeset/little-pets-cross.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-model': patch ---- - -Replace `yup` with `ajv`, for validation of catalog entities. diff --git a/.changeset/loud-walls-collect.md b/.changeset/loud-walls-collect.md deleted file mode 100644 index 78356eef38..0000000000 --- a/.changeset/loud-walls-collect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Fixed module resolution of external libraries during backend development. Modules used to be resolved relative to the backend entrypoint, but are now resolved relative to each individual module. diff --git a/.changeset/lovely-panthers-peel.md b/.changeset/lovely-panthers-peel.md deleted file mode 100644 index 512f0ebd3f..0000000000 --- a/.changeset/lovely-panthers-peel.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/core': minor ---- - -Closes #3556 -The scroll bar of collapsed sidebar is now hidden without full screen. - -![image](https://user-images.githubusercontent.com/46953622/105390193-0bfd0080-5c19-11eb-8e86-2161bbe6e8d9.png) diff --git a/.changeset/metal-insects-compete.md b/.changeset/metal-insects-compete.md deleted file mode 100644 index ead779d6a9..0000000000 --- a/.changeset/metal-insects-compete.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-org': patch ---- - -Migrate to new composability API, exporting the plugin instance as `orgPlugin`, and the entity cards as `EntityGroupProfileCard`, `EntityMembersListCard`, `EntityOwnershipCard`, and `EntityUserProfileCard`. diff --git a/.changeset/metal-pans-leave.md b/.changeset/metal-pans-leave.md deleted file mode 100644 index 16d5478e43..0000000000 --- a/.changeset/metal-pans-leave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/config-loader': patch ---- - -Bump `config-loader` to `ajv` 7, to enable v7 feature use elsewhere diff --git a/.changeset/moody-apricots-warn.md b/.changeset/moody-apricots-warn.md deleted file mode 100644 index c4d9be0ba3..0000000000 --- a/.changeset/moody-apricots-warn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/dev-utils': patch ---- - -Added `path` option to `addPage` that can be used to set a specific path for the page rather than a generated one. Also omit sidebar item altogether if `title` option is not set. diff --git a/.changeset/moody-buckets-visit.md b/.changeset/moody-buckets-visit.md deleted file mode 100644 index 07a960620b..0000000000 --- a/.changeset/moody-buckets-visit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/integration': patch ---- - -Properly forward errors that occur when looking up GitLab project IDs. diff --git a/.changeset/neat-brooms-allow.md b/.changeset/neat-brooms-allow.md deleted file mode 100644 index a4c69b0945..0000000000 --- a/.changeset/neat-brooms-allow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/config-loader': patch ---- - -Each piece of the configuration schema is now validated upfront, in order to produce more informative errors. diff --git a/.changeset/nervous-pens-destroy.md b/.changeset/nervous-pens-destroy.md deleted file mode 100644 index da8ebc162d..0000000000 --- a/.changeset/nervous-pens-destroy.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -'@backstage/cli': minor ---- - -We have updated the default `eslint` rules in the `@backstage/cli` package. - -```diff --'@typescript-eslint/no-shadow': 'off', --'@typescript-eslint/no-redeclare': 'off', -+'no-shadow': 'off', -+'no-redeclare': 'off', -+'@typescript-eslint/no-shadow': 'error', -+'@typescript-eslint/no-redeclare': 'error', -``` - -The rules are documented [here](https://eslint.org/docs/rules/no-shadow) and [here](https://eslint.org/docs/rules/no-redeclare). - -This involved a large number of small changes to the code base. When you compile your own code using the CLI, you may also be -affected. We consider these rules important, and the primary recommendation is to try to update your code according to the -documentation above. But those that prefer to not enable the rules, or need time to perform the updates, may update their -local `.eslintrc.js` file(s) in the repo root and/or in individual plugins as they see fit: - -```js -module.exports = { - // ... other declarations - rules: { - '@typescript-eslint/no-shadow': 'off', - '@typescript-eslint/no-redeclare': 'off', - }, -}; -``` - -Because of the nature of this change, we're unable to provide a grace period for the update :( diff --git a/.changeset/nice-bottles-battle.md b/.changeset/nice-bottles-battle.md deleted file mode 100644 index a688241caa..0000000000 --- a/.changeset/nice-bottles-battle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-app-backend': patch ---- - -Failures to load the frontend configuration schema now throws an error that includes more context and instructions for how to fix the issue. diff --git a/.changeset/ninety-keys-serve.md b/.changeset/ninety-keys-serve.md deleted file mode 100644 index d15884b1c4..0000000000 --- a/.changeset/ninety-keys-serve.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core': patch ---- - -Add a `prop` union for `SignInPage` that allows it to be used for just a single provider, with inline errors, and optionally with automatic sign-in. diff --git a/.changeset/orange-pets-whisper.md b/.changeset/orange-pets-whisper.md deleted file mode 100644 index e96c210e43..0000000000 --- a/.changeset/orange-pets-whisper.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Add check for outdated/duplicate packages to yarn start diff --git a/.changeset/plenty-steaks-confess.md b/.changeset/plenty-steaks-confess.md deleted file mode 100644 index f9ffe0f539..0000000000 --- a/.changeset/plenty-steaks-confess.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-gcp-projects': patch ---- - -Migrate to new composability API, exporting the plugin as `gcpProjectsPlugin` and page as `GcpProjectsPage`. diff --git a/.changeset/poor-ligers-flow.md b/.changeset/poor-ligers-flow.md deleted file mode 100644 index 1c3a8a742b..0000000000 --- a/.changeset/poor-ligers-flow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -Throw `NotAllowedError` when registering locations with entities of disallowed kinds diff --git a/.changeset/pretty-melons-prove.md b/.changeset/pretty-melons-prove.md deleted file mode 100644 index 7048757e5a..0000000000 --- a/.changeset/pretty-melons-prove.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/plugin-fossa': minor ---- - -Port FOSSA plugin to new extension model. - -If you are using the FOSSA plugin adjust the plugin import from `plugin` to -`fossaPlugin` and replace `` with ``. diff --git a/.changeset/quick-apes-shop.md b/.changeset/quick-apes-shop.md deleted file mode 100644 index 4c87b9253d..0000000000 --- a/.changeset/quick-apes-shop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch ---- - -Migrate to new composability API, exporting the plugin as `apiDocsPlugin`, index page as `ApiExplorerPage`, and entity page cards as `EntityApiDefinitionCard`, `EntityConsumedApisCard`, `EntityConsumingComponentsCard`, `EntityProvidedApisCard`, and `EntityProvidingComponentsCard`. diff --git a/.changeset/selfish-kids-know.md b/.changeset/selfish-kids-know.md deleted file mode 100644 index f434559135..0000000000 --- a/.changeset/selfish-kids-know.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Finalize migration to new composability API, with the plugin instance now exported `catalogPlugin`. diff --git a/.changeset/shaggy-dingos-suffer.md b/.changeset/shaggy-dingos-suffer.md deleted file mode 100644 index c1e200b701..0000000000 --- a/.changeset/shaggy-dingos-suffer.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-tech-radar': patch ---- - -Fix mapping RadarEntry and Entry for moved and url attributes -Fix clicking of links in the radar legend diff --git a/.changeset/shiny-rabbits-unite.md b/.changeset/shiny-rabbits-unite.md deleted file mode 100644 index dca9ee0dd9..0000000000 --- a/.changeset/shiny-rabbits-unite.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core': patch ---- - -Fix check that determines whether popup was closed or the messaging was misconfigured. diff --git a/.changeset/silent-readers-worry.md b/.changeset/silent-readers-worry.md deleted file mode 100644 index 866485ecc0..0000000000 --- a/.changeset/silent-readers-worry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-sonarqube': patch ---- - -Migrate to new composability API, exporting the plugin as `sonarQubePlugin` and card as `EntitySonarQubeCard`. diff --git a/.changeset/six-ravens-heal.md b/.changeset/six-ravens-heal.md deleted file mode 100644 index 960c482be9..0000000000 --- a/.changeset/six-ravens-heal.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-model': patch ---- - -Introduce json schema variants of the `yup` validation schemas diff --git a/.changeset/sour-gorillas-fail.md b/.changeset/sour-gorillas-fail.md deleted file mode 100644 index c6394ed76e..0000000000 --- a/.changeset/sour-gorillas-fail.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -# Repo visibility for GitLab and BitBucket repos - -**NOTE: This changes default repo visibility from `private` to `public` for GitLab and BitBucket** which -is consistent with the GitHub default. If you were counting on `private` visibility, you'll need to update -your scaffolder config to use `private`. - -This adds repo visibility feature parity with GitHub for GitLab and BitBucket. - -To configure the repo visibility, set scaffolder._type_.visibility as in this example: - -```yaml -scaffolder: - github: - visibility: private # 'public' or 'internal' or 'private' (default is 'public') - gitlab: - visibility: public # 'public' or 'internal' or 'private' (default is 'public') - bitbucket: - visibility: public # 'public' or 'private' (default is 'public') -``` diff --git a/.changeset/stale-zebras-warn.md b/.changeset/stale-zebras-warn.md deleted file mode 100644 index 33b53d3f75..0000000000 --- a/.changeset/stale-zebras-warn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-welcome': patch ---- - -Migrated to new composability API, exporting the plugin as `welcomePlugin` and the page as `WelcomePage`. diff --git a/.changeset/tame-points-search.md b/.changeset/tame-points-search.md deleted file mode 100644 index 7fffafc1cf..0000000000 --- a/.changeset/tame-points-search.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@backstage/integration': patch ---- - -Add a `resolveUrl` method to integrations, that works like the two-argument URL -constructor. The reason for using this is that Azure have their paths in a -query parameter, rather than the pathname of the URL. - -The implementation is optional (when not present, the URL constructor is used), -so this does not imply a breaking change. diff --git a/.changeset/techdocs-chilly-steaks-brush.md b/.changeset/techdocs-chilly-steaks-brush.md deleted file mode 100644 index 7fd4763c63..0000000000 --- a/.changeset/techdocs-chilly-steaks-brush.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/techdocs-common': patch ---- - -dir preparer will use URL Reader in its implementation. diff --git a/.changeset/thirty-cobras-tickle.md b/.changeset/thirty-cobras-tickle.md deleted file mode 100644 index cf43318658..0000000000 --- a/.changeset/thirty-cobras-tickle.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -'@backstage/core': patch -'@backstage/plugin-catalog': patch -'@backstage/plugin-explore': patch ---- - -Introduce `TabbedLayout` for creating tabs that are routed. - -```typescript - - -
This is rendered under /example/anything-here route
-
-
-``` diff --git a/.changeset/tidy-news-perform.md b/.changeset/tidy-news-perform.md deleted file mode 100644 index 055f051da7..0000000000 --- a/.changeset/tidy-news-perform.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-user-settings': patch ---- - -Keep the Pin Sidebar setting visible on small screens. diff --git a/.changeset/two-foxes-exercise.md b/.changeset/two-foxes-exercise.md deleted file mode 100644 index 953eb83138..0000000000 --- a/.changeset/two-foxes-exercise.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-explore': patch ---- - -Rework the explore plugin to allow the user to explore things in the ecosystem, -including tools and domains. diff --git a/.changeset/wet-suits-live.md b/.changeset/wet-suits-live.md deleted file mode 100644 index 27b8df8a84..0000000000 --- a/.changeset/wet-suits-live.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch -'@backstage/plugin-catalog-import': patch -'@backstage/plugin-circleci': patch -'@backstage/plugin-cloudbuild': patch -'@backstage/plugin-github-actions': patch -'@backstage/plugin-jenkins': patch -'@backstage/plugin-kafka': patch -'@backstage/plugin-lighthouse': patch -'@backstage/plugin-org': patch -'@backstage/plugin-register-component': patch -'@backstage/plugin-rollbar': patch -'@backstage/plugin-scaffolder': patch -'@backstage/plugin-search': patch -'@backstage/plugin-sentry': patch -'@backstage/plugin-techdocs': patch -'@backstage/dev-utils': patch ---- - -Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`. diff --git a/.changeset/wicked-beds-buy.md b/.changeset/wicked-beds-buy.md deleted file mode 100644 index ddcf4f880e..0000000000 --- a/.changeset/wicked-beds-buy.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': minor ---- - -The scaffolder is updated to generate a unique workspace directory inside the temp folder. This directory is cleaned up by the job processor after each run. - -The prepare/template/publish steps have been refactored to operate on known directories, `template/` and `result/`, inside the temporary workspace path. - -Updated preparers to accept the template url instead of the entire template. This is done primarily to allow for backwards compatibility between v1 and v2 scaffolder templates. - -Fixes broken GitHub actions templating in the Create React App template. - -#### For those with **custom** preparers, templates, or publishers - -The preparer interface has changed, the prepare method now only takes a single argument, and doesn't return anything. As part of this change the preparers were refactored to accept a URL pointing to the target directory, rather than computing that from the template entity. - -The `workingDirectory` option was also removed, and replaced with a `workspacePath` option. The difference between the two is that `workingDirectory` was a place for the preparer to create temporary directories, while the `workspacePath` is the specific folder were the entire templating process for a single template job takes place. Instead of returning a path to the folder were the prepared contents were placed, the contents are put at the `/template` path. - -```diff -type PreparerOptions = { -- workingDirectory?: string; -+ /** -+ * Full URL to the directory containg template data -+ */ -+ url: string; -+ /** -+ * The workspace path that will eventually be the the root of the new repo -+ */ -+ workspacePath: string; - logger: Logger; -}; - --prepare(template: TemplateEntityV1alpha1, opts?: PreparerOptions): Promise -+prepare(opts: PreparerOptions): Promise; -``` - -Instead of returning a path to the folder were the templaters contents were placed, the contents are put at the `/result` path. All templaters now also expect the source template to be present in the `template` directory within the `workspacePath`. - -```diff -export type TemplaterRunOptions = { -- directory: string; -+ workspacePath: string; - values: TemplaterValues; - logStream?: Writable; - dockerClient: Docker; -}; - --public async run(options: TemplaterRunOptions): Promise -+public async run(options: TemplaterRunOptions): Promise -``` - -Just like the preparer and templaters, the publishers have also switched to using `workspacePath`. The root of the new repo is expected to be located at `/result`. - -```diff -export type PublisherOptions = { - values: TemplaterValues; -- directory: string; -+ workspacePath: string; - logger: Logger; -}; -``` diff --git a/.changeset/wicked-boxes-taste.md b/.changeset/wicked-boxes-taste.md deleted file mode 100644 index d5272260c6..0000000000 --- a/.changeset/wicked-boxes-taste.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/integration': patch ---- - -#4322 Bitbucket own hosted v5.11.1 branchUrl fix and enabled error tracing… #4347 diff --git a/.changeset/wild-cows-exercise.md b/.changeset/wild-cows-exercise.md deleted file mode 100644 index cbc347888f..0000000000 --- a/.changeset/wild-cows-exercise.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/plugin-catalog': minor -'@backstage/create-app': minor ---- - -`@backstage/plugin-catalog` stopped exporting hooks and helpers for other -plugins. They are migrated to `@backstage/plugin-catalog-react`. -Change both your dependencies and imports to the new package. diff --git a/.changeset/wise-lies-listen.md b/.changeset/wise-lies-listen.md deleted file mode 100644 index 53f1edea02..0000000000 --- a/.changeset/wise-lies-listen.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-model': patch ---- - -Implement matchEntityWithRef for client side filtering of entities by ref matching diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index d39f705546..fcd9b795a4 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,81 @@ # example-app +## 0.2.14 + +### Patch Changes + +- 9d6ef14bc: Migrated to new composability API, exporting the plugin instance as `rollbarPlugin`, the entity page content as `EntityRollbarContent`, and entity conditional as `isRollbarAvailable`. Updated the `EntityPage` for the `example-app` to include a composite `ErrorsSwitcher` component that works with both `Sentry` & `Rollbar`. Also removed the unused and undocumented `RollbarHome` related components. +- Updated dependencies [ceef4dd89] +- Updated dependencies [720149854] +- Updated dependencies [19172f5a9] +- Updated dependencies [4c6a6dddd] +- Updated dependencies [398e1f83e] +- Updated dependencies [87b189d00] +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [7fc89bae2] +- Updated dependencies [c810082ae] +- Updated dependencies [b712841d6] +- Updated dependencies [a5628df40] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [bc5082a00] +- Updated dependencies [6e612ce25] +- Updated dependencies [e44925723] +- Updated dependencies [b37501a3d] +- Updated dependencies [9d6ef14bc] +- Updated dependencies [025e122c3] +- Updated dependencies [e9aab60c7] +- Updated dependencies [21e624ba9] +- Updated dependencies [0269f4fd9] +- Updated dependencies [19fe61c27] +- Updated dependencies [da9f53c60] +- Updated dependencies [a08c4b0b0] +- Updated dependencies [bc5082a00] +- Updated dependencies [bc5082a00] +- Updated dependencies [b37501a3d] +- Updated dependencies [90c8f20b9] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [8dfdec613] +- Updated dependencies [54c7d02f7] +- Updated dependencies [de98c32ed] +- Updated dependencies [806929fe2] +- Updated dependencies [019fe39a0] +- Updated dependencies [019fe39a0] +- Updated dependencies [11cb5ef94] + - @backstage/plugin-catalog-import@0.3.7 + - @backstage/plugin-scaffolder@0.4.2 + - @backstage/plugin-cost-insights@0.8.0 + - @backstage/cli@0.6.0 + - @backstage/plugin-graphiql@0.2.7 + - @backstage/core@0.6.0 + - @backstage/plugin-api-docs@0.4.4 + - @backstage/plugin-catalog@0.3.0 + - @backstage/plugin-catalog-react@0.0.2 + - @backstage/plugin-org@0.3.5 + - @backstage/theme@0.2.3 + - @backstage/plugin-lighthouse@0.2.9 + - @backstage/plugin-techdocs@0.5.5 + - @backstage/plugin-user-settings@0.2.5 + - @backstage/catalog-model@0.7.1 + - @backstage/plugin-rollbar@0.2.9 + - @backstage/plugin-gcp-projects@0.2.4 + - @backstage/plugin-tech-radar@0.3.4 + - @backstage/plugin-welcome@0.2.5 + - @backstage/plugin-explore@0.2.4 + - @backstage/plugin-circleci@0.2.7 + - @backstage/plugin-cloudbuild@0.2.8 + - @backstage/plugin-github-actions@0.3.1 + - @backstage/plugin-jenkins@0.3.8 + - @backstage/plugin-kafka@0.2.1 + - @backstage/plugin-register-component@0.2.8 + - @backstage/plugin-search@0.2.7 + - @backstage/plugin-sentry@0.3.4 + - @backstage/plugin-gitops-profiles@0.2.4 + - @backstage/plugin-kubernetes@0.3.8 + - @backstage/plugin-newrelic@0.2.4 + - @backstage/plugin-pagerduty@0.2.7 + ## 0.2.13 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 9de8299d93..9b82e0bb32 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,41 +1,41 @@ { "name": "example-app", - "version": "0.2.13", + "version": "0.2.14", "private": true, "bundled": true, "dependencies": { - "@backstage/catalog-model": "^0.7.0", - "@backstage/cli": "^0.5.0", - "@backstage/core": "^0.5.0", - "@backstage/plugin-api-docs": "^0.4.3", - "@backstage/plugin-catalog": "^0.2.14", - "@backstage/plugin-catalog-react": "^0.0.1", - "@backstage/plugin-catalog-import": "^0.3.6", - "@backstage/plugin-circleci": "^0.2.6", - "@backstage/plugin-cloudbuild": "^0.2.7", - "@backstage/plugin-cost-insights": "^0.7.0", - "@backstage/plugin-explore": "^0.2.3", - "@backstage/plugin-gcp-projects": "^0.2.3", - "@backstage/plugin-github-actions": "^0.3.0", - "@backstage/plugin-gitops-profiles": "^0.2.3", - "@backstage/plugin-graphiql": "^0.2.6", - "@backstage/plugin-org": "^0.3.4", - "@backstage/plugin-jenkins": "^0.3.6", - "@backstage/plugin-kafka": "^0.2.0", - "@backstage/plugin-kubernetes": "^0.3.7", - "@backstage/plugin-lighthouse": "^0.2.8", - "@backstage/plugin-newrelic": "^0.2.3", - "@backstage/plugin-pagerduty": "0.2.6", - "@backstage/plugin-register-component": "^0.2.7", - "@backstage/plugin-rollbar": "^0.2.8", - "@backstage/plugin-scaffolder": "^0.4.1", - "@backstage/plugin-sentry": "^0.3.3", - "@backstage/plugin-search": "^0.2.6", - "@backstage/plugin-tech-radar": "^0.3.3", - "@backstage/plugin-techdocs": "^0.5.4", - "@backstage/plugin-user-settings": "^0.2.4", - "@backstage/plugin-welcome": "^0.2.4", - "@backstage/theme": "^0.2.2", + "@backstage/catalog-model": "^0.7.1", + "@backstage/cli": "^0.6.0", + "@backstage/core": "^0.6.0", + "@backstage/plugin-api-docs": "^0.4.4", + "@backstage/plugin-catalog": "^0.3.0", + "@backstage/plugin-catalog-react": "^0.0.2", + "@backstage/plugin-catalog-import": "^0.3.7", + "@backstage/plugin-circleci": "^0.2.7", + "@backstage/plugin-cloudbuild": "^0.2.8", + "@backstage/plugin-cost-insights": "^0.8.0", + "@backstage/plugin-explore": "^0.2.4", + "@backstage/plugin-gcp-projects": "^0.2.4", + "@backstage/plugin-github-actions": "^0.3.1", + "@backstage/plugin-gitops-profiles": "^0.2.4", + "@backstage/plugin-graphiql": "^0.2.7", + "@backstage/plugin-org": "^0.3.5", + "@backstage/plugin-jenkins": "^0.3.8", + "@backstage/plugin-kafka": "^0.2.1", + "@backstage/plugin-kubernetes": "^0.3.8", + "@backstage/plugin-lighthouse": "^0.2.9", + "@backstage/plugin-newrelic": "^0.2.4", + "@backstage/plugin-pagerduty": "0.2.7", + "@backstage/plugin-register-component": "^0.2.8", + "@backstage/plugin-rollbar": "^0.2.9", + "@backstage/plugin-scaffolder": "^0.4.2", + "@backstage/plugin-sentry": "^0.3.4", + "@backstage/plugin-search": "^0.2.7", + "@backstage/plugin-tech-radar": "^0.3.4", + "@backstage/plugin-techdocs": "^0.5.5", + "@backstage/plugin-user-settings": "^0.2.5", + "@backstage/plugin-welcome": "^0.2.5", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@octokit/rest": "^18.0.12", diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 3cb57f9056..cc33a7e81b 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/backend-common +## 0.5.2 + +### Patch Changes + +- 2430ee7c2: Updated the `rootLogger` in `@backstage/backend-common` to support custom logging options. This is useful when you want to make some changes without re-implementing the entire logger and calling `setRootLogger` or `logger.configure`. For example you can add additional `defaultMeta` tags to each log entry. The following changes are included: + + - Added `createRootLogger` which accepts winston `LoggerOptions`. These options allow overriding the default keys. + + Example Usage: + + ```ts + // Create the logger + const logger = createRootLogger({ + defaultMeta: { appName: 'backstage', appEnv: 'prod' }, + }); + + // Add a custom logger transport + logger.add(new MyCustomTransport()); + + const config = await loadBackendConfig({ + argv: process.argv, + logger: getRootLogger(), // already set to new logger instance + }); + ``` + +- Updated dependencies [c4abcdb60] +- Updated dependencies [062df71db] +- Updated dependencies [064c513e1] +- Updated dependencies [e9aab60c7] +- Updated dependencies [3149bfe63] +- Updated dependencies [2e62aea6f] + - @backstage/integration@0.3.2 + - @backstage/config-loader@0.5.1 + ## 0.5.1 ### Patch Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 78077c2ce7..3053784dc3 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.5.1", + "version": "0.5.2", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -31,8 +31,8 @@ "dependencies": { "@backstage/cli-common": "^0.1.1", "@backstage/config": "^0.1.2", - "@backstage/config-loader": "^0.5.0", - "@backstage/integration": "^0.3.1", + "@backstage/config-loader": "^0.5.1", + "@backstage/integration": "^0.3.2", "@types/cors": "^2.8.6", "@types/express": "^4.17.6", "archiver": "^5.0.2", @@ -66,7 +66,7 @@ } }, "devDependencies": { - "@backstage/cli": "^0.5.0", + "@backstage/cli": "^0.6.0", "@backstage/test-utils": "^0.1.5", "@types/archiver": "^5.1.0", "@types/compression": "^1.7.0", diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 215e4e7296..f85c22784d 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,31 @@ # example-backend +## 0.2.14 + +### Patch Changes + +- Updated dependencies [c777df180] +- Updated dependencies [2430ee7c2] +- Updated dependencies [3149bfe63] +- Updated dependencies [6e612ce25] +- Updated dependencies [e44925723] +- Updated dependencies [9d6ef14bc] +- Updated dependencies [a26668913] +- Updated dependencies [025e122c3] +- Updated dependencies [e9aab60c7] +- Updated dependencies [24e47ef1e] +- Updated dependencies [7881f2117] +- Updated dependencies [529d16d27] +- Updated dependencies [cdea0baf1] +- Updated dependencies [11cb5ef94] + - @backstage/plugin-techdocs-backend@0.5.5 + - @backstage/backend-common@0.5.2 + - @backstage/plugin-catalog-backend@0.6.0 + - @backstage/catalog-model@0.7.1 + - example-app@0.2.14 + - @backstage/plugin-scaffolder-backend@0.6.0 + - @backstage/plugin-app-backend@0.3.6 + ## 0.2.13 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index aa00407952..544bb976fe 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.13", + "version": "0.2.14", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", @@ -27,24 +27,24 @@ "migrate:create": "knex migrate:make -x ts" }, "dependencies": { - "@backstage/backend-common": "^0.5.1", - "@backstage/catalog-model": "^0.7.0", + "@backstage/backend-common": "^0.5.2", + "@backstage/catalog-model": "^0.7.1", "@backstage/config": "^0.1.2", - "@backstage/plugin-app-backend": "^0.3.5", + "@backstage/plugin-app-backend": "^0.3.6", "@backstage/plugin-auth-backend": "^0.2.12", - "@backstage/plugin-catalog-backend": "^0.5.5", + "@backstage/plugin-catalog-backend": "^0.6.0", "@backstage/plugin-graphql-backend": "^0.1.5", "@backstage/plugin-kubernetes-backend": "^0.2.6", "@backstage/plugin-kafka-backend": "^0.2.0", "@backstage/plugin-proxy-backend": "^0.2.4", "@backstage/plugin-rollbar-backend": "^0.1.7", - "@backstage/plugin-scaffolder-backend": "^0.5.2", - "@backstage/plugin-techdocs-backend": "^0.5.4", + "@backstage/plugin-scaffolder-backend": "^0.6.0", + "@backstage/plugin-techdocs-backend": "^0.5.5", "@gitbeaker/node": "^28.0.2", "@octokit/rest": "^18.0.12", "azure-devops-node-api": "^10.1.1", "dockerode": "^3.2.1", - "example-app": "^0.2.13", + "example-app": "^0.2.14", "express": "^4.17.1", "express-promise-router": "^3.0.3", "knex": "^0.21.6", @@ -54,7 +54,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.5.0", + "@backstage/cli": "^0.6.0", "@types/dockerode": "^3.2.1", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5" diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index c1cbd6157f..00b05e37bf 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -29,12 +29,12 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.0", + "@backstage/catalog-model": "^0.7.1", "@backstage/config": "^0.1.2", "cross-fetch": "^3.0.6" }, "devDependencies": { - "@backstage/cli": "^0.5.0", + "@backstage/cli": "^0.6.0", "@types/jest": "^26.0.7", "msw": "^0.21.2" }, diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index 6a081c3336..8d581dcb0c 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/catalog-model +## 0.7.1 + +### Patch Changes + +- 6e612ce25: Adds a new optional `links` metadata field to the Entity class within the `catalog-model` package (as discussed in [[RFC] Entity Links](https://github.com/backstage/backstage/issues/3787)). This PR adds support for the entity links only. Follow up PR's will introduce the UI component to display them. +- 025e122c3: Replace `yup` with `ajv`, for validation of catalog entities. +- 7881f2117: Introduce json schema variants of the `yup` validation schemas +- 11cb5ef94: Implement matchEntityWithRef for client side filtering of entities by ref matching + ## 0.7.0 ### Minor Changes diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index e81bc3a9b6..af74b78a08 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-model", - "version": "0.7.0", + "version": "0.7.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -39,7 +39,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", + "@backstage/cli": "^0.6.0", "@types/express": "^4.17.6", "@types/jest": "^26.0.7", "@types/lodash": "^4.14.151", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 29cce97ae9..c40f7605eb 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,48 @@ # @backstage/cli +## 0.6.0 + +### Minor Changes + +- 19fe61c27: We have updated the default `eslint` rules in the `@backstage/cli` package. + + ```diff + -'@typescript-eslint/no-shadow': 'off', + -'@typescript-eslint/no-redeclare': 'off', + +'no-shadow': 'off', + +'no-redeclare': 'off', + +'@typescript-eslint/no-shadow': 'error', + +'@typescript-eslint/no-redeclare': 'error', + ``` + + The rules are documented [here](https://eslint.org/docs/rules/no-shadow) and [here](https://eslint.org/docs/rules/no-redeclare). + + This involved a large number of small changes to the code base. When you compile your own code using the CLI, you may also be + affected. We consider these rules important, and the primary recommendation is to try to update your code according to the + documentation above. But those that prefer to not enable the rules, or need time to perform the updates, may update their + local `.eslintrc.js` file(s) in the repo root and/or in individual plugins as they see fit: + + ```js + module.exports = { + // ... other declarations + rules: { + '@typescript-eslint/no-shadow': 'off', + '@typescript-eslint/no-redeclare': 'off', + }, + }; + ``` + + Because of the nature of this change, we're unable to provide a grace period for the update :( + +### Patch Changes + +- 398e1f83e: Update `create-plugin` template to use the new composability API, by switching to exporting a single routable extension component. +- e9aab60c7: Fixed module resolution of external libraries during backend development. Modules used to be resolved relative to the backend entrypoint, but are now resolved relative to each individual module. +- a08c4b0b0: Add check for outdated/duplicate packages to yarn start +- Updated dependencies [062df71db] +- Updated dependencies [e9aab60c7] + - @backstage/config-loader@0.5.1 + ## 0.5.0 ### Minor Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index ed373f6058..fb33a154a7 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.5.0", + "version": "0.6.0", "private": false, "publishConfig": { "access": "public" @@ -30,7 +30,7 @@ "dependencies": { "@backstage/cli-common": "^0.1.1", "@backstage/config": "^0.1.2", - "@backstage/config-loader": "^0.5.0", + "@backstage/config-loader": "^0.5.1", "@hot-loader/react-dom": "^16.13.0", "@lerna/package-graph": "^3.18.5", "@lerna/project": "^3.18.0", @@ -113,12 +113,12 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-common": "^0.5.1", + "@backstage/backend-common": "^0.5.2", "@backstage/config": "^0.1.2", - "@backstage/core": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/core": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", - "@backstage/theme": "^0.2.2", + "@backstage/theme": "^0.2.3", "@types/diff": "^4.0.2", "@types/express": "^4.17.6", "@types/fs-extra": "^9.0.1", diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md index 2079c85096..cbc6473286 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/config-loader +## 0.5.1 + +### Patch Changes + +- 062df71db: Bump `config-loader` to `ajv` 7, to enable v7 feature use elsewhere +- e9aab60c7: Each piece of the configuration schema is now validated upfront, in order to produce more informative errors. + ## 0.5.0 ### Minor Changes diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 3387477231..0b024b2c84 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config-loader", "description": "Config loading functionality used by Backstage backend, and CLI", - "version": "0.5.0", + "version": "0.5.1", "private": false, "publishConfig": { "access": "public", diff --git a/packages/core-api/package.json b/packages/core-api/package.json index bbea58e846..927eca3490 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -30,7 +30,7 @@ }, "dependencies": { "@backstage/config": "^0.1.2", - "@backstage/theme": "^0.2.2", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@types/react": "^16.9", @@ -42,7 +42,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.5.0", + "@backstage/cli": "^0.6.0", "@backstage/test-utils": "^0.1.6", "@backstage/test-utils-core": "^0.1.1", "@testing-library/jest-dom": "^5.10.1", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 0fea08e539..60a6e43654 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/core +## 0.6.0 + +### Minor Changes + +- 21e624ba9: Closes #3556 + The scroll bar of collapsed sidebar is now hidden without full screen. + + ![image](https://user-images.githubusercontent.com/46953622/105390193-0bfd0080-5c19-11eb-8e86-2161bbe6e8d9.png) + +### Patch Changes + +- 12ece98cd: Add className to the SidebarItem +- d82246867: Update `WarningPanel` component to use accordion-style expansion +- 5fa3bdb55: Add `href` in addition to `onClick` to `ItemCard`. Ensure that the height of a + `ItemCard` with and without tags is equal. +- da9f53c60: Add a `prop` union for `SignInPage` that allows it to be used for just a single provider, with inline errors, and optionally with automatic sign-in. +- 32c95605f: Fix check that determines whether popup was closed or the messaging was misconfigured. +- 54c7d02f7: Introduce `TabbedLayout` for creating tabs that are routed. + + ```typescript + + +
This is rendered under /example/anything-here route
+
+
+ ``` + +- Updated dependencies [c810082ae] + - @backstage/theme@0.2.3 + ## 0.5.0 ### Minor Changes diff --git a/packages/core/package.json b/packages/core/package.json index 49090e1362..41dbc8ef14 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core", "description": "Core API used by Backstage plugins and apps", - "version": "0.5.0", + "version": "0.6.0", "private": false, "publishConfig": { "access": "public", @@ -31,7 +31,7 @@ "dependencies": { "@backstage/config": "^0.1.2", "@backstage/core-api": "^0.2.8", - "@backstage/theme": "^0.2.2", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -66,7 +66,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.5.0", + "@backstage/cli": "^0.6.0", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 6c449b33b5..7951e90ff6 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,81 @@ # @backstage/create-app +## 1.0.0 + +### Minor Changes + +- 019fe39a0: `@backstage/plugin-catalog` stopped exporting hooks and helpers for other + plugins. They are migrated to `@backstage/plugin-catalog-react`. + Change both your dependencies and imports to the new package. + +### Patch Changes + +- 436ca3f62: Remove techdocs.requestUrl and techdocs.storageUrl from app-config.yaml +- Updated dependencies [ceef4dd89] +- Updated dependencies [720149854] +- Updated dependencies [c777df180] +- Updated dependencies [398e1f83e] +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [7fc89bae2] +- Updated dependencies [c810082ae] +- Updated dependencies [b712841d6] +- Updated dependencies [a5628df40] +- Updated dependencies [2430ee7c2] +- Updated dependencies [3149bfe63] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [bc5082a00] +- Updated dependencies [6e612ce25] +- Updated dependencies [e44925723] +- Updated dependencies [b37501a3d] +- Updated dependencies [a26668913] +- Updated dependencies [025e122c3] +- Updated dependencies [e9aab60c7] +- Updated dependencies [21e624ba9] +- Updated dependencies [19fe61c27] +- Updated dependencies [e9aab60c7] +- Updated dependencies [da9f53c60] +- Updated dependencies [a08c4b0b0] +- Updated dependencies [24e47ef1e] +- Updated dependencies [bc5082a00] +- Updated dependencies [b37501a3d] +- Updated dependencies [90c8f20b9] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [529d16d27] +- Updated dependencies [54c7d02f7] +- Updated dependencies [de98c32ed] +- Updated dependencies [806929fe2] +- Updated dependencies [019fe39a0] +- Updated dependencies [cdea0baf1] +- Updated dependencies [019fe39a0] +- Updated dependencies [11cb5ef94] + - @backstage/plugin-catalog-import@0.3.7 + - @backstage/plugin-scaffolder@0.4.2 + - @backstage/plugin-techdocs-backend@0.5.5 + - @backstage/cli@0.6.0 + - @backstage/core@0.6.0 + - @backstage/plugin-api-docs@0.4.4 + - @backstage/plugin-catalog@0.3.0 + - @backstage/theme@0.2.3 + - @backstage/plugin-lighthouse@0.2.9 + - @backstage/backend-common@0.5.2 + - @backstage/plugin-catalog-backend@0.6.0 + - @backstage/plugin-techdocs@0.5.5 + - @backstage/plugin-user-settings@0.2.5 + - @backstage/catalog-model@0.7.1 + - @backstage/plugin-scaffolder-backend@0.6.0 + - @backstage/plugin-app-backend@0.3.6 + - @backstage/plugin-tech-radar@0.3.4 + - @backstage/plugin-explore@0.2.4 + - @backstage/plugin-circleci@0.2.7 + - @backstage/plugin-github-actions@0.3.1 + - @backstage/plugin-search@0.2.7 + - @backstage/test-utils@0.1.6 + - @backstage/plugin-auth-backend@0.2.12 + - @backstage/plugin-proxy-backend@0.2.4 + - @backstage/plugin-rollbar-backend@0.1.7 + ## 0.3.7 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 475b61dde8..009c3b6c61 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "Create app package for Backstage", - "version": "0.3.7", + "version": "1.0.0", "private": false, "publishConfig": { "access": "public" @@ -44,32 +44,32 @@ "ts-node": "^8.6.2" }, "peerDependencies": { - "@backstage/backend-common": "^0.5.1", - "@backstage/catalog-model": "^0.7.0", - "@backstage/cli": "^0.5.0", + "@backstage/backend-common": "^0.5.2", + "@backstage/catalog-model": "^0.7.1", + "@backstage/cli": "^0.6.0", "@backstage/config": "^0.1.2", - "@backstage/core": "^0.5.0", - "@backstage/plugin-api-docs": "^0.4.3", - "@backstage/plugin-app-backend": "^0.3.5", + "@backstage/core": "^0.6.0", + "@backstage/plugin-api-docs": "^0.4.4", + "@backstage/plugin-app-backend": "^0.3.6", "@backstage/plugin-auth-backend": "^0.2.12", - "@backstage/plugin-catalog": "^0.2.14", - "@backstage/plugin-catalog-backend": "^0.5.5", - "@backstage/plugin-catalog-import": "^0.3.6", - "@backstage/plugin-circleci": "^0.2.6", - "@backstage/plugin-explore": "^0.2.3", - "@backstage/plugin-github-actions": "^0.3.0", - "@backstage/plugin-lighthouse": "^0.2.8", + "@backstage/plugin-catalog": "^0.3.0", + "@backstage/plugin-catalog-backend": "^0.6.0", + "@backstage/plugin-catalog-import": "^0.3.7", + "@backstage/plugin-circleci": "^0.2.7", + "@backstage/plugin-explore": "^0.2.4", + "@backstage/plugin-github-actions": "^0.3.1", + "@backstage/plugin-lighthouse": "^0.2.9", "@backstage/plugin-proxy-backend": "^0.2.4", "@backstage/plugin-rollbar-backend": "^0.1.7", - "@backstage/plugin-scaffolder": "^0.4.1", - "@backstage/plugin-search": "^0.2.6", - "@backstage/plugin-scaffolder-backend": "^0.5.2", - "@backstage/plugin-tech-radar": "^0.3.3", - "@backstage/plugin-techdocs": "^0.5.4", - "@backstage/plugin-techdocs-backend": "^0.5.4", - "@backstage/plugin-user-settings": "^0.2.4", + "@backstage/plugin-scaffolder": "^0.4.2", + "@backstage/plugin-search": "^0.2.7", + "@backstage/plugin-scaffolder-backend": "^0.6.0", + "@backstage/plugin-tech-radar": "^0.3.4", + "@backstage/plugin-techdocs": "^0.5.5", + "@backstage/plugin-techdocs-backend": "^0.5.5", + "@backstage/plugin-user-settings": "^0.2.5", "@backstage/test-utils": "^0.1.6", - "@backstage/theme": "^0.2.2" + "@backstage/theme": "^0.2.3" }, "nodemonConfig": { "watch": "./src", diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index c6c740bf39..251a4db7ee 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/dev-utils +## 0.1.9 + +### Patch Changes + +- 720149854: Added `path` option to `addPage` that can be used to set a specific path for the page rather than a generated one. Also omit sidebar item altogether if `title` option is not set. +- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`. +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [7fc89bae2] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [54c7d02f7] +- Updated dependencies [11cb5ef94] + - @backstage/core@0.6.0 + - @backstage/plugin-catalog-react@0.0.2 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + ## 0.1.8 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index eff2d6e218..3a01306b46 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/dev-utils", "description": "Utilities for developing Backstage plugins.", - "version": "0.1.8", + "version": "0.1.9", "private": false, "publishConfig": { "access": "public", @@ -29,11 +29,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.5.0", - "@backstage/catalog-model": "^0.7.0", - "@backstage/plugin-catalog-react": "^0.0.1", + "@backstage/core": "^0.6.0", + "@backstage/catalog-model": "^0.7.1", + "@backstage/plugin-catalog-react": "^0.0.2", "@backstage/test-utils": "^0.1.5", - "@backstage/theme": "^0.2.2", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@testing-library/jest-dom": "^5.10.1", @@ -47,7 +47,7 @@ "react-router-dom": "6.0.0-beta.0" }, "devDependencies": { - "@backstage/cli": "^0.5.0", + "@backstage/cli": "^0.6.0", "@types/jest": "^26.0.7", "@types/node": "^12.0.0" }, diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index 11eb164535..11839d733b 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/integration +## 0.3.2 + +### Patch Changes + +- c4abcdb60: Fix GitLab handling of paths with spaces +- 064c513e1: Properly forward errors that occur when looking up GitLab project IDs. +- 3149bfe63: Add a `resolveUrl` method to integrations, that works like the two-argument URL + constructor. The reason for using this is that Azure have their paths in a + query parameter, rather than the pathname of the URL. + + The implementation is optional (when not present, the URL constructor is used), + so this does not imply a breaking change. + +- 2e62aea6f: #4322 Bitbucket own hosted v5.11.1 branchUrl fix and enabled error tracing… #4347 + ## 0.3.1 ### Patch Changes diff --git a/packages/integration/package.json b/packages/integration/package.json index 06a83ea883..f82472ca97 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration", - "version": "0.3.1", + "version": "0.3.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -37,7 +37,7 @@ "luxon": "^1.25.0" }, "devDependencies": { - "@backstage/cli": "^0.5.0", + "@backstage/cli": "^0.6.0", "@backstage/test-utils": "^0.1.5", "@types/jest": "^26.0.7", "@types/luxon": "^1.25.0", diff --git a/packages/techdocs-common/CHANGELOG.md b/packages/techdocs-common/CHANGELOG.md index 568df3157c..baf7a380a2 100644 --- a/packages/techdocs-common/CHANGELOG.md +++ b/packages/techdocs-common/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/techdocs-common +## 0.3.7 + +### Patch Changes + +- c777df180: 1. Added option to use Azure Blob Storage as a choice to store the static generated files for TechDocs. +- e44925723: `techdocs.requestUrl` and `techdocs.storageUrl` are now optional configs and the discovery API will be used to get the URL where techdocs plugin is hosted. +- f0320190d: dir preparer will use URL Reader in its implementation. +- Updated dependencies [c4abcdb60] +- Updated dependencies [2430ee7c2] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [064c513e1] +- Updated dependencies [7881f2117] +- Updated dependencies [3149bfe63] +- Updated dependencies [2e62aea6f] +- Updated dependencies [11cb5ef94] + - @backstage/integration@0.3.2 + - @backstage/backend-common@0.5.2 + - @backstage/catalog-model@0.7.1 + ## 0.3.6 ### Patch Changes diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json index 2a065600a1..9b0f7fbf9f 100644 --- a/packages/techdocs-common/package.json +++ b/packages/techdocs-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/techdocs-common", "description": "Common functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", - "version": "0.3.6", + "version": "0.3.7", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -39,10 +39,10 @@ "@aws-sdk/client-s3": "^3.1.0", "@azure/identity": "^1.2.2", "@azure/storage-blob": "^12.4.0", - "@backstage/backend-common": "^0.5.1", - "@backstage/catalog-model": "^0.7.0", + "@backstage/backend-common": "^0.5.2", + "@backstage/catalog-model": "^0.7.1", "@backstage/config": "^0.1.2", - "@backstage/integration": "^0.3.1", + "@backstage/integration": "^0.3.2", "@google-cloud/storage": "^5.6.0", "@types/dockerode": "^3.2.1", "@types/express": "^4.17.6", @@ -61,7 +61,7 @@ }, "devDependencies": { "@aws-sdk/types": "3.1.0", - "@backstage/cli": "^0.5.0", + "@backstage/cli": "^0.6.0", "@types/fs-extra": "^9.0.5", "@types/git-url-parse": "^9.0.0", "@types/js-yaml": "^3.12.5", diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 10f0e89c14..5cea83fa03 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -31,7 +31,7 @@ "dependencies": { "@backstage/core-api": "^0.2.7", "@backstage/test-utils-core": "^0.1.1", - "@backstage/theme": "^0.2.2", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", @@ -45,7 +45,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.5.0", + "@backstage/cli": "^0.6.0", "@types/jest": "^26.0.7", "@types/node": "^12.0.0" }, diff --git a/packages/theme/CHANGELOG.md b/packages/theme/CHANGELOG.md index 13d95ab856..4ee58b2201 100644 --- a/packages/theme/CHANGELOG.md +++ b/packages/theme/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/theme +## 0.2.3 + +### Patch Changes + +- c810082ae: Updates warning text color to align to updated `WarningPanel` styling + ## 0.2.2 ### Patch Changes diff --git a/packages/theme/package.json b/packages/theme/package.json index 24edf81bb6..31abd9acff 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/theme", "description": "material-ui theme for use with Backstage.", - "version": "0.2.2", + "version": "0.2.3", "private": false, "publishConfig": { "access": "public", @@ -31,7 +31,7 @@ "@material-ui/core": "^4.11.0" }, "devDependencies": { - "@backstage/cli": "^0.5.0" + "@backstage/cli": "^0.6.0" }, "files": [ "dist" diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 01e34d8e1d..4735beb150 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-api-docs +## 0.4.4 + +### Patch Changes + +- 7fc89bae2: Display owner and system as entity page links in the tables of the `api-docs` + plugin. + + Move `isOwnerOf` and `getEntityRelations` from `@backstage/plugin-catalog` to + `@backstage/plugin-catalog-react` and export it from there to use it by other + plugins. + +- bc5082a00: Migrate to new composability API, exporting the plugin as `apiDocsPlugin`, index page as `ApiExplorerPage`, and entity page cards as `EntityApiDefinitionCard`, `EntityConsumedApisCard`, `EntityConsumingComponentsCard`, `EntityProvidedApisCard`, and `EntityProvidingComponentsCard`. +- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`. +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [7fc89bae2] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [54c7d02f7] +- Updated dependencies [11cb5ef94] + - @backstage/core@0.6.0 + - @backstage/plugin-catalog-react@0.0.2 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + ## 0.4.3 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 4bd7bd1727..175b831ada 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.4.3", + "version": "0.4.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,10 +30,10 @@ }, "dependencies": { "@asyncapi/react-component": "^0.18.2", - "@backstage/catalog-model": "^0.7.0", - "@backstage/core": "^0.5.0", - "@backstage/plugin-catalog-react": "^0.0.1", - "@backstage/theme": "^0.2.2", + "@backstage/catalog-model": "^0.7.1", + "@backstage/core": "^0.6.0", + "@backstage/plugin-catalog-react": "^0.0.2", + "@backstage/theme": "^0.2.3", "@material-icons/font": "^1.0.2", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -49,8 +49,8 @@ "swagger-ui-react": "^3.37.2" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index 514e32633a..b57e0d25fb 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-app-backend +## 0.3.6 + +### Patch Changes + +- e9aab60c7: Failures to load the frontend configuration schema now throws an error that includes more context and instructions for how to fix the issue. +- Updated dependencies [2430ee7c2] +- Updated dependencies [062df71db] +- Updated dependencies [e9aab60c7] + - @backstage/backend-common@0.5.2 + - @backstage/config-loader@0.5.1 + ## 0.3.5 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index f397ea1475..cca36a51f8 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-backend", - "version": "0.3.5", + "version": "0.3.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,8 +29,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.5.1", - "@backstage/config-loader": "^0.5.0", + "@backstage/backend-common": "^0.5.2", + "@backstage/config-loader": "^0.5.1", "@backstage/config": "^0.1.2", "@types/express": "^4.17.6", "express": "^4.17.1", @@ -40,7 +40,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.5.0", + "@backstage/cli": "^0.6.0", "@types/supertest": "^2.0.8", "msw": "^0.20.5", "supertest": "^4.0.2" diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index b2198165a5..e87c2b4a90 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -29,9 +29,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.5.1", + "@backstage/backend-common": "^0.5.2", "@backstage/catalog-client": "^0.3.5", - "@backstage/catalog-model": "^0.7.0", + "@backstage/catalog-model": "^0.7.1", "@backstage/config": "^0.1.2", "@types/express": "^4.17.6", "compression": "^1.7.4", @@ -65,7 +65,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.5.0", + "@backstage/cli": "^0.6.0", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/express-session": "^1.17.2", diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 0e446bf0f7..eb256d2c08 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-catalog-backend +## 0.6.0 + +### Minor Changes + +- 3149bfe63: Make use of the `resolveUrl` facility of the `integration` package. + + Also rename the `LocationRefProcessor` to `LocationEntityProcessor`, to match the file name. This constitutes an interface change since the class is exported, but it is unlikely to be consumed outside of the package since it sits comfortably with the other default processors inside the catalog builder. + +### Patch Changes + +- 24e47ef1e: Throw `NotAllowedError` when registering locations with entities of disallowed kinds +- Updated dependencies [c4abcdb60] +- Updated dependencies [2430ee7c2] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [064c513e1] +- Updated dependencies [7881f2117] +- Updated dependencies [3149bfe63] +- Updated dependencies [2e62aea6f] +- Updated dependencies [11cb5ef94] + - @backstage/integration@0.3.2 + - @backstage/backend-common@0.5.2 + - @backstage/catalog-model@0.7.1 + ## 0.5.5 ### Patch Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index cc0a9e412a..bb76441f3f 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "0.5.5", + "version": "0.6.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "dependencies": { "@aws-sdk/client-organizations": "^3.2.0", "@azure/msal-node": "^1.0.0-beta.3", - "@backstage/backend-common": "^0.5.1", - "@backstage/catalog-model": "^0.7.0", + "@backstage/backend-common": "^0.5.2", + "@backstage/catalog-model": "^0.7.1", "@backstage/config": "^0.1.2", - "@backstage/integration": "^0.3.1", + "@backstage/integration": "^0.3.2", "@octokit/graphql": "^4.5.8", "@types/express": "^4.17.6", "@types/ldapjs": "^1.0.9", @@ -58,7 +58,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", + "@backstage/cli": "^0.6.0", "@backstage/test-utils": "^0.1.6", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index c81c29fc3e..9d76545909 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -30,8 +30,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.5.1", - "@backstage/catalog-model": "^0.7.0", + "@backstage/backend-common": "^0.5.2", + "@backstage/catalog-model": "^0.7.1", "@backstage/config": "^0.1.2", "@graphql-modules/core": "^0.7.17", "apollo-server": "^2.16.1", @@ -42,7 +42,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.5.0", + "@backstage/cli": "^0.6.0", "@backstage/test-utils": "^0.1.5", "@graphql-codegen/cli": "^1.17.7", "@graphql-codegen/typescript": "^1.17.7", diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index c6e4995c70..0311d865a9 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-catalog-import +## 0.3.7 + +### Patch Changes + +- ceef4dd89: Export _api_ (Client, API, ref) from the catalog import plugin. +- b712841d6: Migrated to new composability API, exporting the plugin instance as `catalogImportPlugin`, and the page as `CatalogImportPage`. +- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`. +- Updated dependencies [12ece98cd] +- Updated dependencies [c4abcdb60] +- Updated dependencies [d82246867] +- Updated dependencies [7fc89bae2] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [064c513e1] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [3149bfe63] +- Updated dependencies [54c7d02f7] +- Updated dependencies [2e62aea6f] +- Updated dependencies [11cb5ef94] + - @backstage/core@0.6.0 + - @backstage/integration@0.3.2 + - @backstage/plugin-catalog-react@0.0.2 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + ## 0.3.6 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 7f3cc3d1e3..2b2b016716 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.3.6", + "version": "0.3.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,11 +30,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.0", - "@backstage/core": "^0.5.0", - "@backstage/integration": "^0.3.1", - "@backstage/plugin-catalog-react": "^0.0.1", - "@backstage/theme": "^0.2.2", + "@backstage/catalog-model": "^0.7.1", + "@backstage/core": "^0.6.0", + "@backstage/integration": "^0.3.2", + "@backstage/plugin-catalog-react": "^0.0.2", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -50,8 +50,8 @@ }, "devDependencies": { "@backstage/catalog-client": "^0.3.5", - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md new file mode 100644 index 0000000000..90b7c80caf --- /dev/null +++ b/plugins/catalog-react/CHANGELOG.md @@ -0,0 +1,26 @@ +# @backstage/plugin-catalog-react + +## 0.0.2 + +### Patch Changes + +- 7fc89bae2: Display owner and system as entity page links in the tables of the `api-docs` + plugin. + + Move `isOwnerOf` and `getEntityRelations` from `@backstage/plugin-catalog` to + `@backstage/plugin-catalog-react` and export it from there to use it by other + plugins. + +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [54c7d02f7] +- Updated dependencies [11cb5ef94] + - @backstage/core@0.6.0 + - @backstage/catalog-model@0.7.1 diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 94dc835d14..64307f6846 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-react", - "version": "0.0.1", + "version": "0.0.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,8 +29,8 @@ }, "dependencies": { "@backstage/catalog-client": "^0.3.5", - "@backstage/catalog-model": "^0.7.0", - "@backstage/core": "^0.5.0", + "@backstage/catalog-model": "^0.7.1", + "@backstage/core": "^0.6.0", "@material-ui/core": "^4.11.0", "@types/react": "^16.9", "react": "^16.13.1", @@ -39,8 +39,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 3920d8696d..eb4c9a30d7 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,55 @@ # @backstage/plugin-catalog +## 0.3.0 + +### Minor Changes + +- 019fe39a0: `@backstage/plugin-catalog` stopped exporting hooks and helpers for other + plugins. They are migrated to `@backstage/plugin-catalog-react`. + Change both your dependencies and imports to the new package. + +### Patch Changes + +- 7fc89bae2: Display owner and system as entity page links in the tables of the `api-docs` + plugin. + + Move `isOwnerOf` and `getEntityRelations` from `@backstage/plugin-catalog` to + `@backstage/plugin-catalog-react` and export it from there to use it by other + plugins. + +- b37501a3d: Add `children` option to `addPage`, which will be rendered as the children of the `Route`. +- b37501a3d: Finalize migration to new composability API, with the plugin instance now exported `catalogPlugin`. +- 54c7d02f7: Introduce `TabbedLayout` for creating tabs that are routed. + + ```typescript + + +
This is rendered under /example/anything-here route
+
+
+ ``` + +- Updated dependencies [720149854] +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [7fc89bae2] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [54c7d02f7] +- Updated dependencies [019fe39a0] +- Updated dependencies [11cb5ef94] + - @backstage/plugin-scaffolder@0.4.2 + - @backstage/core@0.6.0 + - @backstage/plugin-catalog-react@0.0.2 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + ## 0.2.14 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index bf30e28571..c806c58e67 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "0.2.14", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,11 +31,11 @@ }, "dependencies": { "@backstage/catalog-client": "^0.3.5", - "@backstage/catalog-model": "^0.7.0", - "@backstage/core": "^0.5.0", - "@backstage/plugin-catalog-react": "^0.0.1", - "@backstage/plugin-scaffolder": "^0.4.1", - "@backstage/theme": "^0.2.2", + "@backstage/catalog-model": "^0.7.1", + "@backstage/core": "^0.6.0", + "@backstage/plugin-catalog-react": "^0.0.2", + "@backstage/plugin-scaffolder": "^0.4.2", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -52,8 +52,8 @@ "swr": "^0.3.0" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@microsoft/microsoft-graph-types": "^1.25.0", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/circleci/CHANGELOG.md b/plugins/circleci/CHANGELOG.md index 45025a94e3..99715821a6 100644 --- a/plugins/circleci/CHANGELOG.md +++ b/plugins/circleci/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-circleci +## 0.2.7 + +### Patch Changes + +- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`. +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [7fc89bae2] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [54c7d02f7] +- Updated dependencies [11cb5ef94] + - @backstage/core@0.6.0 + - @backstage/plugin-catalog-react@0.0.2 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + ## 0.2.6 ### Patch Changes diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index f128540784..37e4425b23 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-circleci", - "version": "0.2.6", + "version": "0.2.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "postpack": "backstage-cli postpack" }, "dependencies": { - "@backstage/catalog-model": "^0.7.0", - "@backstage/core": "^0.5.0", - "@backstage/plugin-catalog-react": "^0.0.1", - "@backstage/theme": "^0.2.2", + "@backstage/catalog-model": "^0.7.1", + "@backstage/core": "^0.6.0", + "@backstage/plugin-catalog-react": "^0.0.2", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -50,8 +50,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/cloudbuild/CHANGELOG.md b/plugins/cloudbuild/CHANGELOG.md index d174677f51..ae3f8faada 100644 --- a/plugins/cloudbuild/CHANGELOG.md +++ b/plugins/cloudbuild/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-cloudbuild +## 0.2.8 + +### Patch Changes + +- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`. +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [54c7d02f7] +- Updated dependencies [11cb5ef94] + - @backstage/core@0.6.0 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + ## 0.2.7 ### Patch Changes diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index 3e5bbef3a9..41a5285d7d 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-cloudbuild", - "version": "0.2.7", + "version": "0.2.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,9 +30,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.0", - "@backstage/core": "^0.5.0", - "@backstage/theme": "^0.2.2", + "@backstage/catalog-model": "^0.7.1", + "@backstage/core": "^0.6.0", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -46,8 +46,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/cost-insights/CHANGELOG.md b/plugins/cost-insights/CHANGELOG.md index 78d4c69da4..87bfd3af07 100644 --- a/plugins/cost-insights/CHANGELOG.md +++ b/plugins/cost-insights/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-cost-insights +## 0.8.0 + +### Minor Changes + +- 19172f5a9: add alert hooks + +### Patch Changes + +- 4c6a6dddd: Fixed date calculations incorrectly converting to UTC in some cases. This should be a transparent change. +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [54c7d02f7] + - @backstage/core@0.6.0 + - @backstage/theme@0.2.3 + ## 0.7.0 ### Minor Changes diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index 022e778150..0184d50517 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-cost-insights", - "version": "0.7.0", + "version": "0.8.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,8 +31,8 @@ }, "dependencies": { "@backstage/config": "^0.1.2", - "@backstage/core": "^0.5.0", - "@backstage/theme": "^0.2.2", + "@backstage/core": "^0.6.0", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -55,8 +55,8 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/explore-react/CHANGELOG.md b/plugins/explore-react/CHANGELOG.md new file mode 100644 index 0000000000..1b58da0f04 --- /dev/null +++ b/plugins/explore-react/CHANGELOG.md @@ -0,0 +1,14 @@ +# @backstage/plugin-explore-react + +## 0.0.2 + +### Patch Changes + +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [54c7d02f7] + - @backstage/core@0.6.0 diff --git a/plugins/explore-react/package.json b/plugins/explore-react/package.json index 80d3d9d21e..924c723a67 100644 --- a/plugins/explore-react/package.json +++ b/plugins/explore-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-explore-react", - "version": "0.0.1", + "version": "0.0.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -28,11 +28,11 @@ "postpack": "backstage-cli postpack" }, "dependencies": { - "@backstage/core": "^0.5.0" + "@backstage/core": "^0.6.0" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/explore/CHANGELOG.md b/plugins/explore/CHANGELOG.md index 75a0471ce7..0d2daf37b5 100644 --- a/plugins/explore/CHANGELOG.md +++ b/plugins/explore/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/plugin-explore +## 0.2.4 + +### Patch Changes + +- 54c7d02f7: Introduce `TabbedLayout` for creating tabs that are routed. + + ```typescript + + +
This is rendered under /example/anything-here route
+
+
+ ``` + +- 806929fe2: Rework the explore plugin to allow the user to explore things in the ecosystem, + including tools and domains. +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [7fc89bae2] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [54c7d02f7] +- Updated dependencies [11cb5ef94] + - @backstage/core@0.6.0 + - @backstage/plugin-catalog-react@0.0.2 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + - @backstage/plugin-explore-react@0.0.2 + ## 0.2.3 ### Patch Changes diff --git a/plugins/explore/package.json b/plugins/explore/package.json index 2afa686355..e9d21cc221 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-explore", - "version": "0.2.3", + "version": "0.2.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,11 +30,11 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/catalog-model": "^0.7.0", - "@backstage/core": "^0.5.0", - "@backstage/plugin-catalog-react": "^0.0.1", - "@backstage/plugin-explore-react": "^0.0.1", - "@backstage/theme": "^0.2.2", + "@backstage/catalog-model": "^0.7.1", + "@backstage/core": "^0.6.0", + "@backstage/plugin-catalog-react": "^0.0.2", + "@backstage/plugin-explore-react": "^0.0.2", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -45,8 +45,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/fossa/CHANGELOG.md b/plugins/fossa/CHANGELOG.md index 6fa8584017..de1e2fda06 100644 --- a/plugins/fossa/CHANGELOG.md +++ b/plugins/fossa/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-fossa +## 0.2.0 + +### Minor Changes + +- 5ac9df899: Port FOSSA plugin to new extension model. + + If you are using the FOSSA plugin adjust the plugin import from `plugin` to + `fossaPlugin` and replace `` with ``. + +### Patch Changes + +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [7fc89bae2] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [54c7d02f7] +- Updated dependencies [11cb5ef94] + - @backstage/core@0.6.0 + - @backstage/plugin-catalog-react@0.0.2 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + ## 0.1.2 ### Patch Changes diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index 879f12c6f6..74f04e3fd0 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-fossa", - "version": "0.1.2", + "version": "0.2.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.0", - "@backstage/core": "^0.5.0", - "@backstage/plugin-catalog-react": "^0.0.1", - "@backstage/theme": "^0.2.2", + "@backstage/catalog-model": "^0.7.1", + "@backstage/core": "^0.6.0", + "@backstage/plugin-catalog-react": "^0.0.2", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -44,8 +44,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/gcp-projects/CHANGELOG.md b/plugins/gcp-projects/CHANGELOG.md index 3b4a168644..92f1da463e 100644 --- a/plugins/gcp-projects/CHANGELOG.md +++ b/plugins/gcp-projects/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-gcp-projects +## 0.2.4 + +### Patch Changes + +- bc5082a00: Migrate to new composability API, exporting the plugin as `gcpProjectsPlugin` and page as `GcpProjectsPage`. +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [54c7d02f7] + - @backstage/core@0.6.0 + - @backstage/theme@0.2.3 + ## 0.2.3 ### Patch Changes diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index d48295e204..fced060d99 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-gcp-projects", - "version": "0.2.3", + "version": "0.2.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,8 +30,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.5.0", - "@backstage/theme": "^0.2.2", + "@backstage/core": "^0.6.0", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -41,8 +41,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/github-actions/CHANGELOG.md b/plugins/github-actions/CHANGELOG.md index 1bd0481610..a9bbf67a9a 100644 --- a/plugins/github-actions/CHANGELOG.md +++ b/plugins/github-actions/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-github-actions +## 0.3.1 + +### Patch Changes + +- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`. +- Updated dependencies [12ece98cd] +- Updated dependencies [c4abcdb60] +- Updated dependencies [d82246867] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [064c513e1] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [3149bfe63] +- Updated dependencies [54c7d02f7] +- Updated dependencies [2e62aea6f] +- Updated dependencies [11cb5ef94] + - @backstage/core@0.6.0 + - @backstage/integration@0.3.2 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + ## 0.3.0 ### Minor Changes diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index beb35b3cb0..49f1b0007a 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-github-actions", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,10 +32,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.0", - "@backstage/core": "^0.5.0", - "@backstage/integration": "^0.3.1", - "@backstage/theme": "^0.2.2", + "@backstage/catalog-model": "^0.7.1", + "@backstage/core": "^0.6.0", + "@backstage/integration": "^0.3.2", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -49,8 +49,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/gitops-profiles/CHANGELOG.md b/plugins/gitops-profiles/CHANGELOG.md index 5aa38c65e5..0fa9fa24a2 100644 --- a/plugins/gitops-profiles/CHANGELOG.md +++ b/plugins/gitops-profiles/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-gitops-profiles +## 0.2.4 + +### Patch Changes + +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [54c7d02f7] + - @backstage/core@0.6.0 + - @backstage/theme@0.2.3 + ## 0.2.3 ### Patch Changes diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index 447c3cbb81..c25628fa62 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-gitops-profiles", - "version": "0.2.3", + "version": "0.2.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,8 +31,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.5.0", - "@backstage/theme": "^0.2.2", + "@backstage/core": "^0.6.0", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -42,8 +42,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/graphiql/CHANGELOG.md b/plugins/graphiql/CHANGELOG.md index 88e6cc63e5..d6f4864a2f 100644 --- a/plugins/graphiql/CHANGELOG.md +++ b/plugins/graphiql/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-graphiql +## 0.2.7 + +### Patch Changes + +- 87b189d00: Finalized composability API migration, now exporting the plugin as `graphiqlPlugin`. +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [54c7d02f7] + - @backstage/core@0.6.0 + - @backstage/theme@0.2.3 + ## 0.2.6 ### Patch Changes diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index 850b29108e..9293a07dd7 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-graphiql", "description": "Backstage plugin for browsing GraphQL APIs", - "version": "0.2.6", + "version": "0.2.7", "private": false, "publishConfig": { "access": "public", @@ -31,8 +31,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.5.0", - "@backstage/theme": "^0.2.2", + "@backstage/core": "^0.6.0", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -43,8 +43,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json index 99d369b1a7..ab12ee5278 100644 --- a/plugins/graphql/package.json +++ b/plugins/graphql/package.json @@ -29,7 +29,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.5.1", + "@backstage/backend-common": "^0.5.2", "@backstage/config": "^0.1.2", "@backstage/plugin-catalog-graphql": "^0.2.6", "@graphql-modules/core": "^0.7.17", @@ -45,7 +45,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.5.0", + "@backstage/cli": "^0.6.0", "@types/supertest": "^2.0.8", "eslint-plugin-graphql": "^4.0.0", "msw": "^0.20.5", diff --git a/plugins/jenkins/CHANGELOG.md b/plugins/jenkins/CHANGELOG.md index c49857c7a5..2199c58949 100644 --- a/plugins/jenkins/CHANGELOG.md +++ b/plugins/jenkins/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-jenkins +## 0.3.8 + +### Patch Changes + +- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`. +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [7fc89bae2] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [54c7d02f7] +- Updated dependencies [11cb5ef94] + - @backstage/core@0.6.0 + - @backstage/plugin-catalog-react@0.0.2 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + ## 0.3.7 ### Patch Changes diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index 7456d4be53..5d59d2e5ab 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-jenkins", - "version": "0.3.7", + "version": "0.3.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.0", - "@backstage/core": "^0.5.0", - "@backstage/plugin-catalog-react": "^0.0.1", - "@backstage/theme": "^0.2.2", + "@backstage/catalog-model": "^0.7.1", + "@backstage/core": "^0.6.0", + "@backstage/plugin-catalog-react": "^0.0.2", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -47,8 +47,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/kafka-backend/package.json b/plugins/kafka-backend/package.json index 311464b47d..1eaa1d8316 100644 --- a/plugins/kafka-backend/package.json +++ b/plugins/kafka-backend/package.json @@ -31,8 +31,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.5.1", - "@backstage/catalog-model": "^0.7.0", + "@backstage/backend-common": "^0.5.2", + "@backstage/catalog-model": "^0.7.1", "@backstage/config": "^0.1.2", "@types/express": "^4.17.6", "express": "^4.17.1", @@ -42,7 +42,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.5.0", + "@backstage/cli": "^0.6.0", "@types/jest-when": "^2.7.2", "@types/lodash": "^4.14.151", "jest-when": "^3.1.0", diff --git a/plugins/kafka/CHANGELOG.md b/plugins/kafka/CHANGELOG.md index 92f49a1d35..2895288b56 100644 --- a/plugins/kafka/CHANGELOG.md +++ b/plugins/kafka/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-kafka +## 0.2.1 + +### Patch Changes + +- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`. +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [7fc89bae2] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [54c7d02f7] +- Updated dependencies [11cb5ef94] + - @backstage/core@0.6.0 + - @backstage/plugin-catalog-react@0.0.2 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + ## 0.2.0 ### Minor Changes diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index 546d20bc67..5d6454ed60 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kafka", - "version": "0.2.0", + "version": "0.2.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,10 +20,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.0", - "@backstage/core": "^0.5.0", - "@backstage/plugin-catalog-react": "^0.0.1", - "@backstage/theme": "^0.2.2", + "@backstage/catalog-model": "^0.7.1", + "@backstage/core": "^0.6.0", + "@backstage/plugin-catalog-react": "^0.0.2", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -33,8 +33,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index e69a82b738..3823ee1899 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -32,8 +32,8 @@ }, "dependencies": { "@aws-sdk/credential-provider-node": "^3.3.0", - "@backstage/backend-common": "^0.5.1", - "@backstage/catalog-model": "^0.7.0", + "@backstage/backend-common": "^0.5.2", + "@backstage/catalog-model": "^0.7.1", "@backstage/config": "^0.1.2", "@kubernetes/client-node": "^0.13.2", "@types/express": "^4.17.6", @@ -51,7 +51,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.5.0", + "@backstage/cli": "^0.6.0", "@types/aws4": "^1.5.1", "supertest": "^4.0.2" }, diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index f16728e1fe..311bd5edaa 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-kubernetes +## 0.3.8 + +### Patch Changes + +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [54c7d02f7] +- Updated dependencies [11cb5ef94] + - @backstage/core@0.6.0 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + ## 0.3.7 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 1d985c0d10..8598aec233 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes", - "version": "0.3.7", + "version": "0.3.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,11 +31,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.0", + "@backstage/catalog-model": "^0.7.1", "@backstage/config": "^0.1.2", - "@backstage/core": "^0.5.0", + "@backstage/core": "^0.6.0", "@backstage/plugin-kubernetes-backend": "^0.2.6", - "@backstage/theme": "^0.2.2", + "@backstage/theme": "^0.2.3", "@kubernetes/client-node": "^0.13.2", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -47,8 +47,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/lighthouse/CHANGELOG.md b/plugins/lighthouse/CHANGELOG.md index 301a9b4fed..8fec8887c3 100644 --- a/plugins/lighthouse/CHANGELOG.md +++ b/plugins/lighthouse/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-lighthouse +## 0.2.9 + +### Patch Changes + +- a5628df40: Migrate to new composability API, exporting the plugin instance as `lighthousePlugin`, the top-level page as `LighthousePage`, the entity card as `EntityLastLighthouseAuditCard`, and the entity content as `EntityLighthouseContent`. +- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`. +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [7fc89bae2] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [54c7d02f7] +- Updated dependencies [11cb5ef94] + - @backstage/core@0.6.0 + - @backstage/plugin-catalog-react@0.0.2 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + ## 0.2.8 ### Patch Changes diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 5b45bfa48b..07e1ad8475 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-lighthouse", - "version": "0.2.8", + "version": "0.2.9", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,11 +31,11 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/catalog-model": "^0.7.0", + "@backstage/catalog-model": "^0.7.1", "@backstage/config": "^0.1.2", - "@backstage/core": "^0.5.0", - "@backstage/plugin-catalog-react": "^0.0.1", - "@backstage/theme": "^0.2.2", + "@backstage/core": "^0.6.0", + "@backstage/plugin-catalog-react": "^0.0.2", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -46,8 +46,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/newrelic/CHANGELOG.md b/plugins/newrelic/CHANGELOG.md index d7806c4081..6eae73bc86 100644 --- a/plugins/newrelic/CHANGELOG.md +++ b/plugins/newrelic/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-newrelic +## 0.2.4 + +### Patch Changes + +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [54c7d02f7] + - @backstage/core@0.6.0 + - @backstage/theme@0.2.3 + ## 0.2.3 ### Patch Changes diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index 2f43c9cbeb..ccd43c42c8 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-newrelic", - "version": "0.2.3", + "version": "0.2.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,8 +31,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.5.0", - "@backstage/theme": "^0.2.2", + "@backstage/core": "^0.6.0", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -41,8 +41,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index 17dd37266f..6101685e1d 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-org +## 0.3.5 + +### Patch Changes + +- 7fc89bae2: Display owner and system as entity page links in the tables of the `api-docs` + plugin. + + Move `isOwnerOf` and `getEntityRelations` from `@backstage/plugin-catalog` to + `@backstage/plugin-catalog-react` and export it from there to use it by other + plugins. + +- 0269f4fd9: Migrate to new composability API, exporting the plugin instance as `orgPlugin`, and the entity cards as `EntityGroupProfileCard`, `EntityMembersListCard`, `EntityOwnershipCard`, and `EntityUserProfileCard`. +- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`. +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [7fc89bae2] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [54c7d02f7] +- Updated dependencies [11cb5ef94] + - @backstage/core@0.6.0 + - @backstage/plugin-catalog-react@0.0.2 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + ## 0.3.4 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index e376dea5bc..0491554f77 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org", - "version": "0.3.4", + "version": "0.3.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,10 +20,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.0", - "@backstage/core": "^0.5.0", - "@backstage/plugin-catalog-react": "^0.0.1", - "@backstage/theme": "^0.2.2", + "@backstage/catalog-model": "^0.7.1", + "@backstage/core": "^0.6.0", + "@backstage/plugin-catalog-react": "^0.0.2", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -33,8 +33,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/pagerduty/CHANGELOG.md b/plugins/pagerduty/CHANGELOG.md index d27becfa47..eb00ce36be 100644 --- a/plugins/pagerduty/CHANGELOG.md +++ b/plugins/pagerduty/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-pagerduty +## 0.2.7 + +### Patch Changes + +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [54c7d02f7] +- Updated dependencies [11cb5ef94] + - @backstage/core@0.6.0 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + ## 0.2.6 ### Patch Changes diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index 0e87bdc2d7..a5db20e9ab 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-pagerduty", - "version": "0.2.6", + "version": "0.2.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,9 +30,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.0", - "@backstage/core": "^0.5.0", - "@backstage/theme": "^0.2.2", + "@backstage/catalog-model": "^0.7.1", + "@backstage/core": "^0.6.0", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -44,8 +44,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index ae828cb2ec..51ccf98f8b 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -28,7 +28,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.5.1", + "@backstage/backend-common": "^0.5.2", "@backstage/config": "^0.1.2", "@types/express": "^4.17.6", "express": "^4.17.1", @@ -42,7 +42,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", + "@backstage/cli": "^0.6.0", "@types/http-proxy-middleware": "^0.19.3", "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", diff --git a/plugins/register-component/CHANGELOG.md b/plugins/register-component/CHANGELOG.md index abc2fdbd58..901c556052 100644 --- a/plugins/register-component/CHANGELOG.md +++ b/plugins/register-component/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-register-component +## 0.2.8 + +### Patch Changes + +- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`. +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [7fc89bae2] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [54c7d02f7] +- Updated dependencies [11cb5ef94] + - @backstage/core@0.6.0 + - @backstage/plugin-catalog-react@0.0.2 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + ## 0.2.7 ### Patch Changes diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index 39c431b3cd..9e0f045cd4 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-register-component", - "version": "0.2.7", + "version": "0.2.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,10 +30,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.0", - "@backstage/core": "^0.5.0", - "@backstage/plugin-catalog-react": "^0.0.1", - "@backstage/theme": "^0.2.2", + "@backstage/catalog-model": "^0.7.1", + "@backstage/core": "^0.6.0", + "@backstage/plugin-catalog-react": "^0.0.2", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -45,8 +45,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index 522f5a3b4c..ff7a4f06f5 100644 --- a/plugins/rollbar-backend/package.json +++ b/plugins/rollbar-backend/package.json @@ -30,7 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.5.1", + "@backstage/backend-common": "^0.5.2", "@backstage/config": "^0.1.2", "@types/express": "^4.17.6", "axios": "^0.21.1", @@ -47,7 +47,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.5.0", + "@backstage/cli": "^0.6.0", "@types/supertest": "^2.0.8", "supertest": "^4.0.2" }, diff --git a/plugins/rollbar/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md index d317640948..e49c843116 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-rollbar +## 0.2.9 + +### Patch Changes + +- 9d6ef14bc: Migrated to new composability API, exporting the plugin instance as `rollbarPlugin`, the entity page content as `EntityRollbarContent`, and entity conditional as `isRollbarAvailable`. Updated the `EntityPage` for the `example-app` to include a composite `ErrorsSwitcher` component that works with both `Sentry` & `Rollbar`. Also removed the unused and undocumented `RollbarHome` related components. +- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`. +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [7fc89bae2] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [54c7d02f7] +- Updated dependencies [11cb5ef94] + - @backstage/core@0.6.0 + - @backstage/plugin-catalog-react@0.0.2 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + ## 0.2.8 ### Patch Changes diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 7ba72dad0d..44d8583bfa 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-rollbar", - "version": "0.2.8", + "version": "0.2.9", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.0", - "@backstage/core": "^0.5.0", - "@backstage/plugin-catalog-react": "^0.0.1", - "@backstage/theme": "^0.2.2", + "@backstage/catalog-model": "^0.7.1", + "@backstage/core": "^0.6.0", + "@backstage/plugin-catalog-react": "^0.0.2", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -47,8 +47,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 353e2321cf..e8526e0b56 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,103 @@ # @backstage/plugin-scaffolder-backend +## 0.6.0 + +### Minor Changes + +- cdea0baf1: The scaffolder is updated to generate a unique workspace directory inside the temp folder. This directory is cleaned up by the job processor after each run. + + The prepare/template/publish steps have been refactored to operate on known directories, `template/` and `result/`, inside the temporary workspace path. + + Updated preparers to accept the template url instead of the entire template. This is done primarily to allow for backwards compatibility between v1 and v2 scaffolder templates. + + Fixes broken GitHub actions templating in the Create React App template. + + #### For those with **custom** preparers, templates, or publishers + + The preparer interface has changed, the prepare method now only takes a single argument, and doesn't return anything. As part of this change the preparers were refactored to accept a URL pointing to the target directory, rather than computing that from the template entity. + + The `workingDirectory` option was also removed, and replaced with a `workspacePath` option. The difference between the two is that `workingDirectory` was a place for the preparer to create temporary directories, while the `workspacePath` is the specific folder were the entire templating process for a single template job takes place. Instead of returning a path to the folder were the prepared contents were placed, the contents are put at the `/template` path. + + ```diff + type PreparerOptions = { + - workingDirectory?: string; + + /** + + * Full URL to the directory containg template data + + */ + + url: string; + + /** + + * The workspace path that will eventually be the the root of the new repo + + */ + + workspacePath: string; + logger: Logger; + }; + + -prepare(template: TemplateEntityV1alpha1, opts?: PreparerOptions): Promise + +prepare(opts: PreparerOptions): Promise; + ``` + + Instead of returning a path to the folder were the templaters contents were placed, the contents are put at the `/result` path. All templaters now also expect the source template to be present in the `template` directory within the `workspacePath`. + + ```diff + export type TemplaterRunOptions = { + - directory: string; + + workspacePath: string; + values: TemplaterValues; + logStream?: Writable; + dockerClient: Docker; + }; + + -public async run(options: TemplaterRunOptions): Promise + +public async run(options: TemplaterRunOptions): Promise + ``` + + Just like the preparer and templaters, the publishers have also switched to using `workspacePath`. The root of the new repo is expected to be located at `/result`. + + ```diff + export type PublisherOptions = { + values: TemplaterValues; + - directory: string; + + workspacePath: string; + logger: Logger; + }; + ``` + +### Patch Changes + +- a26668913: Attempt to fix windows test errors in master +- 529d16d27: # Repo visibility for GitLab and BitBucket repos + + **NOTE: This changes default repo visibility from `private` to `public` for GitLab and BitBucket** which + is consistent with the GitHub default. If you were counting on `private` visibility, you'll need to update + your scaffolder config to use `private`. + + This adds repo visibility feature parity with GitHub for GitLab and BitBucket. + + To configure the repo visibility, set scaffolder._type_.visibility as in this example: + + ```yaml + scaffolder: + github: + visibility: private # 'public' or 'internal' or 'private' (default is 'public') + gitlab: + visibility: public # 'public' or 'internal' or 'private' (default is 'public') + bitbucket: + visibility: public # 'public' or 'private' (default is 'public') + ``` + +- Updated dependencies [c4abcdb60] +- Updated dependencies [2430ee7c2] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [064c513e1] +- Updated dependencies [7881f2117] +- Updated dependencies [3149bfe63] +- Updated dependencies [2e62aea6f] +- Updated dependencies [11cb5ef94] + - @backstage/integration@0.3.2 + - @backstage/backend-common@0.5.2 + - @backstage/catalog-model@0.7.1 + ## 0.5.2 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 7af9f567e3..95d15c45a8 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "0.5.2", + "version": "0.6.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,10 +29,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.5.1", - "@backstage/catalog-model": "^0.7.0", + "@backstage/backend-common": "^0.5.2", + "@backstage/catalog-model": "^0.7.1", "@backstage/config": "^0.1.2", - "@backstage/integration": "^0.3.1", + "@backstage/integration": "^0.3.2", "@gitbeaker/core": "^28.0.2", "@gitbeaker/node": "^28.0.2", "@octokit/rest": "^18.0.12", @@ -59,7 +59,7 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.5.0", + "@backstage/cli": "^0.6.0", "@backstage/test-utils": "^0.1.5", "@types/fs-extra": "^9.0.1", "@types/mock-fs": "^4.13.0", diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 11a9cfb75c..cd7c8fe685 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-scaffolder +## 0.4.2 + +### Patch Changes + +- 720149854: Migrated to new composability API, exporting the plugin as `scaffolderPlugin`. The template list page (`/create`) is exported as the `TemplateIndexPage` extension, and the templating page itself is exported as `TemplatePage`. +- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`. +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [7fc89bae2] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [54c7d02f7] +- Updated dependencies [11cb5ef94] + - @backstage/core@0.6.0 + - @backstage/plugin-catalog-react@0.0.2 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + ## 0.4.1 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 69021953a2..56beb7b10b 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "0.4.1", + "version": "0.4.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,10 +30,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.0", - "@backstage/core": "^0.5.0", - "@backstage/plugin-catalog-react": "^0.0.1", - "@backstage/theme": "^0.2.2", + "@backstage/catalog-model": "^0.7.1", + "@backstage/core": "^0.6.0", + "@backstage/plugin-catalog-react": "^0.0.2", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -51,8 +51,8 @@ "swr": "^0.3.0" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@backstage/catalog-client": "^0.3.5", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 35590bc2d7..383cb3c484 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-search +## 0.2.7 + +### Patch Changes + +- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`. +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [7fc89bae2] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [54c7d02f7] +- Updated dependencies [11cb5ef94] + - @backstage/core@0.6.0 + - @backstage/plugin-catalog-react@0.0.2 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + ## 0.2.6 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index 28627b88ea..6142e65dd3 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "0.2.6", + "version": "0.2.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,10 +29,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.5.0", - "@backstage/catalog-model": "^0.7.0", - "@backstage/plugin-catalog-react": "^0.0.1", - "@backstage/theme": "^0.2.2", + "@backstage/core": "^0.6.0", + "@backstage/catalog-model": "^0.7.1", + "@backstage/plugin-catalog-react": "^0.0.2", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -43,8 +43,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/sentry/CHANGELOG.md b/plugins/sentry/CHANGELOG.md index 92d88ece8a..698f72020c 100644 --- a/plugins/sentry/CHANGELOG.md +++ b/plugins/sentry/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-sentry +## 0.3.4 + +### Patch Changes + +- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`. +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [7fc89bae2] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [54c7d02f7] +- Updated dependencies [11cb5ef94] + - @backstage/core@0.6.0 + - @backstage/plugin-catalog-react@0.0.2 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + ## 0.3.3 ### Patch Changes diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index 38e1c55ed0..06b538289b 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sentry", - "version": "0.3.3", + "version": "0.3.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.0", - "@backstage/core": "^0.5.0", - "@backstage/plugin-catalog-react": "^0.0.1", - "@backstage/theme": "^0.2.2", + "@backstage/catalog-model": "^0.7.1", + "@backstage/core": "^0.6.0", + "@backstage/plugin-catalog-react": "^0.0.2", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -46,8 +46,8 @@ "timeago.js": "^4.0.2" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/sonarqube/CHANGELOG.md b/plugins/sonarqube/CHANGELOG.md index c3be5e7415..c422f9d72e 100644 --- a/plugins/sonarqube/CHANGELOG.md +++ b/plugins/sonarqube/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-sonarqube +## 0.1.10 + +### Patch Changes + +- 8dfdec613: Migrate to new composability API, exporting the plugin as `sonarQubePlugin` and card as `EntitySonarQubeCard`. +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [7fc89bae2] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [54c7d02f7] +- Updated dependencies [11cb5ef94] + - @backstage/core@0.6.0 + - @backstage/plugin-catalog-react@0.0.2 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + ## 0.1.9 ### Patch Changes diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index c286dd9dac..a31ee320f2 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sonarqube", - "version": "0.1.9", + "version": "0.1.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,10 +32,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.0", - "@backstage/plugin-catalog-react": "^0.0.1", - "@backstage/core": "^0.5.0", - "@backstage/theme": "^0.2.2", + "@backstage/catalog-model": "^0.7.1", + "@backstage/plugin-catalog-react": "^0.0.2", + "@backstage/core": "^0.6.0", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -47,8 +47,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/tech-radar/CHANGELOG.md b/plugins/tech-radar/CHANGELOG.md index 9325827eb9..49a5f8bfcd 100644 --- a/plugins/tech-radar/CHANGELOG.md +++ b/plugins/tech-radar/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-tech-radar +## 0.3.4 + +### Patch Changes + +- 90c8f20b9: Fix mapping RadarEntry and Entry for moved and url attributes + Fix clicking of links in the radar legend +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [54c7d02f7] + - @backstage/core@0.6.0 + - @backstage/theme@0.2.3 + ## 0.3.3 ### Patch Changes diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 9014d52931..a5c9f7bb53 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-radar", - "version": "0.3.3", + "version": "0.3.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,8 +30,8 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.5.0", - "@backstage/theme": "^0.2.2", + "@backstage/core": "^0.6.0", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -43,8 +43,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index fab57f2a00..28ba4e032c 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-techdocs-backend +## 0.5.5 + +### Patch Changes + +- c777df180: 1. Added option to use Azure Blob Storage as a choice to store the static generated files for TechDocs. +- e44925723: `techdocs.requestUrl` and `techdocs.storageUrl` are now optional configs and the discovery API will be used to get the URL where techdocs plugin is hosted. +- Updated dependencies [c777df180] +- Updated dependencies [2430ee7c2] +- Updated dependencies [6e612ce25] +- Updated dependencies [e44925723] +- Updated dependencies [025e122c3] +- Updated dependencies [7881f2117] +- Updated dependencies [f0320190d] +- Updated dependencies [11cb5ef94] + - @backstage/techdocs-common@0.3.7 + - @backstage/backend-common@0.5.2 + - @backstage/catalog-model@0.7.1 + ## 0.5.4 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index e6af442d20..36e5bd3bcd 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "0.5.4", + "version": "0.5.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,10 +30,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.5.1", - "@backstage/catalog-model": "^0.7.0", + "@backstage/backend-common": "^0.5.2", + "@backstage/catalog-model": "^0.7.1", "@backstage/config": "^0.1.2", - "@backstage/techdocs-common": "^0.3.6", + "@backstage/techdocs-common": "^0.3.7", "@types/dockerode": "^3.2.1", "@types/express": "^4.17.6", "cross-fetch": "^3.0.6", @@ -45,7 +45,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.5.0", + "@backstage/cli": "^0.6.0", "supertest": "^4.0.2" }, "files": [ diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 5d2d04aaee..69fe41dd32 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-techdocs +## 0.5.5 + +### Patch Changes + +- 5fa3bdb55: Add `href` in addition to `onClick` to `ItemCard`. Ensure that the height of a + `ItemCard` with and without tags is equal. +- e44925723: `techdocs.requestUrl` and `techdocs.storageUrl` are now optional configs and the discovery API will be used to get the URL where techdocs plugin is hosted. +- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`. +- Updated dependencies [c777df180] +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [7fc89bae2] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [6e612ce25] +- Updated dependencies [e44925723] +- Updated dependencies [025e122c3] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [7881f2117] +- Updated dependencies [f0320190d] +- Updated dependencies [54c7d02f7] +- Updated dependencies [11cb5ef94] + - @backstage/techdocs-common@0.3.7 + - @backstage/core@0.6.0 + - @backstage/plugin-catalog-react@0.0.2 + - @backstage/theme@0.2.3 + - @backstage/catalog-model@0.7.1 + ## 0.5.4 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 20e294c661..ad9e1f7bef 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "0.5.4", + "version": "0.5.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,12 +32,12 @@ }, "dependencies": { "@backstage/config": "^0.1.2", - "@backstage/catalog-model": "^0.7.0", - "@backstage/core": "^0.5.0", - "@backstage/plugin-catalog-react": "^0.0.1", + "@backstage/catalog-model": "^0.7.1", + "@backstage/core": "^0.6.0", + "@backstage/plugin-catalog-react": "^0.0.2", "@backstage/test-utils": "^0.1.6", - "@backstage/theme": "^0.2.2", - "@backstage/techdocs-common": "^0.3.6", + "@backstage/theme": "^0.2.3", + "@backstage/techdocs-common": "^0.3.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -50,8 +50,8 @@ "sanitize-html": "^1.27.0" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index c9abbf2747..467f207754 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-user-settings +## 0.2.5 + +### Patch Changes + +- bc5082a00: Migrate to new composability API, exporting the plugin as `userSettingsPlugin` and the page as `UserSettingsPage`. +- de98c32ed: Keep the Pin Sidebar setting visible on small screens. +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [54c7d02f7] + - @backstage/core@0.6.0 + - @backstage/theme@0.2.3 + ## 0.2.4 ### Patch Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 78790a891e..88453e8efb 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings", - "version": "0.2.4", + "version": "0.2.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,8 +30,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.5.0", - "@backstage/theme": "^0.2.2", + "@backstage/core": "^0.6.0", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -41,8 +41,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/plugins/welcome/CHANGELOG.md b/plugins/welcome/CHANGELOG.md index a1eaf43dc4..594d30226a 100644 --- a/plugins/welcome/CHANGELOG.md +++ b/plugins/welcome/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-welcome +## 0.2.5 + +### Patch Changes + +- 8dfdec613: Migrated to new composability API, exporting the plugin as `welcomePlugin` and the page as `WelcomePage`. +- Updated dependencies [12ece98cd] +- Updated dependencies [d82246867] +- Updated dependencies [c810082ae] +- Updated dependencies [5fa3bdb55] +- Updated dependencies [21e624ba9] +- Updated dependencies [da9f53c60] +- Updated dependencies [32c95605f] +- Updated dependencies [54c7d02f7] + - @backstage/core@0.6.0 + - @backstage/theme@0.2.3 + ## 0.2.4 ### Patch Changes diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index 9482e34743..f3d50dc675 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-welcome", - "version": "0.2.4", + "version": "0.2.5", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -30,8 +30,8 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.5.0", - "@backstage/theme": "^0.2.2", + "@backstage/core": "^0.6.0", + "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -41,8 +41,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.5.0", - "@backstage/dev-utils": "^0.1.8", + "@backstage/cli": "^0.6.0", + "@backstage/dev-utils": "^0.1.9", "@backstage/test-utils": "^0.1.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", From fcf1def9547d9400201f7be4a8afcb714395f0ee Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 4 Feb 2021 14:06:32 +0100 Subject: [PATCH 072/110] create-app: bring back to 0.3.8 --- packages/create-app/CHANGELOG.md | 9 +++------ packages/create-app/package.json | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 7951e90ff6..01432ba150 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,15 +1,12 @@ # @backstage/create-app -## 1.0.0 +## 0.3.8 -### Minor Changes +### Patch Changes - 019fe39a0: `@backstage/plugin-catalog` stopped exporting hooks and helpers for other plugins. They are migrated to `@backstage/plugin-catalog-react`. Change both your dependencies and imports to the new package. - -### Patch Changes - - 436ca3f62: Remove techdocs.requestUrl and techdocs.storageUrl from app-config.yaml - Updated dependencies [ceef4dd89] - Updated dependencies [720149854] @@ -128,7 +125,7 @@ - @backstage/plugin-catalog@0.2.13 - @backstage/plugin-scaffolder-backend@0.5.1 -## 1.0.0 +## 0.3.6 ### Minor Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 009c3b6c61..5f32a5d13d 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "Create app package for Backstage", - "version": "1.0.0", + "version": "0.3.8", "private": false, "publishConfig": { "access": "public" From 4253f23ee29c351126b3c4cd72f4659eb5398593 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 4 Feb 2021 17:06:24 +0100 Subject: [PATCH 073/110] yarn.lock: sync after release --- package.json | 1 + yarn.lock | 32 ++++++++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index fd130fcbbd..ad0ce2cd57 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ }, "resolutions": { "**/@roadiehq/**/@backstage/core": "*", + "**/@roadiehq/**/@backstage/plugin-catalog": "*", "**/@roadiehq/**/@backstage/catalog-model": "*" }, "version": "1.0.0", diff --git a/yarn.lock b/yarn.lock index 482ce1f737..45f8a8b4fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2583,7 +2583,7 @@ to-fast-properties "^2.0.0" "@backstage/catalog-model@^0.2.0": - version "0.7.0" + version "0.7.1" dependencies: "@backstage/config" "^0.1.2" "@types/json-schema" "^7.0.5" @@ -2595,7 +2595,7 @@ yup "^0.29.3" "@backstage/catalog-model@^0.3.0": - version "0.7.0" + version "0.7.1" dependencies: "@backstage/config" "^0.1.2" "@types/json-schema" "^7.0.5" @@ -2607,11 +2607,11 @@ yup "^0.29.3" "@backstage/core@^0.3.0": - version "0.5.0" + version "0.6.0" dependencies: "@backstage/config" "^0.1.2" "@backstage/core-api" "^0.2.8" - "@backstage/theme" "^0.2.2" + "@backstage/theme" "^0.2.3" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" "@material-ui/lab" "4.0.0-alpha.45" @@ -2645,6 +2645,30 @@ remark-gfm "^1.0.0" zen-observable "^0.8.15" +"@backstage/plugin-catalog@^0.2.0", "@backstage/plugin-catalog@^0.2.1": + version "0.3.0" + dependencies: + "@backstage/catalog-client" "^0.3.5" + "@backstage/catalog-model" "^0.7.1" + "@backstage/core" "^0.6.0" + "@backstage/plugin-catalog-react" "^0.0.2" + "@backstage/plugin-scaffolder" "^0.4.2" + "@backstage/theme" "^0.2.3" + "@material-ui/core" "^4.11.0" + "@material-ui/icons" "^4.9.1" + "@material-ui/lab" "4.0.0-alpha.45" + "@types/react" "^16.9" + classnames "^2.2.6" + git-url-parse "^11.4.4" + moment "^2.26.0" + react "^16.13.1" + react-dom "^16.13.1" + react-helmet "6.1.0" + react-router "6.0.0-beta.0" + react-router-dom "6.0.0-beta.0" + react-use "^15.3.3" + swr "^0.3.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" From 0009804a843bd5ad3b150a3bf8d2a6fc0c2407e5 Mon Sep 17 00:00:00 2001 From: "Alex C. Bilimoria" <11621050+acbilimoria@users.noreply.github.com> Date: Thu, 4 Feb 2021 09:58:41 -0700 Subject: [PATCH 074/110] Update docker run command in deployment-other.md --- docs/getting-started/deployment-other.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/deployment-other.md b/docs/getting-started/deployment-other.md index 418a6b3dd3..e92b54d08b 100644 --- a/docs/getting-started/deployment-other.md +++ b/docs/getting-started/deployment-other.md @@ -90,7 +90,7 @@ $ docker build -t example-deployment . To run the image locally you can run: ```sh -$ docker run -p -it 7000:7000 example-deployment +$ docker run -it -p 7000:7000 example-deployment ``` You should then start to get logs in your terminal, and then you can open your From accdfeb30b019ce7ece3b080d1a7779d17650672 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 4 Feb 2021 00:38:45 +0100 Subject: [PATCH 075/110] gitops-profiles: migrate to new composability API --- .changeset/two-dogs-search.md | 5 ++++ plugins/gitops-profiles/dev/index.tsx | 4 +-- plugins/gitops-profiles/src/index.ts | 8 ++++- plugins/gitops-profiles/src/plugin.test.ts | 4 +-- plugins/gitops-profiles/src/plugin.ts | 34 ++++++++++++++++++++-- plugins/gitops-profiles/src/routes.ts | 1 + 6 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 .changeset/two-dogs-search.md diff --git a/.changeset/two-dogs-search.md b/.changeset/two-dogs-search.md new file mode 100644 index 0000000000..5f673c0cfb --- /dev/null +++ b/.changeset/two-dogs-search.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-gitops-profiles': patch +--- + +Migrated to new composability API, exporting the plugin instance as `gitopsProfilesPlugin` and pages as `GitopsProfilesClusterListPage`, `GitopsProfilesClusterPage`, and `GitopsProfilesCreatePage`. diff --git a/plugins/gitops-profiles/dev/index.tsx b/plugins/gitops-profiles/dev/index.tsx index 812a5585d4..c164e4005d 100644 --- a/plugins/gitops-profiles/dev/index.tsx +++ b/plugins/gitops-profiles/dev/index.tsx @@ -15,6 +15,6 @@ */ import { createDevApp } from '@backstage/dev-utils'; -import { plugin } from '../src/plugin'; +import { gitopsProfilesPlugin } from '../src/plugin'; -createDevApp().registerPlugin(plugin).render(); +createDevApp().registerPlugin(gitopsProfilesPlugin).render(); diff --git a/plugins/gitops-profiles/src/index.ts b/plugins/gitops-profiles/src/index.ts index d67bc6a864..876c7c3263 100644 --- a/plugins/gitops-profiles/src/index.ts +++ b/plugins/gitops-profiles/src/index.ts @@ -14,5 +14,11 @@ * limitations under the License. */ -export { plugin } from './plugin'; +export { + gitopsProfilesPlugin, + gitopsProfilesPlugin as plugin, + GitopsProfilesClusterListPage, + GitopsProfilesClusterPage, + GitopsProfilesCreatePage, +} from './plugin'; export * from './api'; diff --git a/plugins/gitops-profiles/src/plugin.test.ts b/plugins/gitops-profiles/src/plugin.test.ts index 19bc49eba7..fa26902f2b 100644 --- a/plugins/gitops-profiles/src/plugin.test.ts +++ b/plugins/gitops-profiles/src/plugin.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { plugin } from './plugin'; +import { gitopsProfilesPlugin } from './plugin'; describe('gitops-profiles', () => { it('should export plugin', () => { - expect(plugin).toBeDefined(); + expect(gitopsProfilesPlugin).toBeDefined(); }); }); diff --git a/plugins/gitops-profiles/src/plugin.ts b/plugins/gitops-profiles/src/plugin.ts index 45820644f7..98a0067969 100644 --- a/plugins/gitops-profiles/src/plugin.ts +++ b/plugins/gitops-profiles/src/plugin.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { createPlugin, createApiFactory } from '@backstage/core'; +import { + createPlugin, + createApiFactory, + createRoutableExtension, +} from '@backstage/core'; import ProfileCatalog from './components/ProfileCatalog'; import ClusterPage from './components/ClusterPage'; import ClusterList from './components/ClusterList'; @@ -25,7 +29,7 @@ import { } from './routes'; import { gitOpsApiRef, GitOpsRestApi } from './api'; -export const plugin = createPlugin({ +export const gitopsProfilesPlugin = createPlugin({ id: 'gitops-profiles', apis: [ createApiFactory(gitOpsApiRef, new GitOpsRestApi('http://localhost:3008')), @@ -35,4 +39,30 @@ export const plugin = createPlugin({ router.addRoute(gitOpsClusterDetailsRoute, ClusterPage); router.addRoute(gitOpsClusterCreateRoute, ProfileCatalog); }, + routes: { + listPage: gitOpsClusterListRoute, + detailsPage: gitOpsClusterDetailsRoute, + createPage: gitOpsClusterCreateRoute, + }, }); + +export const GitopsProfilesClusterListPage = gitopsProfilesPlugin.provide( + createRoutableExtension({ + component: () => import('./components/ClusterList').then(m => m.default), + mountPoint: gitOpsClusterListRoute, + }), +); + +export const GitopsProfilesClusterPage = gitopsProfilesPlugin.provide( + createRoutableExtension({ + component: () => import('./components/ClusterPage').then(m => m.default), + mountPoint: gitOpsClusterDetailsRoute, + }), +); + +export const GitopsProfilesCreatePage = gitopsProfilesPlugin.provide( + createRoutableExtension({ + component: () => import('./components/ProfileCatalog').then(m => m.default), + mountPoint: gitOpsClusterCreateRoute, + }), +); diff --git a/plugins/gitops-profiles/src/routes.ts b/plugins/gitops-profiles/src/routes.ts index f9bdefa806..28be85bd38 100644 --- a/plugins/gitops-profiles/src/routes.ts +++ b/plugins/gitops-profiles/src/routes.ts @@ -28,6 +28,7 @@ export const gitOpsClusterDetailsRoute = createRouteRef({ icon: NoIcon, path: '/gitops-cluster/:owner/:repo', title: 'GitOps Cluster details', + params: ['owner', 'repo'], }); export const gitOpsClusterCreateRoute = createRouteRef({ From b288a291ee393f15f711e84d3e86d8fa8929a81e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 4 Feb 2021 00:52:33 +0100 Subject: [PATCH 076/110] pagerduty: migrate to new composability API --- .changeset/perfect-ladybugs-listen.md | 5 +++++ plugins/pagerduty/dev/index.tsx | 4 ++-- plugins/pagerduty/package.json | 1 + plugins/pagerduty/src/components/PagerDutyCard.tsx | 5 ++++- plugins/pagerduty/src/index.ts | 7 ++++++- plugins/pagerduty/src/plugin.test.ts | 4 ++-- plugins/pagerduty/src/plugin.ts | 12 +++++++++++- 7 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 .changeset/perfect-ladybugs-listen.md diff --git a/.changeset/perfect-ladybugs-listen.md b/.changeset/perfect-ladybugs-listen.md new file mode 100644 index 0000000000..2f9e65c1d9 --- /dev/null +++ b/.changeset/perfect-ladybugs-listen.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-pagerduty': patch +--- + +Migrated to new composability API, exporting the plugin instance as `pagerDutyPlugin`, entity card as `EntityPagerDutyCard`, and entity conditional as `isPagerDutyAvailable`. diff --git a/plugins/pagerduty/dev/index.tsx b/plugins/pagerduty/dev/index.tsx index 264d6f801f..ad46c0ca9e 100644 --- a/plugins/pagerduty/dev/index.tsx +++ b/plugins/pagerduty/dev/index.tsx @@ -14,6 +14,6 @@ * limitations under the License. */ import { createDevApp } from '@backstage/dev-utils'; -import { plugin } from '../src/plugin'; +import { pagerDutyPlugin } from '../src/plugin'; -createDevApp().registerPlugin(plugin).render(); +createDevApp().registerPlugin(pagerDutyPlugin).render(); diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index a5db20e9ab..afd41dcc46 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -31,6 +31,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.1", + "@backstage/plugin-catalog-react": "^0.0.2", "@backstage/core": "^0.6.0", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", diff --git a/plugins/pagerduty/src/components/PagerDutyCard.tsx b/plugins/pagerduty/src/components/PagerDutyCard.tsx index 5fb2cd7f4a..f7360c01d6 100644 --- a/plugins/pagerduty/src/components/PagerDutyCard.tsx +++ b/plugins/pagerduty/src/components/PagerDutyCard.tsx @@ -16,6 +16,7 @@ import React, { useState, useCallback } from 'react'; import { useApi, Progress, HeaderIconLinkRow } from '@backstage/core'; import { Entity } from '@backstage/catalog-model'; +import { useEntity } from '@backstage/plugin-catalog-react'; import { Button, makeStyles, @@ -56,11 +57,13 @@ export const isPluginApplicableToEntity = (entity: Entity) => Boolean(entity.metadata.annotations?.[PAGERDUTY_INTEGRATION_KEY]); type Props = { + /** @deprecated The entity is now grabbed from context instead */ entity: Entity; }; -export const PagerDutyCard = ({ entity }: Props) => { +export const PagerDutyCard = (_props: Props) => { const classes = useStyles(); + const { entity } = useEntity(); const api = useApi(pagerDutyApiRef); const [showDialog, setShowDialog] = useState(false); const [refreshIncidents, setRefreshIncidents] = useState(false); diff --git a/plugins/pagerduty/src/index.ts b/plugins/pagerduty/src/index.ts index 4ecd4edcc6..2dfedab164 100644 --- a/plugins/pagerduty/src/index.ts +++ b/plugins/pagerduty/src/index.ts @@ -13,9 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { plugin } from './plugin'; +export { + pagerDutyPlugin, + pagerDutyPlugin as plugin, + EntityPagerDutyCard, +} from './plugin'; export { isPluginApplicableToEntity, + isPluginApplicableToEntity as isPagerDutyAvailable, PagerDutyCard, } from './components/PagerDutyCard'; export { diff --git a/plugins/pagerduty/src/plugin.test.ts b/plugins/pagerduty/src/plugin.test.ts index 8d4545ac12..c1175ab384 100644 --- a/plugins/pagerduty/src/plugin.test.ts +++ b/plugins/pagerduty/src/plugin.test.ts @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { plugin } from './plugin'; +import { pagerDutyPlugin } from './plugin'; describe('pagerduty', () => { it('should export plugin', () => { - expect(plugin).toBeDefined(); + expect(pagerDutyPlugin).toBeDefined(); }); }); diff --git a/plugins/pagerduty/src/plugin.ts b/plugins/pagerduty/src/plugin.ts index 34796f491e..fbe827b90d 100644 --- a/plugins/pagerduty/src/plugin.ts +++ b/plugins/pagerduty/src/plugin.ts @@ -19,6 +19,7 @@ import { createRouteRef, discoveryApiRef, configApiRef, + createComponentExtension, } from '@backstage/core'; import { pagerDutyApiRef, PagerDutyClient } from './api'; @@ -27,7 +28,7 @@ export const rootRouteRef = createRouteRef({ title: 'pagerduty', }); -export const plugin = createPlugin({ +export const pagerDutyPlugin = createPlugin({ id: 'pagerduty', apis: [ createApiFactory({ @@ -38,3 +39,12 @@ export const plugin = createPlugin({ }), ], }); + +export const EntityPagerDutyCard = pagerDutyPlugin.provide( + createComponentExtension({ + component: { + lazy: () => + import('./components/PagerDutyCard').then(m => m.PagerDutyCard), + }, + }), +); From b3f0c38112be1afddc30fe4260ce3ed7dc160d18 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 4 Feb 2021 12:02:28 +0100 Subject: [PATCH 077/110] search: migrate to new composability API --- .changeset/cyan-lies-flow.md | 5 +++++ plugins/search/dev/index.tsx | 4 ++-- plugins/search/src/index.ts | 12 ++++++++++-- plugins/search/src/plugin.test.ts | 4 ++-- plugins/search/src/plugin.ts | 22 ++++++++++++++++++---- 5 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 .changeset/cyan-lies-flow.md diff --git a/.changeset/cyan-lies-flow.md b/.changeset/cyan-lies-flow.md new file mode 100644 index 0000000000..e7da9fab0a --- /dev/null +++ b/.changeset/cyan-lies-flow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search': minor +--- + +Migrated to new composability API, exporting the plugin instance as `searchPlugin`, and page as `SearchPage`. Due to the old router component also being called `SearchPage`, this is a breaking change. The old page component is now exported as `Router`, which can be used to maintain the old behavior. diff --git a/plugins/search/dev/index.tsx b/plugins/search/dev/index.tsx index 264d6f801f..e6e97ead6d 100644 --- a/plugins/search/dev/index.tsx +++ b/plugins/search/dev/index.tsx @@ -14,6 +14,6 @@ * limitations under the License. */ import { createDevApp } from '@backstage/dev-utils'; -import { plugin } from '../src/plugin'; +import { searchPlugin } from '../src/plugin'; -createDevApp().registerPlugin(plugin).render(); +createDevApp().registerPlugin(searchPlugin).render(); diff --git a/plugins/search/src/index.ts b/plugins/search/src/index.ts index 77ad7f9266..572d75bea5 100644 --- a/plugins/search/src/index.ts +++ b/plugins/search/src/index.ts @@ -13,5 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { plugin } from './plugin'; -export * from './components'; +export { searchPlugin, searchPlugin as plugin, SearchPage } from './plugin'; +export { + Filters, + FiltersButton, + SearchBar, + SearchPage as Router, + SearchResult, + SidebarSearch, +} from './components'; +export type { FiltersState } from './components'; diff --git a/plugins/search/src/plugin.test.ts b/plugins/search/src/plugin.test.ts index 92b8d5bcbf..902faeaf9e 100644 --- a/plugins/search/src/plugin.test.ts +++ b/plugins/search/src/plugin.test.ts @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { plugin } from './plugin'; +import { searchPlugin } from './plugin'; describe('search', () => { it('should export plugin', () => { - expect(plugin).toBeDefined(); + expect(searchPlugin).toBeDefined(); }); }); diff --git a/plugins/search/src/plugin.ts b/plugins/search/src/plugin.ts index 44c7bcb042..37503b7751 100644 --- a/plugins/search/src/plugin.ts +++ b/plugins/search/src/plugin.ts @@ -13,17 +13,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createPlugin, createRouteRef } from '@backstage/core'; -import { SearchPage } from './components/SearchPage'; +import { + createPlugin, + createRouteRef, + createRoutableExtension, +} from '@backstage/core'; +import { SearchPage as SearchPageComponent } from './components/SearchPage'; export const rootRouteRef = createRouteRef({ path: '/search', title: 'search', }); -export const plugin = createPlugin({ +export const searchPlugin = createPlugin({ id: 'search', register({ router }) { - router.addRoute(rootRouteRef, SearchPage); + router.addRoute(rootRouteRef, SearchPageComponent); + }, + routes: { + root: rootRouteRef, }, }); + +export const SearchPage = searchPlugin.provide( + createRoutableExtension({ + component: () => import('./components/SearchPage').then(m => m.SearchPage), + mountPoint: rootRouteRef, + }), +); From 9ec66c3459d170d4bdd698e6f8b1d29b56ad9df0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 4 Feb 2021 19:57:44 +0100 Subject: [PATCH 078/110] register-component: migrated to new composability API --- .changeset/funny-students-shout.md | 5 ++++ plugins/register-component/dev/index.tsx | 4 +-- plugins/register-component/src/index.ts | 6 ++++- plugins/register-component/src/plugin.test.ts | 4 +-- plugins/register-component/src/plugin.ts | 27 ++++++++++++++++--- 5 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 .changeset/funny-students-shout.md diff --git a/.changeset/funny-students-shout.md b/.changeset/funny-students-shout.md new file mode 100644 index 0000000000..97b273c687 --- /dev/null +++ b/.changeset/funny-students-shout.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-register-component': patch +--- + +Migrated to new composability API, exporting the plugin instance as `registerComponentPlugin`, and page as `RegisterComponentPage`. diff --git a/plugins/register-component/dev/index.tsx b/plugins/register-component/dev/index.tsx index 812a5585d4..3304d0874f 100644 --- a/plugins/register-component/dev/index.tsx +++ b/plugins/register-component/dev/index.tsx @@ -15,6 +15,6 @@ */ import { createDevApp } from '@backstage/dev-utils'; -import { plugin } from '../src/plugin'; +import { registerComponentPlugin } from '../src/plugin'; -createDevApp().registerPlugin(plugin).render(); +createDevApp().registerPlugin(registerComponentPlugin).render(); diff --git a/plugins/register-component/src/index.ts b/plugins/register-component/src/index.ts index ff7857cacd..56bcd06fde 100644 --- a/plugins/register-component/src/index.ts +++ b/plugins/register-component/src/index.ts @@ -14,5 +14,9 @@ * limitations under the License. */ -export { plugin } from './plugin'; +export { + registerComponentPlugin, + registerComponentPlugin as plugin, + RegisterComponentPage, +} from './plugin'; export { Router } from './components/Router'; diff --git a/plugins/register-component/src/plugin.test.ts b/plugins/register-component/src/plugin.test.ts index 1e61060202..2b6ad48bcf 100644 --- a/plugins/register-component/src/plugin.test.ts +++ b/plugins/register-component/src/plugin.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { plugin } from './plugin'; +import { registerComponentPlugin } from './plugin'; describe('register-component', () => { it('should export plugin', () => { - expect(plugin).toBeDefined(); + expect(registerComponentPlugin).toBeDefined(); }); }); diff --git a/plugins/register-component/src/plugin.ts b/plugins/register-component/src/plugin.ts index 17f99917c8..d3727158e5 100644 --- a/plugins/register-component/src/plugin.ts +++ b/plugins/register-component/src/plugin.ts @@ -14,8 +14,29 @@ * limitations under the License. */ -import { createPlugin } from '@backstage/core'; +import { + createPlugin, + createRoutableExtension, + createRouteRef, +} from '@backstage/core'; -export const plugin = createPlugin({ - id: 'register-component', +const rootRouteRef = createRouteRef({ + title: 'Register Component', }); + +export const registerComponentPlugin = createPlugin({ + id: 'register-component', + routes: { + root: rootRouteRef, + }, +}); + +export const RegisterComponentPage = registerComponentPlugin.provide( + createRoutableExtension({ + component: () => + import('./components/RegisterComponentPage').then( + m => m.RegisterComponentPage, + ), + mountPoint: rootRouteRef, + }), +); From 41af18227fd4893a24f39cb8512556bee2e63e58 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 4 Feb 2021 20:07:12 +0100 Subject: [PATCH 079/110] techdocs: migrate to new composability API --- .changeset/ninety-houses-shout.md | 5 +++++ plugins/techdocs/dev/index.tsx | 4 ++-- plugins/techdocs/src/Router.tsx | 10 +++++++++- plugins/techdocs/src/index.ts | 7 ++++++- plugins/techdocs/src/plugin.test.ts | 4 ++-- plugins/techdocs/src/plugin.ts | 21 ++++++++++++++++++++- 6 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 .changeset/ninety-houses-shout.md diff --git a/.changeset/ninety-houses-shout.md b/.changeset/ninety-houses-shout.md new file mode 100644 index 0000000000..845decc74c --- /dev/null +++ b/.changeset/ninety-houses-shout.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Migrated to new composability API, exporting the plugin instance as `techdocsPlugin`, the top-level page as `TechdocsPage`, and the entity content as `EntityTechdocsContent`. diff --git a/plugins/techdocs/dev/index.tsx b/plugins/techdocs/dev/index.tsx index 42fa9a9ec4..4cf74c9622 100644 --- a/plugins/techdocs/dev/index.tsx +++ b/plugins/techdocs/dev/index.tsx @@ -16,7 +16,7 @@ import { configApiRef, discoveryApiRef } from '@backstage/core'; import { createDevApp } from '@backstage/dev-utils'; -import { plugin } from '../src/plugin'; +import { techdocsPlugin } from '../src/plugin'; import { TechDocsDevStorageApi } from './api'; import { techdocsStorageApiRef } from '../src'; @@ -30,5 +30,5 @@ createDevApp() discoveryApi, }), }) - .registerPlugin(plugin) + .registerPlugin(techdocsPlugin) .render(); diff --git a/plugins/techdocs/src/Router.tsx b/plugins/techdocs/src/Router.tsx index 262f542ee6..b220912174 100644 --- a/plugins/techdocs/src/Router.tsx +++ b/plugins/techdocs/src/Router.tsx @@ -16,6 +16,7 @@ import React from 'react'; import { Entity } from '@backstage/catalog-model'; +import { useEntity } from '@backstage/plugin-catalog-react'; import { Route, Routes } from 'react-router-dom'; import { MissingAnnotationEmptyState } from '@backstage/core'; import { @@ -38,7 +39,14 @@ export const Router = () => { ); }; -export const EmbeddedDocsRouter = ({ entity }: { entity: Entity }) => { +type Props = { + /** @deprecated The entity is now grabbed from context instead */ + entity?: Entity; +}; + +export const EmbeddedDocsRouter = (_props: Props) => { + const { entity } = useEntity(); + const projectId = entity.metadata.annotations?.[TECHDOCS_ANNOTATION]; if (!projectId) { diff --git a/plugins/techdocs/src/index.ts b/plugins/techdocs/src/index.ts index 1726e0daf3..30c143e31f 100644 --- a/plugins/techdocs/src/index.ts +++ b/plugins/techdocs/src/index.ts @@ -14,7 +14,12 @@ * limitations under the License. */ -export { plugin } from './plugin'; +export { + techdocsPlugin, + techdocsPlugin as plugin, + TechdocsPage, + EntityTechdocsContent, +} from './plugin'; export { Router, EmbeddedDocsRouter } from './Router'; export * from './reader'; export * from './api'; diff --git a/plugins/techdocs/src/plugin.test.ts b/plugins/techdocs/src/plugin.test.ts index e750be9ac3..52f072627c 100644 --- a/plugins/techdocs/src/plugin.test.ts +++ b/plugins/techdocs/src/plugin.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { plugin } from './plugin'; +import { techdocsPlugin } from './plugin'; describe('techdocs', () => { it('should export plugin', () => { - expect(plugin).toBeDefined(); + expect(techdocsPlugin).toBeDefined(); }); }); diff --git a/plugins/techdocs/src/plugin.ts b/plugins/techdocs/src/plugin.ts index 982b749f2c..0b4063c237 100644 --- a/plugins/techdocs/src/plugin.ts +++ b/plugins/techdocs/src/plugin.ts @@ -35,6 +35,7 @@ import { createApiFactory, configApiRef, discoveryApiRef, + createRoutableExtension, } from '@backstage/core'; import { techdocsStorageApiRef, @@ -58,7 +59,7 @@ export const rootCatalogDocsRouteRef = createRouteRef({ title: 'Docs', }); -export const plugin = createPlugin({ +export const techdocsPlugin = createPlugin({ id: 'techdocs', apis: [ createApiFactory({ @@ -80,4 +81,22 @@ export const plugin = createPlugin({ }), }), ], + routes: { + root: rootRouteRef, + entityContent: rootCatalogDocsRouteRef, + }, }); + +export const TechdocsPage = techdocsPlugin.provide( + createRoutableExtension({ + component: () => import('./Router').then(m => m.Router), + mountPoint: rootRouteRef, + }), +); + +export const EntityTechdocsContent = techdocsPlugin.provide( + createRoutableExtension({ + component: () => import('./Router').then(m => m.EmbeddedDocsRouter), + mountPoint: rootCatalogDocsRouteRef, + }), +); From 7c789117881a4654822383ebfcdea88066b1a9bd Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 4 Feb 2021 20:36:18 +0100 Subject: [PATCH 080/110] pagerduty: fix entity card tests --- .../src/components/PagerDutyCard.test.tsx | 17 +++++++++++++---- .../pagerduty/src/components/PagerDutyCard.tsx | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/plugins/pagerduty/src/components/PagerDutyCard.test.tsx b/plugins/pagerduty/src/components/PagerDutyCard.test.tsx index a6615aee82..a719a27f86 100644 --- a/plugins/pagerduty/src/components/PagerDutyCard.test.tsx +++ b/plugins/pagerduty/src/components/PagerDutyCard.test.tsx @@ -17,6 +17,7 @@ import React from 'react'; import { render, waitFor, fireEvent, act } from '@testing-library/react'; import { PagerDutyCard } from './PagerDutyCard'; import { Entity } from '@backstage/catalog-model'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; import { wrapInTestApp } from '@backstage/test-utils'; import { alertApiRef, @@ -80,7 +81,9 @@ describe('PageDutyCard', () => { const { getByText, queryByTestId } = render( wrapInTestApp( - + + + , ), ); @@ -99,7 +102,9 @@ describe('PageDutyCard', () => { const { getByText, queryByTestId } = render( wrapInTestApp( - + + + , ), ); @@ -114,7 +119,9 @@ describe('PageDutyCard', () => { const { getByText, queryByTestId } = render( wrapInTestApp( - + + + , ), ); @@ -134,7 +141,9 @@ describe('PageDutyCard', () => { const { getByText, queryByTestId, getByTestId, getByRole } = render( wrapInTestApp( - + + + , ), ); diff --git a/plugins/pagerduty/src/components/PagerDutyCard.tsx b/plugins/pagerduty/src/components/PagerDutyCard.tsx index f7360c01d6..e03fb0fdbe 100644 --- a/plugins/pagerduty/src/components/PagerDutyCard.tsx +++ b/plugins/pagerduty/src/components/PagerDutyCard.tsx @@ -58,7 +58,7 @@ export const isPluginApplicableToEntity = (entity: Entity) => type Props = { /** @deprecated The entity is now grabbed from context instead */ - entity: Entity; + entity?: Entity; }; export const PagerDutyCard = (_props: Props) => { From f9dacb504e1357bee73a05f5f2a810705c685534 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Feb 2021 04:38:07 +0000 Subject: [PATCH 081/110] chore(deps-dev): bump husky from 4.3.6 to 4.3.8 Bumps [husky](https://github.com/typicode/husky) from 4.3.6 to 4.3.8. - [Release notes](https://github.com/typicode/husky/releases) - [Commits](https://github.com/typicode/husky/compare/v4.3.6...v4.3.8) Signed-off-by: dependabot[bot] --- yarn.lock | 61 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/yarn.lock b/yarn.lock index 45f8a8b4fb..d5e90fa45a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2645,7 +2645,31 @@ remark-gfm "^1.0.0" zen-observable "^0.8.15" -"@backstage/plugin-catalog@^0.2.0", "@backstage/plugin-catalog@^0.2.1": +"@backstage/plugin-catalog@^0.2.0": + version "0.3.0" + dependencies: + "@backstage/catalog-client" "^0.3.5" + "@backstage/catalog-model" "^0.7.1" + "@backstage/core" "^0.6.0" + "@backstage/plugin-catalog-react" "^0.0.2" + "@backstage/plugin-scaffolder" "^0.4.2" + "@backstage/theme" "^0.2.3" + "@material-ui/core" "^4.11.0" + "@material-ui/icons" "^4.9.1" + "@material-ui/lab" "4.0.0-alpha.45" + "@types/react" "^16.9" + classnames "^2.2.6" + git-url-parse "^11.4.4" + moment "^2.26.0" + react "^16.13.1" + react-dom "^16.13.1" + react-helmet "6.1.0" + react-router "6.0.0-beta.0" + react-router-dom "6.0.0-beta.0" + react-use "^15.3.3" + swr "^0.3.0" + +"@backstage/plugin-catalog@^0.2.1": version "0.3.0" dependencies: "@backstage/catalog-client" "^0.3.5" @@ -13795,12 +13819,12 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -find-versions@^3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz#10297f98030a786829681690545ef659ed1d254e" - integrity sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww== +find-versions@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz#3c57e573bf97769b8cb8df16934b627915da4965" + integrity sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ== dependencies: - semver-regex "^2.0.0" + semver-regex "^3.1.2" find-yarn-workspace-root2@1.2.16: version "1.2.16" @@ -15406,17 +15430,17 @@ humanize-ms@^1.2.1: ms "^2.0.0" husky@^4.2.3: - version "4.3.6" - resolved "https://registry.npmjs.org/husky/-/husky-4.3.6.tgz#ebd9dd8b9324aa851f1587318db4cccb7665a13c" - integrity sha512-o6UjVI8xtlWRL5395iWq9LKDyp/9TE7XMOTvIpEVzW638UcGxTmV5cfel6fsk/jbZSTlvfGVJf2svFtybcIZag== + version "4.3.8" + resolved "https://registry.npmjs.org/husky/-/husky-4.3.8.tgz#31144060be963fd6850e5cc8f019a1dfe194296d" + integrity sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow== dependencies: chalk "^4.0.0" ci-info "^2.0.0" compare-versions "^3.6.0" cosmiconfig "^7.0.0" - find-versions "^3.2.0" + find-versions "^4.0.0" opencollective-postinstall "^2.0.2" - pkg-dir "^4.2.0" + pkg-dir "^5.0.0" please-upgrade-node "^3.2.0" slash "^3.0.0" which-pm-runs "^1.0.0" @@ -20738,6 +20762,13 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +pkg-dir@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz#a02d6aebe6ba133a928f74aec20bafdfe6b8e760" + integrity sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA== + dependencies: + find-up "^5.0.0" + pkg-up@3.1.0, pkg-up@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" @@ -23359,10 +23390,10 @@ semver-diff@^3.1.1: dependencies: semver "^6.3.0" -semver-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz#a93c2c5844539a770233379107b38c7b4ac9d338" - integrity sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw== +semver-regex@^3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.2.tgz#34b4c0d361eef262e07199dbef316d0f2ab11807" + integrity sha512-bXWyL6EAKOJa81XG1OZ/Yyuq+oT0b2YLlxx7c+mrdYPaPbnj6WgVULXhinMIeZGufuUBu/eVRqXEhiv4imfwxA== "semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" From ee65c94555e786f83209355d66d451a85ec30709 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 5 Feb 2021 12:25:38 +0100 Subject: [PATCH 082/110] backend-common: ignore rate limiting errors in UrlReader integration tests --- packages/backend-common/src/reading/integration.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/backend-common/src/reading/integration.test.ts b/packages/backend-common/src/reading/integration.test.ts index 18f87636a5..16fdf3ff58 100644 --- a/packages/backend-common/src/reading/integration.test.ts +++ b/packages/backend-common/src/reading/integration.test.ts @@ -71,7 +71,11 @@ function withRetries(count: number, fn: () => Promise) { error = err; } } - throw error; + if (!error.message.match(/rate limit|Too Many Requests/)) { + throw error; + } else { + console.warn('Request was rate limited', error); + } }; } From ba3a6a9be0128d1b952263187b0d5d3a59e0e12a Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 5 Feb 2021 13:52:20 +0100 Subject: [PATCH 083/110] Scaffolder: fix typos and log output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw --- .../src/scaffolder/tasks/TemplateConverter.ts | 4 ++-- plugins/scaffolder-backend/src/service/router.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts index 86f722e475..69788238cc 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TemplateConverter.ts @@ -99,7 +99,7 @@ export class TemplateActionRegistry { register(action: TemplateAction) { if (this.actions.has(action.id)) { throw new ConflictError( - `Template action with id ${action.id} as already been registered`, + `Template action with ID '${action.id}' has already been registered`, ); } this.actions.set(action.id, action); @@ -109,7 +109,7 @@ export class TemplateActionRegistry { const action = this.actions.get(actionId); if (!action) { throw new NotFoundError( - `Template action with id ${actionId} is not registered.`, + `Template action with ID '${actionId}' is not registered.`, ); } return action; diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index a8a61972af..5821edfe8c 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -258,7 +258,7 @@ export async function createRouter( 'Content-Type': 'text/event-stream', }); - // After client opens connection send all nests as string + // After client opens connection send all events as string const unsubscribe = taskBroker.observe( { taskId, after }, (error, { events }) => { From 0e640893dfeb944a84c4c7ec993e1af5f3696253 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 5 Feb 2021 13:56:53 +0100 Subject: [PATCH 084/110] test-utils: add mountedRoutes option to wrapInTestApp --- .../src/testUtils/appWrappers.test.tsx | 27 +++++++++++++++ .../test-utils/src/testUtils/appWrappers.tsx | 34 +++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/packages/test-utils/src/testUtils/appWrappers.test.tsx b/packages/test-utils/src/testUtils/appWrappers.test.tsx index 29f8d97793..4899f07061 100644 --- a/packages/test-utils/src/testUtils/appWrappers.test.tsx +++ b/packages/test-utils/src/testUtils/appWrappers.test.tsx @@ -21,9 +21,11 @@ import { Route, Routes } from 'react-router'; import { withLogCollector } from '@backstage/test-utils-core'; import { useApi, + useRouteRef, errorApiRef, ApiProvider, ApiRegistry, + createRouteRef, } from '@backstage/core-api'; import { MockErrorApi } from './apis'; @@ -113,4 +115,29 @@ describe('wrapInTestApp', () => { expect(rendered.getByText('foo')).toBeInTheDocument(); expect(mockErrorApi.getErrors()).toEqual([{ error: new Error('NOPE') }]); }); + + it('should allow route refs to be mounted on specific paths', async () => { + const aRouteRef = createRouteRef({ title: 'A' }); + const bRouteRef = createRouteRef({ title: 'B', params: ['name'] }); + + const MyComponent = () => { + const a = useRouteRef(aRouteRef); + const b = useRouteRef(bRouteRef); + return ( +
+
Link A: {a()}
+
Link B: {b({ name: 'x' })}
+
+ ); + }; + + const rendered = await renderInTestApp(, { + mountedRoutes: { + '/my-a-path': aRouteRef, + '/my-b-path/:name': bRouteRef, + }, + }); + expect(rendered.getByText('Link A: /my-a-path')).toBeInTheDocument(); + expect(rendered.getByText('Link B: /my-b-path/x')).toBeInTheDocument(); + }); }); diff --git a/packages/test-utils/src/testUtils/appWrappers.tsx b/packages/test-utils/src/testUtils/appWrappers.tsx index 1f6c7622b8..cc67a5b707 100644 --- a/packages/test-utils/src/testUtils/appWrappers.tsx +++ b/packages/test-utils/src/testUtils/appWrappers.tsx @@ -21,6 +21,9 @@ import { lightTheme } from '@backstage/theme'; import privateExports, { defaultSystemIcons, BootErrorPageProps, + RouteRef, + createPlugin, + createRoutableExtension, } from '@backstage/core-api'; import { RenderResult } from '@testing-library/react'; import { renderWithEffects } from '@backstage/test-utils-core'; @@ -44,6 +47,22 @@ type TestAppOptions = { * Initial route entries to pass along as `initialEntries` to the router. */ routeEntries?: string[]; + + /** + * An object of paths to mount route ref on, with the key being the path and the value + * being the RouteRef that the path will be bound to. This allows the route refs to be + * used by `useRouteRef` in the rendered elements. + * + * @example + * wrapInTestApp(, { + * mountedRoutes: { + * '/my-path': myRouteRef, + * } + * }) + * // ... + * const link = useRouteRef(myRouteRef) + */ + mountedRoutes?: { [path: string]: RouteRef }; }; /** @@ -90,12 +109,27 @@ export function wrapInTestApp( Wrapper = () => Component as React.ReactElement; } + const routePlugin = createPlugin({ id: 'mock-route-plugin' }); + const routeElements = Object.entries(options.mountedRoutes ?? {}).map( + ([path, routeRef]) => { + const PageComponent = () =>
Mounted at {path}
; + const Page = routePlugin.provide( + createRoutableExtension({ + component: async () => PageComponent, + mountPoint: routeRef, + }), + ); + return } />; + }, + ); + const AppProvider = app.getProvider(); const AppRouter = app.getRouter(); return ( + {routeElements} {/* The path of * here is needed to be set as a catch all, so it will render the wrapper element * and work with nested routes if they exist too */} } /> From 95cd312d55659c34b3b4f5ea39903b8db001fafb Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 5 Feb 2021 14:10:41 +0100 Subject: [PATCH 085/110] scaffolder: Make updateCount errors ConflictError --- .../src/scaffolder/tasks/DatabaseTaskStore.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index 22697f5fdb..373a4486f1 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -141,7 +141,7 @@ export class DatabaseTaskStore implements TaskStore { last_heartbeat_at: this.db.fn.now(), }); if (updateCount === 0) { - throw new Error(`No running task with taskId ${taskId} found`); + throw new ConflictError(`No running task with taskId ${taskId} found`); } } @@ -210,7 +210,7 @@ export class DatabaseTaskStore implements TaskStore { status, }); if (updateCount !== 1) { - throw new Error( + throw new ConflictError( `Failed to update status to '${status}' for taskId ${taskId}`, ); } From b3b766b79b2efd7f9e0201627c5e7fe10ffb1daa Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 5 Feb 2021 14:34:00 +0100 Subject: [PATCH 086/110] Scaffolder: Remove sleep --- plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index f87d2df934..c62495ca4c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -101,9 +101,6 @@ export class TaskWorker { task.emitLog(`Finished step ${step.name}`); } - logger.info('So done right now'); - await new Promise(resolve => setTimeout(resolve, 5000)); - await task.complete('completed'); } catch (error) { task.emitLog(String(error.stack)); From 9afa2d24c63bad5e9b1f789c06bb5a6ae3f5bcfc Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 5 Feb 2021 15:25:34 +0100 Subject: [PATCH 087/110] scaffolder: Fix legacy publish error message --- plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts b/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts index 6daf1ba4f6..f151780ba9 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/legacy.ts @@ -87,10 +87,9 @@ export function registerLegacyActions( } const owner = values.owner as unknown; if (typeof owner !== 'string') { - throw new Error( - `Invalid store path passed to publish, got ${typeof owner}`, - ); + throw new Error(`Invalid owner passed to publish, got ${typeof owner}`); } + const publisher = publishers.get(storePath); ctx.logger.info('Will now store the template'); const { remoteUrl, catalogInfoUrl } = await publisher.publish({ From e84170d6443d907806d86c2966aa17eaf2110b62 Mon Sep 17 00:00:00 2001 From: Gowind Date: Fri, 5 Feb 2021 15:37:44 +0100 Subject: [PATCH 088/110] use child logger to log single location update --- .../src/ingestion/HigherOrderOperations.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts b/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts index ba97232810..467c0eb2e8 100644 --- a/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts +++ b/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts @@ -130,7 +130,7 @@ export class HigherOrderOperations implements HigherOrderOperation { `Locations Refresh: Refreshing location ${location.type}:${location.target}`, ); try { - await this.refreshSingleLocation(location); + await this.refreshSingleLocation(location, logger); await this.locationsCatalog.logUpdateSuccess(location.id, undefined); } catch (e) { logger.warn( @@ -148,8 +148,12 @@ export class HigherOrderOperations implements HigherOrderOperation { } // Performs a full refresh of a single location - private async refreshSingleLocation(location: Location) { + private async refreshSingleLocation( + location: Location, + optionalLogger?: Logger, + ) { let startTimestamp = process.hrtime(); + const logger = optionalLogger || this.logger; const readerOutput = await this.locationReader.read({ type: location.type, @@ -157,12 +161,12 @@ export class HigherOrderOperations implements HigherOrderOperation { }); for (const item of readerOutput.errors) { - this.logger.warn( + logger.warn( `Failed item in location ${item.location.type}:${item.location.target}, ${item.error.stack}`, ); } - this.logger.info( + logger.info( `Read ${readerOutput.entities.length} entities from location ${ location.type }:${location.target} in ${durationText(startTimestamp)}`, @@ -186,14 +190,14 @@ export class HigherOrderOperations implements HigherOrderOperation { throw e; } - this.logger.debug(`Posting update success markers`); + logger.debug(`Posting update success markers`); await this.locationsCatalog.logUpdateSuccess( location.id, readerOutput.entities.map(e => e.entity.metadata.name), ); - this.logger.info( + logger.info( `Wrote ${readerOutput.entities.length} entities from location ${ location.type }:${location.target} in ${durationText(startTimestamp)}`, From 8f3443427287abf6408d686739bc2fc84043fe27 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Fri, 5 Feb 2021 10:49:12 -0500 Subject: [PATCH 089/110] Rename green-rabbits-burn.md to techdocs-green-rabbits-burn.md --- .../{green-rabbits-burn.md => techdocs-green-rabbits-burn.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .changeset/{green-rabbits-burn.md => techdocs-green-rabbits-burn.md} (100%) diff --git a/.changeset/green-rabbits-burn.md b/.changeset/techdocs-green-rabbits-burn.md similarity index 100% rename from .changeset/green-rabbits-burn.md rename to .changeset/techdocs-green-rabbits-burn.md From fd933c4239e4099034f6c0bafd6a124f5d78f85b Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Fri, 5 Feb 2021 10:50:11 -0500 Subject: [PATCH 090/110] Add comment TODO --- plugins/techdocs/src/reader/components/Reader.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index c9e76ddeeb..f1937ae8a8 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -155,6 +155,8 @@ export const Reader = ({ entityId, onReady }: Props) => { ]); if (error) { + // TODO Enhance API call to return customize error objects so we can identify which we ran into + // For now this defaults to display error code 404 return ; } From 2ab3b3f0e3614de2faed69f23387b2969de4d510 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 5 Feb 2021 17:49:41 +0100 Subject: [PATCH 091/110] core-api: fix type inference of createRouteRef --- packages/core-api/src/routing/RouteRef.ts | 23 ++++++++++++++++---- packages/core-api/src/routing/hooks.test.tsx | 3 ++- packages/core-api/src/routing/index.ts | 2 +- packages/core-api/src/routing/types.ts | 8 ------- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/packages/core-api/src/routing/RouteRef.ts b/packages/core-api/src/routing/RouteRef.ts index 4c2da34098..3f48d70827 100644 --- a/packages/core-api/src/routing/RouteRef.ts +++ b/packages/core-api/src/routing/RouteRef.ts @@ -14,7 +14,16 @@ * limitations under the License. */ -import { RouteRefConfig, RouteRef } from './types'; +import { RouteRef } from './types'; +import { IconComponent } from '../icons'; + +export type RouteRefConfig = { + params?: Array; + /** @deprecated Route refs no longer decide their own path */ + path?: string; + icon?: IconComponent; + title: string; +}; export class AbsoluteRouteRef { constructor(private readonly config: RouteRefConfig) {} @@ -38,9 +47,15 @@ export class AbsoluteRouteRef { } export function createRouteRef< - ParamKeys extends string, - Params extends { [param in string]: string } = { [name in ParamKeys]: string } ->(config: RouteRefConfig): RouteRef { + Params extends { [param in ParamKey]: string }, + ParamKey extends string = never +>(config: { + params?: ParamKey[]; + /** @deprecated Route refs no longer decide their own path */ + path?: string; + icon?: IconComponent; + title: string; +}): RouteRef { return new AbsoluteRouteRef(config); } diff --git a/packages/core-api/src/routing/hooks.test.tsx b/packages/core-api/src/routing/hooks.test.tsx index 3dbeaf8e3e..84a8314807 100644 --- a/packages/core-api/src/routing/hooks.test.tsx +++ b/packages/core-api/src/routing/hooks.test.tsx @@ -39,8 +39,9 @@ import { createRouteRef, createExternalRouteRef, ExternalRouteRef, + RouteRefConfig, } from './RouteRef'; -import { RouteRef, RouteRefConfig } from './types'; +import { RouteRef } from './types'; const mockConfig = (extra?: Partial>) => ({ path: '/unused', diff --git a/packages/core-api/src/routing/index.ts b/packages/core-api/src/routing/index.ts index 9564b3b225..af71e0c3e9 100644 --- a/packages/core-api/src/routing/index.ts +++ b/packages/core-api/src/routing/index.ts @@ -16,11 +16,11 @@ export type { RouteRef, - RouteRefConfig, AbsoluteRouteRef, ConcreteRoute, MutableRouteRef, } from './types'; export { FlatRoutes } from './FlatRoutes'; export { createRouteRef } from './RouteRef'; +export type { RouteRefConfig } from './RouteRef'; export { useRouteRef } from './hooks'; diff --git a/packages/core-api/src/routing/types.ts b/packages/core-api/src/routing/types.ts index 99a20c31e9..5f9376c1d2 100644 --- a/packages/core-api/src/routing/types.ts +++ b/packages/core-api/src/routing/types.ts @@ -45,14 +45,6 @@ export type AbsoluteRouteRef = RouteRef<{}>; */ export type MutableRouteRef = RouteRef<{}>; -export type RouteRefConfig = { - params?: Array; - /** @deprecated Route refs no longer decide their own path */ - path?: string; - icon?: IconComponent; - title: string; -}; - // A duplicate of the react-router RouteObject, but with routeRef added export interface BackstageRouteObject { caseSensitive: boolean; From b51ee6ece987552331c8673ba7452ab4bcd95575 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 5 Feb 2021 17:56:01 +0100 Subject: [PATCH 092/110] added changesets --- .changeset/poor-sheep-give.md | 5 +++++ .changeset/tricky-dancers-rush.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/poor-sheep-give.md create mode 100644 .changeset/tricky-dancers-rush.md diff --git a/.changeset/poor-sheep-give.md b/.changeset/poor-sheep-give.md new file mode 100644 index 0000000000..4c52594b35 --- /dev/null +++ b/.changeset/poor-sheep-give.md @@ -0,0 +1,5 @@ +--- +'@backstage/test-utils': patch +--- + +Added `mountedRoutes` option to `wrapInTestApp`, allowing routes to be associated to concrete paths to make `useRouteRef` usable in tested components. diff --git a/.changeset/tricky-dancers-rush.md b/.changeset/tricky-dancers-rush.md new file mode 100644 index 0000000000..301b23d98e --- /dev/null +++ b/.changeset/tricky-dancers-rush.md @@ -0,0 +1,5 @@ +--- +'@backstage/core': patch +--- + +Fixed type inference of `createRouteRef`. From fb53eb7cb4057a33ae76f09fa1f009334adf1f51 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Fri, 5 Feb 2021 19:41:39 +0100 Subject: [PATCH 093/110] Don't respond to a request twice if an entity has not been found. --- .changeset/fluffy-hats-hug.md | 5 +++++ plugins/catalog-backend/src/service/router.ts | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 .changeset/fluffy-hats-hug.md diff --git a/.changeset/fluffy-hats-hug.md b/.changeset/fluffy-hats-hug.md new file mode 100644 index 0000000000..b3d06337fd --- /dev/null +++ b/.changeset/fluffy-hats-hug.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Don't respond to a request twice if an entity has not been found. diff --git a/plugins/catalog-backend/src/service/router.ts b/plugins/catalog-backend/src/service/router.ts index 43c2967233..6064164e31 100644 --- a/plugins/catalog-backend/src/service/router.ts +++ b/plugins/catalog-backend/src/service/router.ts @@ -76,8 +76,9 @@ export async function createRouter( ); if (!entities.length) { res.status(404).send(`No entity with uid ${uid}`); + } else { + res.status(200).send(entities[0]); } - res.status(200).send(entities[0]); }) .delete('/entities/by-uid/:uid', async (req, res) => { const { uid } = req.params; @@ -99,8 +100,9 @@ export async function createRouter( .send( `No entity with kind ${kind} namespace ${namespace} name ${name}`, ); + } else { + res.status(200).send(entities[0]); } - res.status(200).send(entities[0]); }); } From 7cdd586e579643f1213096323d11c8d4e4fe7465 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 6 Feb 2021 01:19:20 +0100 Subject: [PATCH 094/110] create-app: update changelog to put more emphasis on plugin-catalog-react migration --- packages/create-app/CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 01432ba150..5e8ff0e9b1 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -4,9 +4,9 @@ ### Patch Changes -- 019fe39a0: `@backstage/plugin-catalog` stopped exporting hooks and helpers for other - plugins. They are migrated to `@backstage/plugin-catalog-react`. - Change both your dependencies and imports to the new package. +- 019fe39a0: **BREAKING CHANGE**: The `useEntity` hooks has been moved from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`. + To apply this change to an existing app, add `@backstage/plugin-catalog-react` to your dependencies in `packages/app/package.json`, and update + the import inside `packages/app/src/components/catalog/EntityPage.tsx` as well as any other places you were using `useEntity` or any other functions that were moved to `@backstage/plugin-catalog-react`. - 436ca3f62: Remove techdocs.requestUrl and techdocs.storageUrl from app-config.yaml - Updated dependencies [ceef4dd89] - Updated dependencies [720149854] From bee68316675ad51de20a750a980a2cc488a80bf0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 6 Feb 2021 10:13:24 +0100 Subject: [PATCH 095/110] Update packages/create-app/CHANGELOG.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw --- packages/create-app/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 5e8ff0e9b1..13b854972a 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -4,7 +4,7 @@ ### Patch Changes -- 019fe39a0: **BREAKING CHANGE**: The `useEntity` hooks has been moved from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`. +- 019fe39a0: **BREAKING CHANGE**: The `useEntity` hook has been moved from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`. To apply this change to an existing app, add `@backstage/plugin-catalog-react` to your dependencies in `packages/app/package.json`, and update the import inside `packages/app/src/components/catalog/EntityPage.tsx` as well as any other places you were using `useEntity` or any other functions that were moved to `@backstage/plugin-catalog-react`. - 436ca3f62: Remove techdocs.requestUrl and techdocs.storageUrl from app-config.yaml From 43397061fdcc861d0b537c51d87171be569d5376 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Sat, 6 Feb 2021 17:26:44 +0100 Subject: [PATCH 096/110] docs(TechDocs): docs/ is configurable by mkdocs.yml --- docs/features/techdocs/creating-and-publishing.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/features/techdocs/creating-and-publishing.md b/docs/features/techdocs/creating-and-publishing.md index c15dc1fe69..b9340330a8 100644 --- a/docs/features/techdocs/creating-and-publishing.md +++ b/docs/features/techdocs/creating-and-publishing.md @@ -80,6 +80,10 @@ Create a `/docs` folder in the root of the project with at least an `index.md` file. _(If you add more markdown files, make sure to update the nav in the mkdocs.yml file to get a proper navigation for your documentation.)_ +> Note - Although `docs` is a popular directory name for storing documentation, +> it can be renamed to something else and can be configured by `mkdocs.yml`. See +> https://www.mkdocs.org/user-guide/configuration/#docs_dir + The `docs/index.md` can for example have the following content: ```md From 1585732a7bf9e67a0506c378d08c7698706f2c2d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 7 Feb 2021 20:10:54 +0100 Subject: [PATCH 097/110] core-api: document createRouteRef type inference logic --- packages/core-api/src/routing/RouteRef.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/core-api/src/routing/RouteRef.ts b/packages/core-api/src/routing/RouteRef.ts index 3f48d70827..0a3df49b58 100644 --- a/packages/core-api/src/routing/RouteRef.ts +++ b/packages/core-api/src/routing/RouteRef.ts @@ -47,7 +47,12 @@ export class AbsoluteRouteRef { } export function createRouteRef< + // Params is the type that we care about and the one to be embedded in the route ref. + // For example, given the params ['name', 'kind'], Params will be {name: string, kind: string} Params extends { [param in ParamKey]: string }, + // ParamKey is here to make sure the Params type properly has its keys narrowed down + // to only the elements of params. Defaulting to never makes sure we end up with + // Param = {} if the params array is empty. ParamKey extends string = never >(config: { params?: ParamKey[]; From 3eb1b1baccfc0328b796808dacbf9ea6e1223732 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 7 Feb 2021 23:40:44 +0100 Subject: [PATCH 098/110] github-actions: GitHub -> Github --- .changeset/selfish-years-pump.md | 2 +- plugins/github-actions/src/index.ts | 10 +++++----- plugins/github-actions/src/plugin.ts | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.changeset/selfish-years-pump.md b/.changeset/selfish-years-pump.md index 48994a1d9e..e5cc5f3bc3 100644 --- a/.changeset/selfish-years-pump.md +++ b/.changeset/selfish-years-pump.md @@ -2,4 +2,4 @@ '@backstage/plugin-github-actions': patch --- -Migrate to new composability API, exporting the plugin instance as `githubActionsPlugin`, the entity content as `EntityGitHubActionsContent`, entity conditional as `isGitHubActionsAvailable`, and entity cards as `EntityLatestGitHubActionRunCard`, `EntityLatestGitHubActionsForBranchCard`, and `EntityRecentGitHubActionsRunsCard`. +Migrate to new composability API, exporting the plugin instance as `githubActionsPlugin`, the entity content as `EntityGithubActionsContent`, entity conditional as `isGithubActionsAvailable`, and entity cards as `EntityLatestGithubActionRunCard`, `EntityLatestGithubActionsForBranchCard`, and `EntityRecentGithubActionsRunsCard`. diff --git a/plugins/github-actions/src/index.ts b/plugins/github-actions/src/index.ts index 6dd3b9724e..6dd4807b75 100644 --- a/plugins/github-actions/src/index.ts +++ b/plugins/github-actions/src/index.ts @@ -17,16 +17,16 @@ export { githubActionsPlugin, githubActionsPlugin as plugin, - EntityGitHubActionsContent, - EntityLatestGitHubActionRunCard, - EntityLatestGitHubActionsForBranchCard, - EntityRecentGitHubActionsRunsCard, + EntityGithubActionsContent, + EntityLatestGithubActionRunCard, + EntityLatestGithubActionsForBranchCard, + EntityRecentGithubActionsRunsCard, } from './plugin'; export * from './api'; export { Router, isPluginApplicableToEntity, - isPluginApplicableToEntity as isGitHubActionsAvailable, + isPluginApplicableToEntity as isGithubActionsAvailable, } from './components/Router'; export * from './components/Cards'; export { GITHUB_ACTIONS_ANNOTATION } from './components/useProjectName'; diff --git a/plugins/github-actions/src/plugin.ts b/plugins/github-actions/src/plugin.ts index 71c05745d4..d296b4ad30 100644 --- a/plugins/github-actions/src/plugin.ts +++ b/plugins/github-actions/src/plugin.ts @@ -51,14 +51,14 @@ export const githubActionsPlugin = createPlugin({ }, }); -export const EntityGitHubActionsContent = githubActionsPlugin.provide( +export const EntityGithubActionsContent = githubActionsPlugin.provide( createRoutableExtension({ component: () => import('./components/Router').then(m => m.Router), mountPoint: rootRouteRef, }), ); -export const EntityLatestGitHubActionRunCard = githubActionsPlugin.provide( +export const EntityLatestGithubActionRunCard = githubActionsPlugin.provide( createComponentExtension({ component: { lazy: () => @@ -67,7 +67,7 @@ export const EntityLatestGitHubActionRunCard = githubActionsPlugin.provide( }), ); -export const EntityLatestGitHubActionsForBranchCard = githubActionsPlugin.provide( +export const EntityLatestGithubActionsForBranchCard = githubActionsPlugin.provide( createComponentExtension({ component: { lazy: () => @@ -76,7 +76,7 @@ export const EntityLatestGitHubActionsForBranchCard = githubActionsPlugin.provid }), ); -export const EntityRecentGitHubActionsRunsCard = githubActionsPlugin.provide( +export const EntityRecentGithubActionsRunsCard = githubActionsPlugin.provide( createComponentExtension({ component: { lazy: () => From 8e1789e0c312f93ed2fec60fa31f2c961216fec4 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 7 Feb 2021 23:48:31 +0100 Subject: [PATCH 099/110] circleci,cloudbuild,github-actions,jenkins: rename internal entity conditional --- plugins/circleci/src/components/Router.tsx | 4 ++-- plugins/circleci/src/index.ts | 4 ++-- plugins/cloudbuild/src/components/Router.tsx | 4 ++-- plugins/cloudbuild/src/index.ts | 4 ++-- plugins/github-actions/src/components/Router.tsx | 4 ++-- plugins/github-actions/src/index.ts | 4 ++-- plugins/jenkins/src/components/Router.tsx | 4 ++-- plugins/jenkins/src/index.ts | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/plugins/circleci/src/components/Router.tsx b/plugins/circleci/src/components/Router.tsx index b3a0d2c3e2..d18b6c1e61 100644 --- a/plugins/circleci/src/components/Router.tsx +++ b/plugins/circleci/src/components/Router.tsx @@ -24,7 +24,7 @@ import { Entity } from '@backstage/catalog-model'; import { useEntity } from '@backstage/plugin-catalog-react'; import { MissingAnnotationEmptyState } from '@backstage/core'; -export const isPluginApplicableToEntity = (entity: Entity) => +export const isCircleCIAvailable = (entity: Entity) => Boolean(entity.metadata.annotations?.[CIRCLECI_ANNOTATION]); type Props = { @@ -35,7 +35,7 @@ type Props = { export const Router = (_props: Props) => { const { entity } = useEntity(); - if (!isPluginApplicableToEntity(entity)) { + if (!isCircleCIAvailable(entity)) { return ; } diff --git a/plugins/circleci/src/index.ts b/plugins/circleci/src/index.ts index 1e8b213279..2a37cbdee0 100644 --- a/plugins/circleci/src/index.ts +++ b/plugins/circleci/src/index.ts @@ -23,7 +23,7 @@ export * from './api'; export * from './route-refs'; export { Router, - isPluginApplicableToEntity, - isPluginApplicableToEntity as isCircleCIAvailable, + isCircleCIAvailable, + isCircleCIAvailable as isPluginApplicableToEntity, } from './components/Router'; export { CIRCLECI_ANNOTATION } from './constants'; diff --git a/plugins/cloudbuild/src/components/Router.tsx b/plugins/cloudbuild/src/components/Router.tsx index c31947a531..0f71efcf86 100644 --- a/plugins/cloudbuild/src/components/Router.tsx +++ b/plugins/cloudbuild/src/components/Router.tsx @@ -23,7 +23,7 @@ import { WorkflowRunsTable } from './WorkflowRunsTable'; import { CLOUDBUILD_ANNOTATION } from './useProjectName'; import { MissingAnnotationEmptyState } from '@backstage/core'; -export const isPluginApplicableToEntity = (entity: Entity) => +export const isCloudbuildAvailable = (entity: Entity) => Boolean(entity.metadata.annotations?.[CLOUDBUILD_ANNOTATION]); type Props = { @@ -34,7 +34,7 @@ type Props = { export const Router = (_props: Props) => { const { entity } = useEntity(); - if (!isPluginApplicableToEntity(entity)) { + if (!isCloudbuildAvailable(entity)) { // TODO(shmidt-i): move warning to a separate standardized component return ; } diff --git a/plugins/cloudbuild/src/index.ts b/plugins/cloudbuild/src/index.ts index a846792fa2..b7cd6c29d5 100644 --- a/plugins/cloudbuild/src/index.ts +++ b/plugins/cloudbuild/src/index.ts @@ -23,8 +23,8 @@ export { export * from './api'; export { Router, - isPluginApplicableToEntity, - isPluginApplicableToEntity as isCloudbuildAvailable, + isCloudbuildAvailable, + isCloudbuildAvailable as isPluginApplicableToEntity, } from './components/Router'; export * from './components/Cards'; export { CLOUDBUILD_ANNOTATION } from './components/useProjectName'; diff --git a/plugins/github-actions/src/components/Router.tsx b/plugins/github-actions/src/components/Router.tsx index e867b05b18..834fcae43c 100644 --- a/plugins/github-actions/src/components/Router.tsx +++ b/plugins/github-actions/src/components/Router.tsx @@ -23,7 +23,7 @@ import { WorkflowRunsTable } from './WorkflowRunsTable'; import { GITHUB_ACTIONS_ANNOTATION } from './useProjectName'; import { MissingAnnotationEmptyState } from '@backstage/core'; -export const isPluginApplicableToEntity = (entity: Entity) => +export const isGithubActionsAvailable = (entity: Entity) => Boolean(entity.metadata.annotations?.[GITHUB_ACTIONS_ANNOTATION]); type Props = { @@ -34,7 +34,7 @@ type Props = { export const Router = (_props: Props) => { const { entity } = useEntity(); - if (!isPluginApplicableToEntity(entity)) { + if (!isGithubActionsAvailable(entity)) { return ( ); diff --git a/plugins/github-actions/src/index.ts b/plugins/github-actions/src/index.ts index 6dd4807b75..b4179a9d92 100644 --- a/plugins/github-actions/src/index.ts +++ b/plugins/github-actions/src/index.ts @@ -25,8 +25,8 @@ export { export * from './api'; export { Router, - isPluginApplicableToEntity, - isPluginApplicableToEntity as isGithubActionsAvailable, + isGithubActionsAvailable, + isGithubActionsAvailable as isPluginApplicableToEntity, } from './components/Router'; export * from './components/Cards'; export { GITHUB_ACTIONS_ANNOTATION } from './components/useProjectName'; diff --git a/plugins/jenkins/src/components/Router.tsx b/plugins/jenkins/src/components/Router.tsx index e310cd4193..85f06bb0e1 100644 --- a/plugins/jenkins/src/components/Router.tsx +++ b/plugins/jenkins/src/components/Router.tsx @@ -23,7 +23,7 @@ import { Entity } from '@backstage/catalog-model'; import { MissingAnnotationEmptyState } from '@backstage/core'; import { CITable } from './BuildsPage/lib/CITable'; -export const isPluginApplicableToEntity = (entity: Entity) => +export const isJenkinsAvailable = (entity: Entity) => Boolean(entity.metadata.annotations?.[JENKINS_ANNOTATION]); type Props = { @@ -34,7 +34,7 @@ type Props = { export const Router = (_props: Props) => { const { entity } = useEntity(); - if (!isPluginApplicableToEntity(entity)) { + if (!isJenkinsAvailable(entity)) { return ; } diff --git a/plugins/jenkins/src/index.ts b/plugins/jenkins/src/index.ts index 6d0e7837d3..e434e959e8 100644 --- a/plugins/jenkins/src/index.ts +++ b/plugins/jenkins/src/index.ts @@ -23,8 +23,8 @@ export { export { LatestRunCard } from './components/Cards'; export { Router, - isPluginApplicableToEntity, - isPluginApplicableToEntity as isJenkinsAvailable, + isJenkinsAvailable, + isJenkinsAvailable as isPluginApplicableToEntity, } from './components/Router'; export { JENKINS_ANNOTATION } from './constants'; export * from './api'; From 8761cdb3ff8c62a15bca0f30acb1bf594ef6f097 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Feb 2021 04:56:58 +0000 Subject: [PATCH 100/110] chore(deps): bump rollup-plugin-typescript2 from 0.27.3 to 0.29.0 Bumps [rollup-plugin-typescript2](https://github.com/ezolenko/rollup-plugin-typescript2) from 0.27.3 to 0.29.0. - [Release notes](https://github.com/ezolenko/rollup-plugin-typescript2/releases) - [Commits](https://github.com/ezolenko/rollup-plugin-typescript2/compare/0.27.3...0.29.0) Signed-off-by: dependabot[bot] --- packages/cli/package.json | 2 +- yarn.lock | 34 +++++++++++++++++++++++++++++----- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index fb33a154a7..9aaeb00459 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -93,7 +93,7 @@ "rollup-plugin-esbuild": "2.6.x", "rollup-plugin-peer-deps-external": "^2.2.2", "rollup-plugin-postcss": "^3.1.1", - "rollup-plugin-typescript2": "^0.27.3", + "rollup-plugin-typescript2": "^0.29.0", "rollup-pluginutils": "^2.8.2", "semver": "^7.3.2", "start-server-webpack-plugin": "^2.2.5", diff --git a/yarn.lock b/yarn.lock index 45f8a8b4fb..c8858afb59 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2645,7 +2645,31 @@ remark-gfm "^1.0.0" zen-observable "^0.8.15" -"@backstage/plugin-catalog@^0.2.0", "@backstage/plugin-catalog@^0.2.1": +"@backstage/plugin-catalog@^0.2.0": + version "0.3.0" + dependencies: + "@backstage/catalog-client" "^0.3.5" + "@backstage/catalog-model" "^0.7.1" + "@backstage/core" "^0.6.0" + "@backstage/plugin-catalog-react" "^0.0.2" + "@backstage/plugin-scaffolder" "^0.4.2" + "@backstage/theme" "^0.2.3" + "@material-ui/core" "^4.11.0" + "@material-ui/icons" "^4.9.1" + "@material-ui/lab" "4.0.0-alpha.45" + "@types/react" "^16.9" + classnames "^2.2.6" + git-url-parse "^11.4.4" + moment "^2.26.0" + react "^16.13.1" + react-dom "^16.13.1" + react-helmet "6.1.0" + react-router "6.0.0-beta.0" + react-router-dom "6.0.0-beta.0" + react-use "^15.3.3" + swr "^0.3.0" + +"@backstage/plugin-catalog@^0.2.1": version "0.3.0" dependencies: "@backstage/catalog-client" "^0.3.5" @@ -23147,10 +23171,10 @@ rollup-plugin-postcss@^3.1.1: safe-identifier "^0.4.1" style-inject "^0.3.0" -rollup-plugin-typescript2@^0.27.3: - version "0.27.3" - resolved "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.27.3.tgz#cd9455ac026d325b20c5728d2cc54a08a771b68b" - integrity sha512-gmYPIFmALj9D3Ga1ZbTZAKTXq1JKlTQBtj299DXhqYz9cL3g/AQfUvbb2UhH+Nf++cCq941W2Mv7UcrcgLzJJg== +rollup-plugin-typescript2@^0.29.0: + version "0.29.0" + resolved "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.29.0.tgz#b7ad83f5241dbc5bdf1e98d9c3fca005ffe39e1a" + integrity sha512-YytahBSZCIjn/elFugEGQR5qTsVhxhUwGZIsA9TmrSsC88qroGo65O5HZP/TTArH2dm0vUmYWhKchhwi2wL9bw== dependencies: "@rollup/pluginutils" "^3.1.0" find-cache-dir "^3.3.1" From 44da5d09c581e54f94ad989b9a411e3c35219de5 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 8 Feb 2021 09:24:48 +0100 Subject: [PATCH 101/110] scaffolder: Convert to prepared statements --- .../src/scaffolder/tasks/DatabaseTaskStore.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index 373a4486f1..5717f3c8c1 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -158,8 +158,11 @@ export class DatabaseTaskStore implements TaskStore { 'last_heartbeat_at', '<=', this.db.client.config.client === 'sqlite3' - ? this.db.raw(`datetime('now', '-${Number(timeoutS)} seconds')`) - : this.db.raw(`dateadd('second', -${Number(timeoutS)}, now())`), + ? this.db.raw(`datetime('now', ?)`, [`-${timeoutS} seconds`]) + : this.db.raw(`dateadd('second', ?, ?)`, [ + `-${timeoutS}`, + this.db.fn.now(), + ]), ); const tasks = rawRows.map(row => ({ taskId: row.id, From 965e200c61670758d6358b4ed58c3687f6f022c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 4 Feb 2021 14:19:17 +0100 Subject: [PATCH 102/110] backend-common: slight refactoring in support of future search impl --- .changeset/old-tools-exist.md | 5 + packages/backend-common/package.json | 1 + .../src/reading/GithubUrlReader.ts | 128 ++++++++++-------- .../src/reading/tree/TarArchiveResponse.ts | 12 +- .../src/reading/tree/ZipArchiveResponse.ts | 8 +- packages/backend-common/src/reading/types.ts | 72 +++++----- .../src/github/GithubCredentialsProvider.ts | 2 +- 7 files changed, 124 insertions(+), 104 deletions(-) create mode 100644 .changeset/old-tools-exist.md diff --git a/.changeset/old-tools-exist.md b/.changeset/old-tools-exist.md new file mode 100644 index 0000000000..a9365cb784 --- /dev/null +++ b/.changeset/old-tools-exist.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Slight refactoring in support of a future search implementation in `UrlReader`. Mostly moving code around. diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 3053784dc3..faec36dab4 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -33,6 +33,7 @@ "@backstage/config": "^0.1.2", "@backstage/config-loader": "^0.5.1", "@backstage/integration": "^0.3.2", + "@octokit/rest": "^18.0.12", "@types/cors": "^2.8.6", "@types/express": "^4.17.6", "archiver": "^5.0.2", diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index 4f1aa7c07b..99d641f2dd 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -15,11 +15,12 @@ */ import { - GitHubIntegrationConfig, - readGitHubIntegrationConfigs, getGitHubFileFetchUrl, GithubCredentialsProvider, + GitHubIntegrationConfig, + readGitHubIntegrationConfigs, } from '@backstage/integration'; +import { RestEndpointMethodTypes } from '@octokit/rest'; import fetch from 'cross-fetch'; import parseGitUrl from 'git-url-parse'; import { Readable } from 'stream'; @@ -98,74 +99,26 @@ export class GithubUrlReader implements UrlReader { url: string, options?: ReadTreeOptions, ): Promise { - const { ref, filepath, full_name } = parseGitUrl(url); - // Caveat: The ref will totally be incorrect if the branch name includes a / - // Thus, readTree can not work on url containing branch name that has a / - - const { headers } = await this.deps.credentialsProvider.getCredentials({ - url, - }); - - // Get GitHub API urls for the repository - const repoGitHubResponse = await fetch( - new URL(`${this.config.apiBaseUrl}/repos/${full_name}`).toString(), - { - headers, - }, - ); - if (!repoGitHubResponse.ok) { - const message = `Failed to read tree (repository) from ${url}, ${repoGitHubResponse.status} ${repoGitHubResponse.statusText}`; - if (repoGitHubResponse.status === 404) { - throw new NotFoundError(message); - } - throw new Error(message); - } - - const repoResponseJson = await repoGitHubResponse.json(); - - // ref is an empty string if no branch is set in provided url to readTree. - // Use GitHub API to get the default branch of the repository. - const branch = ref || repoResponseJson.default_branch; - const branchesApiUrl = repoResponseJson.branches_url; - const archiveApiUrl = repoResponseJson.archive_url; - - // Fetch the latest commit in the provided or default branch to compare against - // the provided sha. - const branchGitHubResponse = await fetch( - // branchesApiUrl looks like "https://api.github.com/repos/owner/repo/branches{/branch}" - branchesApiUrl.replace('{/branch}', `/${branch}`), - { - headers, - }, - ); - if (!branchGitHubResponse.ok) { - const message = `Failed to read tree (branch) from ${url}, ${branchGitHubResponse.status} ${branchGitHubResponse.statusText}`; - if (branchGitHubResponse.status === 404) { - throw new NotFoundError(message); - } - throw new Error(message); - } - const commitSha = (await branchGitHubResponse.json()).commit.sha; + const repoDetails = await this.getRepoDetails(url); + const commitSha = repoDetails.branch.commit.sha!; if (options?.etag && options.etag === commitSha) { throw new NotModifiedError(); } - const archive = await fetch( - // archiveApiUrl looks like "https://api.github.com/repos/owner/repo/{archive_format}{/ref}" - archiveApiUrl + const { headers } = await this.deps.credentialsProvider.getCredentials({ + url, + }); + + // archive_url looks like "https://api.github.com/repos/owner/repo/{archive_format}{/ref}" + const archive = await this.fetchResponse( + repoDetails.repo.archive_url .replace('{archive_format}', 'tarball') .replace('{/ref}', `/${commitSha}`), { headers }, ); - if (!archive.ok) { - const message = `Failed to read tree (archive) from ${url}, ${archive.status} ${archive.statusText}`; - if (archive.status === 404) { - throw new NotFoundError(message); - } - throw new Error(message); - } + const { filepath } = parseGitUrl(url); return await this.deps.treeResponseFactory.fromTarArchive({ // TODO(Rugvip): Underlying implementation of fetch will be node-fetch, we probably want // to stick to using that in exclusively backend code. @@ -180,4 +133,59 @@ export class GithubUrlReader implements UrlReader { const { host, token } = this.config; return `github{host=${host},authed=${Boolean(token)}}`; } + + private async getRepoDetails( + url: string, + ): Promise<{ + repo: RestEndpointMethodTypes['repos']['get']['response']['data']; + branch: RestEndpointMethodTypes['repos']['getBranch']['response']['data']; + }> { + const parsed = parseGitUrl(url); + const { ref, full_name } = parsed; + + // Caveat: The ref will totally be incorrect if the branch name includes a + // slash. Thus, some operations can not work on URLs containing branch + // names that have a slash in them. + + const { headers } = await this.deps.credentialsProvider.getCredentials({ + url, + }); + + const repo: RestEndpointMethodTypes['repos']['get']['response']['data'] = await this.fetchJson( + `${this.config.apiBaseUrl}/repos/${full_name}`, + { headers }, + ); + + // branches_url looks like "https://api.github.com/repos/owner/repo/branches{/branch}" + const branch: RestEndpointMethodTypes['repos']['getBranch']['response']['data'] = await this.fetchJson( + repo.branches_url.replace('{/branch}', `/${ref || repo.default_branch}`), + { headers }, + ); + + return { repo, branch }; + } + + private async fetchResponse( + url: string | URL, + init: RequestInit, + ): Promise { + const urlAsString = url.toString(); + + const response = await fetch(urlAsString, init); + + if (!response.ok) { + const message = `Request failed for ${urlAsString}, ${response.status} ${response.statusText}`; + if (response.status === 404) { + throw new NotFoundError(message); + } + throw new Error(message); + } + + return response; + } + + private async fetchJson(url: string | URL, init: RequestInit): Promise { + const response = await this.fetchResponse(url, init); + return await response.json(); + } } diff --git a/packages/backend-common/src/reading/tree/TarArchiveResponse.ts b/packages/backend-common/src/reading/tree/TarArchiveResponse.ts index 24bfa3f1a8..d22529293f 100644 --- a/packages/backend-common/src/reading/tree/TarArchiveResponse.ts +++ b/packages/backend-common/src/reading/tree/TarArchiveResponse.ts @@ -14,16 +14,16 @@ * limitations under the License. */ -import tar, { Parse, ParseStream, ReadEntry } from 'tar'; -import platformPath from 'path'; -import fs from 'fs-extra'; -import { Readable, pipeline as pipelineCb } from 'stream'; -import { promisify } from 'util'; import concatStream from 'concat-stream'; +import fs from 'fs-extra'; +import platformPath from 'path'; +import { pipeline as pipelineCb, Readable } from 'stream'; +import tar, { Parse, ParseStream, ReadEntry } from 'tar'; +import { promisify } from 'util'; import { ReadTreeResponse, - ReadTreeResponseFile, ReadTreeResponseDirOptions, + ReadTreeResponseFile, } from '../types'; // Tar types for `Parse` is not a proper constructor, but it should be diff --git a/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts b/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts index 323f7b4778..2547c7b0e4 100644 --- a/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts +++ b/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts @@ -14,15 +14,15 @@ * limitations under the License. */ -import platformPath from 'path'; -import fs from 'fs-extra'; -import unzipper, { Entry } from 'unzipper'; import archiver from 'archiver'; +import fs from 'fs-extra'; +import platformPath from 'path'; import { Readable } from 'stream'; +import unzipper, { Entry } from 'unzipper'; import { ReadTreeResponse, - ReadTreeResponseFile, ReadTreeResponseDirOptions, + ReadTreeResponseFile, } from '../types'; // Matches a directory name + one `/` at the start of any string, diff --git a/packages/backend-common/src/reading/types.ts b/packages/backend-common/src/reading/types.ts index f9dfbe9aff..be788fc6e2 100644 --- a/packages/backend-common/src/reading/types.ts +++ b/packages/backend-common/src/reading/types.ts @@ -18,6 +18,32 @@ import { Logger } from 'winston'; import { Config } from '@backstage/config'; import { ReadTreeResponseFactory } from './tree'; +/** + * A generic interface for fetching plain data from URLs. + */ +export type UrlReader = { + read(url: string): Promise; + readTree(url: string, options?: ReadTreeOptions): Promise; +}; + +export type UrlReaderPredicateTuple = { + predicate: (url: URL) => boolean; + reader: UrlReader; +}; + +/** + * A factory function that can read config to construct zero or more + * UrlReaders along with a predicate for when it should be used. + */ +export type ReaderFactory = (options: { + config: Config; + logger: Logger; + treeResponseFactory: ReadTreeResponseFactory; +}) => UrlReaderPredicateTuple[]; + +/** + * An options object for readTree operations. + */ export type ReadTreeOptions = { /** * A filter that can be used to select which files should be included. @@ -47,39 +73,6 @@ export type ReadTreeOptions = { etag?: string; }; -/** - * A generic interface for fetching plain data from URLs. - */ -export type UrlReader = { - read(url: string): Promise; - readTree(url: string, options?: ReadTreeOptions): Promise; -}; - -export type UrlReaderPredicateTuple = { - predicate: (url: URL) => boolean; - reader: UrlReader; -}; - -/** - * A factory function that can read config to construct zero or more - * UrlReaders along with a predicate for when it should be used. - */ -export type ReaderFactory = (options: { - config: Config; - logger: Logger; - treeResponseFactory: ReadTreeResponseFactory; -}) => UrlReaderPredicateTuple[]; - -export type ReadTreeResponseFile = { - path: string; - content(): Promise; -}; - -export type ReadTreeResponseDirOptions = { - /** The directory to write files to. Defaults to the OS tmpdir or `backend.workingDirectory` if set in config */ - targetDir?: string; -}; - export type ReadTreeResponse = { /** * files() returns an array of all the files inside the tree and corresponding functions to read their content. @@ -97,3 +90,16 @@ export type ReadTreeResponse = { */ etag: string; }; + +export type ReadTreeResponseDirOptions = { + /** The directory to write files to. Defaults to the OS tmpdir or `backend.workingDirectory` if set in config */ + targetDir?: string; +}; + +/** + * Represents a single file in a readTree response. + */ +export type ReadTreeResponseFile = { + path: string; + content(): Promise; +}; diff --git a/packages/integration/src/github/GithubCredentialsProvider.ts b/packages/integration/src/github/GithubCredentialsProvider.ts index a22c69909b..0bf217c824 100644 --- a/packages/integration/src/github/GithubCredentialsProvider.ts +++ b/packages/integration/src/github/GithubCredentialsProvider.ts @@ -53,7 +53,7 @@ class Cache { /** * This accept header is required when calling App APIs in GitHub Enterprise. - * It has no effect on calls to github.com and can probably be removed entierly + * It has no effect on calls to github.com and can probably be removed entirely * once GitHub Apps is out of preview. */ const HEADERS = { From 82af0855bc2d9fe8ae71d2d84af9dd467f51507a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 8 Feb 2021 12:21:47 +0100 Subject: [PATCH 103/110] backend-common: use explicit content type in error response --- packages/backend-common/src/middleware/errorHandler.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/backend-common/src/middleware/errorHandler.ts b/packages/backend-common/src/middleware/errorHandler.ts index a08849813d..2f3b904d38 100644 --- a/packages/backend-common/src/middleware/errorHandler.ts +++ b/packages/backend-common/src/middleware/errorHandler.ts @@ -63,10 +63,10 @@ export function errorHandler( return ( error: Error, _request: Request, - response: Response, + res: Response, next: NextFunction, ) => { - if (response.headersSent) { + if (res.headersSent) { // If the headers have already been sent, do not send the response again // as this will throw an error in the backend. next(error); @@ -80,7 +80,9 @@ export function errorHandler( logger.error(error); } - response.status(status).send(message); + res.status(status); + res.setHeader('content-type', 'text/plain'); + res.send(message); }; } From 3c4444cea2b0f65c8676d2829cf059edf7db9b46 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 8 Feb 2021 13:20:45 +0100 Subject: [PATCH 104/110] catalog-backend: use more explicit response types and let error handlers handle errors --- plugins/catalog-backend/src/service/router.ts | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/plugins/catalog-backend/src/service/router.ts b/plugins/catalog-backend/src/service/router.ts index 6064164e31..17cfc77d8b 100644 --- a/plugins/catalog-backend/src/service/router.ts +++ b/plugins/catalog-backend/src/service/router.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { errorHandler } from '@backstage/backend-common'; +import { errorHandler, NotFoundError } from '@backstage/backend-common'; import { locationSpecSchema, analyzeLocationSchema, @@ -57,7 +57,7 @@ export async function createRouter( const filter = EntityFilters.ofQuery(req.query); const fieldMapper = translateQueryToFieldMapper(req.query); const entities = await entitiesCatalog.entities(filter); - res.status(200).send(entities.map(fieldMapper)); + res.status(200).json(entities.map(fieldMapper)); }) .post('/entities', async (req, res) => { const body = await requireRequestBody(req); @@ -67,7 +67,7 @@ export async function createRouter( const [entity] = await entitiesCatalog.entities( EntityFilters.ofMatchers({ 'metadata.uid': result.entityId }), ); - res.status(200).send(entity); + res.status(200).json(entity); }) .get('/entities/by-uid/:uid', async (req, res) => { const { uid } = req.params; @@ -75,15 +75,14 @@ export async function createRouter( EntityFilters.ofMatchers({ 'metadata.uid': uid }), ); if (!entities.length) { - res.status(404).send(`No entity with uid ${uid}`); - } else { - res.status(200).send(entities[0]); + throw new NotFoundError(`No entity with uid ${uid}`); } + res.status(200).json(entities[0]); }) .delete('/entities/by-uid/:uid', async (req, res) => { const { uid } = req.params; await entitiesCatalog.removeEntityByUid(uid); - res.status(204).send(); + res.status(204).end(); }) .get('/entities/by-name/:kind/:namespace/:name', async (req, res) => { const { kind, namespace, name } = req.params; @@ -95,14 +94,11 @@ export async function createRouter( }), ); if (!entities.length) { - res - .status(404) - .send( - `No entity with kind ${kind} namespace ${namespace} name ${name}`, - ); - } else { - res.status(200).send(entities[0]); + throw new NotFoundError( + `No entity with kind ${kind} namespace ${namespace} name ${name}`, + ); } + res.status(200).json(entities[0]); }); } @@ -111,7 +107,7 @@ export async function createRouter( const input = await validateRequestBody(req, locationSpecSchema); const dryRun = yn(req.query.dryRun, { default: false }); const output = await higherOrderOperation.addLocation(input, { dryRun }); - res.status(201).send(output); + res.status(201).json(output); }); } @@ -119,22 +115,22 @@ export async function createRouter( router .get('/locations', async (_req, res) => { const output = await locationsCatalog.locations(); - res.status(200).send(output); + res.status(200).json(output); }) .get('/locations/:id/history', async (req, res) => { const { id } = req.params; const output = await locationsCatalog.locationHistory(id); - res.status(200).send(output); + res.status(200).json(output); }) .get('/locations/:id', async (req, res) => { const { id } = req.params; const output = await locationsCatalog.location(id); - res.status(200).send(output); + res.status(200).json(output); }) .delete('/locations/:id', async (req, res) => { const { id } = req.params; await locationsCatalog.removeLocation(id); - res.status(204).send(); + res.status(204).end(); }); } @@ -142,7 +138,7 @@ export async function createRouter( router.post('/analyze-location', async (req, res) => { const input = await validateRequestBody(req, analyzeLocationSchema); const output = await locationAnalyzer.analyzeLocation(input); - res.status(200).send(output); + res.status(200).json(output); }); } From 82b2c11b67472ec8ba2f44219e04151aadf5cc24 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 8 Feb 2021 13:26:11 +0100 Subject: [PATCH 105/110] added changesets --- .changeset/odd-buckets-compare.md | 5 +++++ .changeset/strange-cobras-unite.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/odd-buckets-compare.md create mode 100644 .changeset/strange-cobras-unite.md diff --git a/.changeset/odd-buckets-compare.md b/.changeset/odd-buckets-compare.md new file mode 100644 index 0000000000..faf68fb4a0 --- /dev/null +++ b/.changeset/odd-buckets-compare.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Set explicit content-type in error handler responses. diff --git a/.changeset/strange-cobras-unite.md b/.changeset/strange-cobras-unite.md new file mode 100644 index 0000000000..d53a324dab --- /dev/null +++ b/.changeset/strange-cobras-unite.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Refactored route response handling to use more explicit types and throw errors. From d2441aee3cd8fedc5d3962e918ba11a9634bcb79 Mon Sep 17 00:00:00 2001 From: Gowind Date: Fri, 5 Feb 2021 15:47:00 +0100 Subject: [PATCH 106/110] Add changeset --- .changeset/early-hotels-mate.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/early-hotels-mate.md diff --git a/.changeset/early-hotels-mate.md b/.changeset/early-hotels-mate.md new file mode 100644 index 0000000000..2cda0344bf --- /dev/null +++ b/.changeset/early-hotels-mate.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +use child logger, if provided, to log single location refresh From e3101688bc74cdc6fefbd2408eaa52f95cfab05b Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Mon, 8 Feb 2021 09:27:19 -0500 Subject: [PATCH 107/110] use P30D for snooze option --- plugins/cost-insights/src/types/Alert.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/cost-insights/src/types/Alert.ts b/plugins/cost-insights/src/types/Alert.ts index eb8919b71a..84c37a7c15 100644 --- a/plugins/cost-insights/src/types/Alert.ts +++ b/plugins/cost-insights/src/types/Alert.ts @@ -149,7 +149,7 @@ export const AlertSnoozeOptions: AlertSnoozeOption[] = [ label: '1 Month', }, { - duration: Duration.P3M, + duration: Duration.P90D, label: '1 Quarter', }, ]; From d36660721cd1a81b424a94ce7d8c40042e74ac0b Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Mon, 8 Feb 2021 09:30:36 -0500 Subject: [PATCH 108/110] changeset --- .changeset/cost-insights-tidy-geese-play.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cost-insights-tidy-geese-play.md diff --git a/.changeset/cost-insights-tidy-geese-play.md b/.changeset/cost-insights-tidy-geese-play.md new file mode 100644 index 0000000000..f8aa503ca6 --- /dev/null +++ b/.changeset/cost-insights-tidy-geese-play.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-cost-insights': patch +--- + +Fix snooze quarter option From 98cdb20f01b1329798456068a14d1fcc2415f6e8 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Tue, 9 Feb 2021 11:03:54 +0100 Subject: [PATCH 109/110] update roadmap with milestones links (#4453) --- docs/features/search/README.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/features/search/README.md b/docs/features/search/README.md index cd971d9d41..a06b9aee2b 100644 --- a/docs/features/search/README.md +++ b/docs/features/search/README.md @@ -24,12 +24,18 @@ Backstage ecosystem. ## Project roadmap -| Version | Description | -| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Backstage Search V.0 ✅ | Search Frontend letting you search through the entities of the software catalog. [See V.0 Use Cases.](#backstage-search-v0) | -| Backstage Search V.1 ⌛ | Basic “out-of-the-box” in-memory indexing process of entities, and their metadata, registered to the Software Catalog. [See V.1 Use Cases.](#backstage-search-v1) | -| Backstage Search V.2 ⌛ | Search Backend responsible for the indexing process of entities, and their metadata, registered to the Software Catalog. [See V.2 Use Cases.](#backstage-search-v2) | -| Backstage Search V.3 ⌛ | Standardized Search API lets you index other plugins data to the search engine of choice. [See V.3 Use Cases.](#backstage-search-v3) | +| Version | Description | +| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Backstage Search v0 ✅ | Search Frontend letting you search through the entities of the software catalog. [See v0 Use Cases.](#backstage-search-v0) | +| [Backstage Search V0.5 ⌛][v0.5] | Foundations for the architecture. | +| [Backstage Search v1 ⌛][v1] | Basic “out-of-the-box” in-memory indexing process of entities, and their metadata, registered to the Software Catalog. [See v1 Use Cases.](#backstage-search-v1) | +| [Backstage Search v2 ⌛][v2] | Search Backend responsible for the indexing process of entities, and their metadata, registered to the Software Catalog. [See v2 Use Cases.](#backstage-search-v2) | +| [Backstage Search v3 ⌛][v3] | Standardized Search API lets you index other plugins data to the search engine of choice. [See v3 Use Cases.](#backstage-search-v3) | + +[v0.5]: https://github.com/backstage/backstage/milestone/25 +[v1]: https://github.com/backstage/backstage/milestone/26 +[v2]: https://github.com/backstage/backstage/milestone/27 +[v3]: https://github.com/backstage/backstage/milestone/28 ## Use Cases From 5a5163519744d5e2589ecf719f1812c2fa48f735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 4 Feb 2021 15:54:54 +0100 Subject: [PATCH 110/110] backend-common: implement UrlReader.search that does glob matching --- .changeset/shiny-shirts-worry.md | 5 + packages/backend-common/package.json | 1 + .../src/reading/AzureUrlReader.ts | 5 + .../src/reading/BitbucketUrlReader.ts | 5 + .../src/reading/FetchUrlReader.ts | 13 +- .../src/reading/GithubUrlReader.test.ts | 383 +++++++++++++++++- .../src/reading/GithubUrlReader.ts | 142 ++++++- .../src/reading/GitlabUrlReader.ts | 9 +- .../src/reading/UrlReaderPredicateMux.ts | 14 + packages/backend-common/src/reading/index.ts | 2 +- packages/backend-common/src/reading/types.ts | 49 +++ packages/techdocs-common/src/helpers.test.ts | 15 +- .../src/stages/prepare/dir.test.ts | 1 + .../src/ingestion/LocationReaders.ts | 10 + .../processors/CodeOwnersProcessor.test.ts | 12 +- .../processors/PlaceholderProcessor.test.ts | 2 +- .../processors/UrlReaderProcessor.test.ts | 38 +- .../processors/UrlReaderProcessor.ts | 35 +- .../src/service/CatalogBuilder.test.ts | 1 + .../src/service/standaloneServer.ts | 1 + 20 files changed, 696 insertions(+), 47 deletions(-) create mode 100644 .changeset/shiny-shirts-worry.md diff --git a/.changeset/shiny-shirts-worry.md b/.changeset/shiny-shirts-worry.md new file mode 100644 index 0000000000..b4797b61b4 --- /dev/null +++ b/.changeset/shiny-shirts-worry.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Implement `UrlReader.search` which implements glob matching. diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index faec36dab4..895d241c6a 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -50,6 +50,7 @@ "knex": "^0.21.6", "lodash": "^4.17.15", "logform": "^2.1.1", + "minimatch": "^3.0.4", "minimist": "^1.2.5", "morgan": "^1.10.0", "selfsigned": "^1.10.7", diff --git a/packages/backend-common/src/reading/AzureUrlReader.ts b/packages/backend-common/src/reading/AzureUrlReader.ts index 578db2ac92..bc1b61a041 100644 --- a/packages/backend-common/src/reading/AzureUrlReader.ts +++ b/packages/backend-common/src/reading/AzureUrlReader.ts @@ -29,6 +29,7 @@ import { ReaderFactory, ReadTreeOptions, ReadTreeResponse, + SearchResponse, UrlReader, } from './types'; import { ReadTreeResponseFactory } from './tree'; @@ -116,6 +117,10 @@ export class AzureUrlReader implements UrlReader { }); } + async search(): Promise { + throw new Error('AzureUrlReader does not implement search'); + } + toString() { const { host, token } = this.options; return `azure{host=${host},authed=${Boolean(token)}}`; diff --git a/packages/backend-common/src/reading/BitbucketUrlReader.ts b/packages/backend-common/src/reading/BitbucketUrlReader.ts index 373c75588e..e51c03e562 100644 --- a/packages/backend-common/src/reading/BitbucketUrlReader.ts +++ b/packages/backend-common/src/reading/BitbucketUrlReader.ts @@ -31,6 +31,7 @@ import { ReaderFactory, ReadTreeOptions, ReadTreeResponse, + SearchResponse, UrlReader, } from './types'; @@ -129,6 +130,10 @@ export class BitbucketUrlReader implements UrlReader { }); } + async search(): Promise { + throw new Error('BitbucketUrlReader does not implement search'); + } + toString() { const { host, token, username, appPassword } = this.config; let authed = Boolean(token); diff --git a/packages/backend-common/src/reading/FetchUrlReader.ts b/packages/backend-common/src/reading/FetchUrlReader.ts index 81f1dfa90e..0b26dfe911 100644 --- a/packages/backend-common/src/reading/FetchUrlReader.ts +++ b/packages/backend-common/src/reading/FetchUrlReader.ts @@ -16,7 +16,12 @@ import fetch from 'cross-fetch'; import { NotFoundError } from '../errors'; -import { ReaderFactory, ReadTreeResponse, UrlReader } from './types'; +import { + ReaderFactory, + ReadTreeResponse, + SearchResponse, + UrlReader, +} from './types'; /** * A UrlReader that does a plain fetch of the URL. @@ -68,10 +73,14 @@ export class FetchUrlReader implements UrlReader { throw new Error(message); } - readTree(): Promise { + async readTree(): Promise { throw new Error('FetchUrlReader does not implement readTree'); } + async search(): Promise { + throw new Error('FetchUrlReader does not implement search'); + } + toString() { return 'fetch{}'; } diff --git a/packages/backend-common/src/reading/GithubUrlReader.test.ts b/packages/backend-common/src/reading/GithubUrlReader.test.ts index fae3f3ba12..d4e579b143 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.test.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.test.ts @@ -23,7 +23,13 @@ import { rest } from 'msw'; import { setupServer } from 'msw/node'; import path from 'path'; import { NotFoundError, NotModifiedError } from '../errors'; -import { GithubUrlReader } from './GithubUrlReader'; +import { + GhBlobResponse, + GhBranchResponse, + GhRepoResponse, + GhTreeResponse, + GithubUrlReader, +} from './GithubUrlReader'; import { ReadTreeResponseFactory } from './tree'; const treeResponseFactory = ReadTreeResponseFactory.create({ @@ -69,6 +75,10 @@ describe('GithubUrlReader', () => { }); }); + /* + * read + */ + describe('read', () => { it('should use the headers from the credentials provider to the fetch request when doing read', async () => { expect.assertions(2); @@ -107,6 +117,10 @@ describe('GithubUrlReader', () => { }); }); + /* + * readTree + */ + describe('readTree', () => { beforeEach(() => { mockFs({ @@ -128,29 +142,31 @@ describe('GithubUrlReader', () => { ); const reposGithubApiResponse = { - id: '123', + id: 123, full_name: 'backstage/mock', default_branch: 'main', branches_url: 'https://api.github.com/repos/backstage/mock/branches{/branch}', archive_url: 'https://api.github.com/repos/backstage/mock/{archive_format}{/ref}', - }; + } as Partial; const reposGheApiResponse = { - ...reposGithubApiResponse, + id: 123, + full_name: 'backstage/mock', + default_branch: 'main', branches_url: 'https://ghe.github.com/api/v3/repos/backstage/mock/branches{/branch}', archive_url: 'https://ghe.github.com/api/v3/repos/backstage/mock/{archive_format}{/ref}', - }; + } as Partial; const branchesApiResponse = { name: 'main', commit: { sha: 'etag123abc', }, - }; + } as Partial; beforeEach(() => { worker.use( @@ -392,4 +408,359 @@ describe('GithubUrlReader', () => { }).toThrowError('must configure an explicit apiBaseUrl'); }); }); + + /* + * search + */ + + describe('search', () => { + beforeEach(() => { + mockFs({ '/tmp': mockFs.directory() }); + }); + + afterEach(() => { + mockFs.restore(); + }); + + const repoBuffer = fs.readFileSync( + path.resolve( + 'src', + 'reading', + '__fixtures__', + 'backstage-mock-etag123.tar.gz', + ), + ); + + const githubTreeContents: GhTreeResponse['tree'] = [ + { + path: 'mkdocs.yml', + type: 'blob', + url: 'https://api.github.com/repos/backstage/mock/git/blobs/1', + }, + { + path: 'docs', + type: 'tree', + url: 'https://api.github.com/repos/backstage/mock/git/trees/2', + }, + { + path: 'docs/index.md', + type: 'blob', + url: 'https://api.github.com/repos/backstage/mock/git/blobs/3', + }, + ]; + + const gheTreeContents: GhTreeResponse['tree'] = [ + { + path: 'mkdocs.yml', + type: 'blob', + url: 'https://ghe.github.com/api/v3/repos/backstage/mock/git/blobs/1', + }, + { + path: 'docs', + type: 'tree', + url: 'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees/2', + }, + { + path: 'docs/index.md', + type: 'blob', + url: 'https://ghe.github.com/api/v3/repos/backstage/mock/git/blobs/3', + }, + ]; + + // Tarballs + beforeEach(() => { + worker.use( + rest.get( + 'https://api.github.com/repos/backstage/mock/tarball/etag123abc', + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/x-gzip'), + ctx.set( + 'content-disposition', + 'attachment; filename=backstage-mock-etag123.tar.gz', + ), + ctx.body(repoBuffer), + ), + ), + rest.get( + 'https://ghe.github.com/api/v3/repos/backstage/mock/tarball/etag123abc', + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/x-gzip'), + ctx.set( + 'content-disposition', + 'attachment; filename=backstage-mock-etag123.tar.gz', + ), + ctx.body(repoBuffer), + ), + ), + ); + }); + + // Repo details + beforeEach(() => { + const githubResponse = { + id: 123, + full_name: 'backstage/mock', + default_branch: 'main', + branches_url: + 'https://api.github.com/repos/backstage/mock/branches{/branch}', + archive_url: + 'https://api.github.com/repos/backstage/mock/{archive_format}{/ref}', + trees_url: + 'https://api.github.com/repos/backstage/mock/git/trees{/sha}', + } as Partial; + + const gheResponse = { + id: 123, + full_name: 'backstage/mock', + default_branch: 'main', + branches_url: + 'https://ghe.github.com/api/v3/repos/backstage/mock/branches{/branch}', + archive_url: + 'https://ghe.github.com/api/v3/repos/backstage/mock/{archive_format}{/ref}', + trees_url: + 'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees{/sha}', + } as Partial; + + worker.use( + rest.get('https://api.github.com/repos/backstage/mock', (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(githubResponse), + ), + ), + rest.get( + 'https://ghe.github.com/api/v3/repos/backstage/mock', + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(gheResponse), + ), + ), + ); + }); + + // Branch details + beforeEach(() => { + const response = { + name: 'main', + commit: { + sha: 'etag123abc', + }, + } as Partial; + + worker.use( + rest.get( + 'https://api.github.com/repos/backstage/mock/branches/main', + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(response), + ), + ), + rest.get( + 'https://ghe.github.com/api/v3/repos/backstage/mock/branches/main', + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(response), + ), + ), + rest.get( + 'https://api.github.com/repos/backstage/mock/branches/branchDoesNotExist', + (_, res, ctx) => res(ctx.status(404)), + ), + ); + }); + + // Blobs + beforeEach(() => { + const blob1Response = { + content: Buffer.from('site_name: Test\n').toString('base64'), + } as Partial; + + const blob3Response = { + content: Buffer.from('# Test\n').toString('base64'), + } as Partial; + + worker.use( + rest.get( + 'https://api.github.com/repos/backstage/mock/git/blobs/1', + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(blob1Response), + ), + ), + rest.get( + 'https://api.github.com/repos/backstage/mock/git/blobs/3', + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(blob3Response), + ), + ), + rest.get( + 'https://ghe.github.com/api/v3/repos/backstage/mock/git/blobs/1', + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(blob1Response), + ), + ), + rest.get( + 'https://ghe.github.com/api/v3/repos/backstage/mock/git/blobs/3', + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(blob3Response), + ), + ), + ); + }); + + async function runTests(reader: GithubUrlReader, baseUrl: string) { + const r1 = await reader.search( + `${baseUrl}/backstage/mock/tree/main/**/*`, + ); + expect(r1.etag).toBe('etag123abc'); + expect(r1.files.length).toBe(2); + + const r2 = await reader.search( + `${baseUrl}/backstage/mock/tree/main/**/*`, + { etag: 'somethingElse' }, + ); + expect(r2.etag).toBe('etag123abc'); + expect(r2.files.length).toBe(2); + + const r3 = await reader.search(`${baseUrl}/backstage/mock/tree/main/o`); + expect(r3.files.length).toBe(0); + + const r4 = await reader.search( + `${baseUrl}/backstage/mock/tree/main/*docs*`, + ); + expect(r4.files.length).toBe(1); + expect(r4.files[0].url).toBe( + `${baseUrl}/backstage/mock/tree/main/mkdocs.yml`, + ); + await expect(r4.files[0].content()).resolves.toEqual( + Buffer.from('site_name: Test\n'), + ); + + const r5 = await reader.search( + `${baseUrl}/backstage/mock/tree/main/*/index.*`, + ); + expect(r5.files.length).toBe(1); + expect(r5.files[0].url).toBe( + `${baseUrl}/backstage/mock/tree/main/docs/index.md`, + ); + await expect(r5.files[0].content()).resolves.toEqual( + Buffer.from('# Test\n'), + ); + } + + // eslint-disable-next-line jest/expect-expect + it('succeeds on github when going via repo listing', async () => { + worker.use( + rest.get( + 'https://api.github.com/repos/backstage/mock/git/trees/etag123abc', + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + truncated: false, + tree: githubTreeContents, + } as Partial), + ), + ), + ); + await runTests(githubProcessor, 'https://github.com'); + }); + + // eslint-disable-next-line jest/expect-expect + it('succeeds on github when going via readTree', async () => { + worker.use( + rest.get( + 'https://api.github.com/repos/backstage/mock/git/trees/etag123abc', + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + truncated: true, + tree: [], + } as Partial), + ), + ), + ); + await runTests(githubProcessor, 'https://github.com'); + }); + + // eslint-disable-next-line jest/expect-expect + it('succeeds on ghe when going via repo listing', async () => { + worker.use( + rest.get( + 'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees/etag123abc', + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + truncated: false, + tree: gheTreeContents, + } as Partial), + ), + ), + ); + await runTests(gheProcessor, 'https://ghe.github.com'); + }); + + // eslint-disable-next-line jest/expect-expect + it('succeeds on ghe when going via readTree', async () => { + worker.use( + rest.get( + 'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees/etag123abc', + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + truncated: true, + tree: [], + } as Partial), + ), + ), + ); + await runTests(gheProcessor, 'https://ghe.github.com'); + }); + + it('throws NotModifiedError when same etag', async () => { + await expect( + githubProcessor.search( + 'https://githib.com/backstage/mock/tree/main/**/*', + { etag: 'etag123abc' }, + ), + ).rejects.toThrow(NotModifiedError); + }); + + it('throws NotFoundError when missing branch', async () => { + await expect( + githubProcessor.search( + 'https://githib.com/backstage/mock/tree/branchDoesNotExist/**/*', + ), + ).rejects.toThrow(NotFoundError); + }); + }); }); diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index 99d641f2dd..291740a9fc 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -23,6 +23,7 @@ import { import { RestEndpointMethodTypes } from '@octokit/rest'; import fetch from 'cross-fetch'; import parseGitUrl from 'git-url-parse'; +import { Minimatch } from 'minimatch'; import { Readable } from 'stream'; import { NotFoundError, NotModifiedError } from '../errors'; import { ReadTreeResponseFactory } from './tree'; @@ -30,9 +31,17 @@ import { ReaderFactory, ReadTreeOptions, ReadTreeResponse, + SearchOptions, + SearchResponse, + SearchResponseFile, UrlReader, } from './types'; +export type GhRepoResponse = RestEndpointMethodTypes['repos']['get']['response']['data']; +export type GhBranchResponse = RestEndpointMethodTypes['repos']['getBranch']['response']['data']; +export type GhTreeResponse = RestEndpointMethodTypes['git']['getTree']['response']['data']; +export type GhBlobResponse = RestEndpointMethodTypes['git']['getBlob']['response']['data']; + /** * A processor that adds the ability to read files from GitHub v3 APIs, such as * the one exposed by GitHub itself. @@ -106,27 +115,43 @@ export class GithubUrlReader implements UrlReader { throw new NotModifiedError(); } + const { filepath } = parseGitUrl(url); const { headers } = await this.deps.credentialsProvider.getCredentials({ url, }); - // archive_url looks like "https://api.github.com/repos/owner/repo/{archive_format}{/ref}" - const archive = await this.fetchResponse( - repoDetails.repo.archive_url - .replace('{archive_format}', 'tarball') - .replace('{/ref}', `/${commitSha}`), + return this.doReadTree( + repoDetails.repo.archive_url, + commitSha, + filepath, + { headers }, + options, + ); + } + + async search(url: string, options?: SearchOptions): Promise { + const repoDetails = await this.getRepoDetails(url); + const commitSha = repoDetails.branch.commit.sha!; + + if (options?.etag && options.etag === commitSha) { + throw new NotModifiedError(); + } + + const { filepath } = parseGitUrl(url); + const { headers } = await this.deps.credentialsProvider.getCredentials({ + url, + }); + + const files = await this.doSearch( + url, + repoDetails.repo.trees_url, + repoDetails.repo.archive_url, + commitSha, + filepath, { headers }, ); - const { filepath } = parseGitUrl(url); - return await this.deps.treeResponseFactory.fromTarArchive({ - // TODO(Rugvip): Underlying implementation of fetch will be node-fetch, we probably want - // to stick to using that in exclusively backend code. - stream: (archive.body as unknown) as Readable, - subpath: filepath, - etag: commitSha, - filter: options?.filter, - }); + return { files, etag: commitSha }; } toString() { @@ -134,11 +159,92 @@ export class GithubUrlReader implements UrlReader { return `github{host=${host},authed=${Boolean(token)}}`; } + private async doReadTree( + archiveUrl: string, + sha: string, + subpath: string, + init: RequestInit, + options?: ReadTreeOptions, + ): Promise { + // archive_url looks like "https://api.github.com/repos/owner/repo/{archive_format}{/ref}" + const archive = await this.fetchResponse( + archiveUrl + .replace('{archive_format}', 'tarball') + .replace('{/ref}', `/${sha}`), + init, + ); + + return await this.deps.treeResponseFactory.fromTarArchive({ + // TODO(Rugvip): Underlying implementation of fetch will be node-fetch, we probably want + // to stick to using that in exclusively backend code. + stream: (archive.body as unknown) as Readable, + subpath, + etag: sha, + filter: options?.filter, + }); + } + + private async doSearch( + url: string, + treesUrl: string, + archiveUrl: string, + sha: string, + query: string, + init: RequestInit, + ): Promise { + function pathToUrl(path: string): string { + // TODO(freben): Use the integration package facility for this instead + // pathname starts as /backstage/backstage/blob/master/ + const updated = new URL(url); + const base = updated.pathname.split('/').slice(1, 5).join('/'); + updated.pathname = `${base}/${path}`; + return updated.toString(); + } + + const matcher = new Minimatch(query.replace(/^\/+/, '')); + + // trees_url looks like "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}" + const recursiveTree: GhTreeResponse = await this.fetchJson( + treesUrl.replace('{/sha}', `/${sha}?recursive=true`), + init, + ); + + // The simple case is that we got the entire tree in a single operation. + if (!recursiveTree.truncated) { + const matching = recursiveTree.tree.filter( + item => + item.type === 'blob' && + item.path && + item.url && + matcher.match(item.path), + ); + + return matching.map(item => ({ + url: pathToUrl(item.path!), + content: async () => { + const blob: GhBlobResponse = await this.fetchJson(item.url!, init); + return Buffer.from(blob.content, 'base64'); + }, + })); + } + + // For larger repos, we leverage readTree and filter through that instead + const tree = await this.doReadTree(archiveUrl, sha, '', init, { + filter: path => matcher.match(path), + }); + const files = await tree.files(); + + return files.map(file => ({ + url: pathToUrl(file.path), + content: file.content, + })); + } + private async getRepoDetails( url: string, ): Promise<{ - repo: RestEndpointMethodTypes['repos']['get']['response']['data']; - branch: RestEndpointMethodTypes['repos']['getBranch']['response']['data']; + repo: GhRepoResponse; + branch: GhBranchResponse; }> { const parsed = parseGitUrl(url); const { ref, full_name } = parsed; @@ -151,13 +257,13 @@ export class GithubUrlReader implements UrlReader { url, }); - const repo: RestEndpointMethodTypes['repos']['get']['response']['data'] = await this.fetchJson( + const repo: GhRepoResponse = await this.fetchJson( `${this.config.apiBaseUrl}/repos/${full_name}`, { headers }, ); // branches_url looks like "https://api.github.com/repos/owner/repo/branches{/branch}" - const branch: RestEndpointMethodTypes['repos']['getBranch']['response']['data'] = await this.fetchJson( + const branch: GhBranchResponse = await this.fetchJson( repo.branches_url.replace('{/branch}', `/${ref || repo.default_branch}`), { headers }, ); diff --git a/packages/backend-common/src/reading/GitlabUrlReader.ts b/packages/backend-common/src/reading/GitlabUrlReader.ts index 8a763d1259..5e02a9b8e6 100644 --- a/packages/backend-common/src/reading/GitlabUrlReader.ts +++ b/packages/backend-common/src/reading/GitlabUrlReader.ts @@ -21,16 +21,17 @@ import { readGitLabIntegrationConfigs, } from '@backstage/integration'; import fetch from 'cross-fetch'; +import parseGitUrl from 'git-url-parse'; +import { Readable } from 'stream'; import { NotFoundError, NotModifiedError } from '../errors'; import { ReadTreeResponseFactory } from './tree'; import { ReaderFactory, ReadTreeOptions, ReadTreeResponse, + SearchResponse, UrlReader, } from './types'; -import parseGitUrl from 'git-url-parse'; -import { Readable } from 'stream'; export class GitlabUrlReader implements UrlReader { private readonly treeResponseFactory: ReadTreeResponseFactory; @@ -154,6 +155,10 @@ export class GitlabUrlReader implements UrlReader { }); } + async search(): Promise { + throw new Error('GitlabUrlReader does not implement search'); + } + toString() { const { host, token } = this.config; return `gitlab{host=${host},authed=${Boolean(token)}}`; diff --git a/packages/backend-common/src/reading/UrlReaderPredicateMux.ts b/packages/backend-common/src/reading/UrlReaderPredicateMux.ts index 3183aa0c28..91f4ae63ab 100644 --- a/packages/backend-common/src/reading/UrlReaderPredicateMux.ts +++ b/packages/backend-common/src/reading/UrlReaderPredicateMux.ts @@ -18,6 +18,8 @@ import { NotAllowedError } from '../errors'; import { ReadTreeOptions, ReadTreeResponse, + SearchOptions, + SearchResponse, UrlReader, UrlReaderPredicateTuple, } from './types'; @@ -60,6 +62,18 @@ export class UrlReaderPredicateMux implements UrlReader { throw new NotAllowedError(`Reading from '${url}' is not allowed`); } + async search(url: string, options?: SearchOptions): Promise { + const parsed = new URL(url); + + for (const { predicate, reader } of this.readers) { + if (predicate(parsed)) { + return await reader.search(url, options); + } + } + + throw new NotAllowedError(`Reading from '${url}' is not allowed`); + } + toString() { return `predicateMux{readers=${this.readers.map(t => t.reader).join(',')}`; } diff --git a/packages/backend-common/src/reading/index.ts b/packages/backend-common/src/reading/index.ts index 8a6c2bf1a3..1266161ddf 100644 --- a/packages/backend-common/src/reading/index.ts +++ b/packages/backend-common/src/reading/index.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -export type { UrlReader, ReadTreeResponse } from './types'; +export type { UrlReader, ReadTreeResponse, SearchResponse } from './types'; export { UrlReaders } from './UrlReaders'; export { AzureUrlReader } from './AzureUrlReader'; export { BitbucketUrlReader } from './BitbucketUrlReader'; diff --git a/packages/backend-common/src/reading/types.ts b/packages/backend-common/src/reading/types.ts index be788fc6e2..4caa76841a 100644 --- a/packages/backend-common/src/reading/types.ts +++ b/packages/backend-common/src/reading/types.ts @@ -24,6 +24,7 @@ import { ReadTreeResponseFactory } from './tree'; export type UrlReader = { read(url: string): Promise; readTree(url: string, options?: ReadTreeOptions): Promise; + search(url: string, options?: SearchOptions): Promise; }; export type UrlReaderPredicateTuple = { @@ -103,3 +104,51 @@ export type ReadTreeResponseFile = { path: string; content(): Promise; }; + +/** + * An options object for search operations. + */ +export type SearchOptions = { + /** + * An etag can be provided to check whether the search response has changed from a previous execution. + * + * In the search() response, an etag is returned along with the files. The etag is a unique identifer + * of the current tree, usually the commit SHA or etag from the target. + * + * When an etag is given in SearchOptions, search will first compare the etag against the etag + * on the target branch. If they match, search will throw a NotModifiedError indicating that the search + * response will not differ from the previous response which included this particular etag. If they mismatch, + * search will return the rest of SearchResponse along with a new etag. + */ + etag?: string; +}; + +/** + * The output of a search operation. + */ +export type SearchResponse = { + /** + * The files that matched the search query. + */ + files: SearchResponseFile[]; + + /** + * A unique identifer of the current remote tree, usually the commit SHA or etag from the target. + */ + etag: string; +}; + +/** + * Represents a single file in a search response. + */ +export type SearchResponseFile = { + /** + * The full URL to the file. + */ + url: string; + + /** + * The binary contents of the file. + */ + content(): Promise; +}; diff --git a/packages/techdocs-common/src/helpers.test.ts b/packages/techdocs-common/src/helpers.test.ts index 740a08aaa2..eba1386b11 100644 --- a/packages/techdocs-common/src/helpers.test.ts +++ b/packages/techdocs-common/src/helpers.test.ts @@ -14,14 +14,18 @@ * limitations under the License. */ +import { + ReadTreeResponse, + SearchResponse, + UrlReader, +} from '@backstage/backend-common'; +import { Entity } from '@backstage/catalog-model'; import { Readable } from 'stream'; import { getDocFilesFromRepository, getLocationForEntity, parseReferenceAnnotation, } from './helpers'; -import { UrlReader, ReadTreeResponse } from '@backstage/backend-common'; -import { Entity } from '@backstage/catalog-model'; const entityBase: Entity = { metadata: { @@ -138,6 +142,13 @@ describe('getDocFilesFromRepository', () => { etag: '', }; } + + async search(): Promise { + return { + etag: '', + files: [], + }; + } } const output = await getDocFilesFromRepository( diff --git a/packages/techdocs-common/src/stages/prepare/dir.test.ts b/packages/techdocs-common/src/stages/prepare/dir.test.ts index 630608e817..cd8d358afe 100644 --- a/packages/techdocs-common/src/stages/prepare/dir.test.ts +++ b/packages/techdocs-common/src/stages/prepare/dir.test.ts @@ -49,6 +49,7 @@ const mockConfig = new ConfigReader({}); const mockUrlReader: jest.Mocked = { read: jest.fn(), readTree: jest.fn(), + search: jest.fn(), }; describe('directory preparer', () => { diff --git a/plugins/catalog-backend/src/ingestion/LocationReaders.ts b/plugins/catalog-backend/src/ingestion/LocationReaders.ts index a789e669d0..be88aa2810 100644 --- a/plugins/catalog-backend/src/ingestion/LocationReaders.ts +++ b/plugins/catalog-backend/src/ingestion/LocationReaders.ts @@ -139,6 +139,16 @@ export class LocationReaders implements LocationReader { if (emitResult.type === 'relation') { throw new Error('readLocation may not emit entity relations'); } + if ( + emitResult.type === 'location' && + emitResult.location.type === item.location.type && + emitResult.location.target === item.location.target + ) { + // Ignore self-referential locations silently (this can happen for + // example if you use a glob target like "**/*.yaml" in a Location + // entity) + return; + } emit(emitResult); }; diff --git a/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts index be4f15e501..eb1187eda5 100644 --- a/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts @@ -160,7 +160,7 @@ describe('CodeOwnersProcessor', () => { const read = jest .fn() .mockResolvedValue(mockReadResult({ data: ownersText })); - const reader = { read, readTree: jest.fn() }; + const reader = { read, readTree: jest.fn(), search: jest.fn() }; const result = await findRawCodeOwners(mockLocation(), { reader, logger, @@ -170,7 +170,7 @@ describe('CodeOwnersProcessor', () => { it('should return undefined when no codeowner', async () => { const read = jest.fn().mockRejectedValue(mockReadResult()); - const reader = { read, readTree: jest.fn() }; + const reader = { read, readTree: jest.fn(), search: jest.fn() }; await expect( findRawCodeOwners(mockLocation(), { reader, logger }), @@ -184,7 +184,7 @@ describe('CodeOwnersProcessor', () => { .mockImplementationOnce(() => mockReadResult({ error: 'foo' })) .mockImplementationOnce(() => mockReadResult({ error: 'bar' })) .mockResolvedValue(mockReadResult({ data: ownersText })); - const reader = { read, readTree: jest.fn() }; + const reader = { read, readTree: jest.fn(), search: jest.fn() }; const result = await findRawCodeOwners(mockLocation(), { reader, @@ -206,7 +206,7 @@ describe('CodeOwnersProcessor', () => { const read = jest .fn() .mockResolvedValue(mockReadResult({ data: mockCodeOwnersText() })); - const reader = { read, readTree: jest.fn() }; + const reader = { read, readTree: jest.fn(), search: jest.fn() }; const owner = await resolveCodeOwner(mockLocation(), { reader, logger }); expect(owner).toBe('backstage-core'); @@ -216,7 +216,7 @@ describe('CodeOwnersProcessor', () => { const read = jest .fn() .mockImplementation(() => mockReadResult({ error: 'error: foo' })); - const reader = { read, readTree: jest.fn() }; + const reader = { read, readTree: jest.fn(), search: jest.fn() }; await expect( resolveCodeOwner(mockLocation(), { reader, logger }), @@ -230,7 +230,7 @@ describe('CodeOwnersProcessor', () => { const read = jest .fn() .mockResolvedValue(mockReadResult({ data: mockCodeOwnersText() })); - const reader = { read, readTree: jest.fn() }; + const reader = { read, readTree: jest.fn(), search: jest.fn() }; const processor = new CodeOwnersProcessor({ reader, logger }); return { entity, processor, read }; diff --git a/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.test.ts index 8b2632ad5d..00b6af94e5 100644 --- a/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.test.ts @@ -27,7 +27,7 @@ import { describe('PlaceholderProcessor', () => { const read: jest.MockedFunction = jest.fn(); - const reader: UrlReader = { read, readTree: jest.fn() }; + const reader: UrlReader = { read, readTree: jest.fn(), search: jest.fn() }; beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.test.ts index 49b6fe71e0..db731b5b17 100644 --- a/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.test.ts @@ -14,24 +14,29 @@ * limitations under the License. */ -import { UrlReaderProcessor } from './UrlReaderProcessor'; -import { getVoidLogger, UrlReaders } from '@backstage/backend-common'; +import { + getVoidLogger, + UrlReader, + UrlReaders, +} from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; +import { msw } from '@backstage/test-utils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { msw } from '@backstage/test-utils'; import { CatalogProcessorEntityResult, CatalogProcessorErrorResult, CatalogProcessorResult, } from './types'; +import { UrlReaderProcessor } from './UrlReaderProcessor'; import { defaultEntityDataParser } from './util/parse'; describe('UrlReaderProcessor', () => { const mockApiOrigin = 'http://localhost'; - const server = setupServer(); + const server = setupServer(); msw.setupDefaultHandlers(server); + it('should load from url', async () => { const logger = getVoidLogger(); const reader = UrlReaders.default({ @@ -57,7 +62,7 @@ describe('UrlReaderProcessor', () => { )) as CatalogProcessorEntityResult; expect(generated.type).toBe('entity'); - expect(generated.location).toBe(spec); + expect(generated.location).toEqual(spec); expect(generated.entity).toEqual({ mock: 'entity' }); }); @@ -92,4 +97,27 @@ describe('UrlReaderProcessor', () => { `Unable to read url, NotFoundError: could not read ${mockApiOrigin}/component-notfound.yaml, 404 Not Found`, ); }); + + it('uses search when there are globs', async () => { + const logger = getVoidLogger(); + + const reader: jest.Mocked = { + read: jest.fn(), + readTree: jest.fn(), + search: jest.fn().mockImplementation(async () => []), + }; + + const processor = new UrlReaderProcessor({ reader, logger }); + + const emit = jest.fn(); + + await processor.readLocation( + { type: 'url', target: 'https://github.com/a/b/blob/x/**/b.yaml' }, + false, + emit, + defaultEntityDataParser, + ); + + expect(reader.search).toBeCalledTimes(1); + }); }); diff --git a/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.ts index 9daae29d3b..b70ef2877d 100644 --- a/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.ts @@ -16,6 +16,8 @@ import { UrlReader } from '@backstage/backend-common'; import { LocationSpec } from '@backstage/catalog-model'; +import parseGitUrl from 'git-url-parse'; +import limiterFactory from 'p-limit'; import { Logger } from 'winston'; import * as result from './results'; import { @@ -59,10 +61,14 @@ export class UrlReaderProcessor implements CatalogProcessor { } try { - const data = await this.options.reader.read(location.target); - - for await (const parseResult of parser({ data, location })) { - emit(parseResult); + const output = await this.doRead(location.target); + for (const item of output) { + for await (const parseResult of parser({ + data: item.data, + location: { type: location.type, target: item.url }, + })) { + emit(parseResult); + } } } catch (error) { const message = `Unable to read ${location.type}, ${error}`; @@ -78,4 +84,25 @@ export class UrlReaderProcessor implements CatalogProcessor { return true; } + + private async doRead( + location: string, + ): Promise<{ data: Buffer; url: string }[]> { + // Does it contain globs? I.e. does it contain asterisks or question marks + // (no curly braces for now) + const { filepath } = parseGitUrl(location); + if (filepath?.match(/[*?]/)) { + const limiter = limiterFactory(5); + const response = await this.options.reader.search(location); + const output = response.files.map(async file => ({ + url: file.url, + data: await limiter(file.content), + })); + return Promise.all(output); + } + + // Otherwise do a plain read + const data = await this.options.reader.read(location); + return [{ url: location, data }]; + } } diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.test.ts b/plugins/catalog-backend/src/service/CatalogBuilder.test.ts index 695902fec9..bc58338fce 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.test.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.test.ts @@ -44,6 +44,7 @@ describe('CatalogBuilder', () => { const reader: jest.Mocked = { read: jest.fn(), readTree: jest.fn(), + search: jest.fn(), }; const env: CatalogEnvironment = { logger: getVoidLogger(), diff --git a/plugins/techdocs-backend/src/service/standaloneServer.ts b/plugins/techdocs-backend/src/service/standaloneServer.ts index d91e2f08a2..0fd23a4f7d 100644 --- a/plugins/techdocs-backend/src/service/standaloneServer.ts +++ b/plugins/techdocs-backend/src/service/standaloneServer.ts @@ -53,6 +53,7 @@ export async function startStandaloneServer( const mockUrlReader: jest.Mocked = { read: jest.fn(), readTree: jest.fn(), + search: jest.fn(), }; logger.debug('Creating application...');