From f2b339a30cc3f0343e8bd366f5e33b4cc9f3b371 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 28 May 2021 14:02:15 +0200 Subject: [PATCH 001/102] Fix existing GCS tests to match others. Signed-off-by: Eric Peterson --- packages/techdocs-common/__mocks__/@google-cloud/storage.ts | 3 +++ .../techdocs-common/src/stages/publish/googleStorage.test.ts | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/techdocs-common/__mocks__/@google-cloud/storage.ts b/packages/techdocs-common/__mocks__/@google-cloud/storage.ts index 5e9890cd98..684c4023d7 100644 --- a/packages/techdocs-common/__mocks__/@google-cloud/storage.ts +++ b/packages/techdocs-common/__mocks__/@google-cloud/storage.ts @@ -63,6 +63,9 @@ class GCSFile { process.nextTick(() => { if (fs.existsSync(this.localFilePath)) { + if (readable.eventNames().includes('pipe')) { + readable.emit('pipe'); + } readable.emit('data', fs.readFileSync(this.localFilePath)); readable.emit('end'); } else { diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts index f29e1f10be..c4c032015e 100644 --- a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts @@ -309,7 +309,9 @@ describe('GoogleGCSPublish', () => { const pngResponse = await request(app).get( `/${namespace}/${kind}/${name}/img/with%20spaces.png`, ); - expect(pngResponse.text).toEqual('found it'); + expect(Buffer.from(pngResponse.body).toString('utf8')).toEqual( + 'found it', + ); const jsResponse = await request(app).get( `/${namespace}/${kind}/${name}/some%20folder/also%20with%20spaces.js`, ); From dc6cf3b14f81212fc8fbd4d3ba8a9d11d1b4887d Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 28 May 2021 14:06:44 +0200 Subject: [PATCH 002/102] Test for sanitization bypass Signed-off-by: Eric Peterson --- .../src/stages/publish/awsS3.test.ts | 18 +++++++ .../stages/publish/azureBlobStorage.test.ts | 18 +++++++ .../src/stages/publish/googleStorage.test.ts | 19 ++++++- .../src/stages/publish/local.test.ts | 49 ++++++++++++++++--- .../src/stages/publish/openStackSwift.test.ts | 18 +++++++ 5 files changed, 114 insertions(+), 8 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/awsS3.test.ts b/packages/techdocs-common/src/stages/publish/awsS3.test.ts index b2954f7b2e..ada3bf1b8f 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.test.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.test.ts @@ -286,6 +286,9 @@ describe('AwsS3Publish', () => { mockFs.restore(); mockFs({ [entityRootDir]: { + html: { + 'file.html': '', + }, img: { 'with spaces.png': 'found it', }, @@ -318,5 +321,20 @@ describe('AwsS3Publish', () => { ); expect(jsResponse.text).toEqual('found it too'); }); + + it('should pass text/plain content-type for html', async () => { + const { + kind, + metadata: { namespace, name }, + } = entity; + + const response = await request(app).get( + `/${namespace}/${kind}/${name}/html/file.html`, + ); + expect(response.text).toEqual(''); + expect(response.header).toMatchObject({ + 'content-type': 'text/plain; charset=utf-8', + }); + }); }); }); diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts index 568393662b..9ddf640abf 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts @@ -343,6 +343,9 @@ describe('publishing with valid credentials', () => { mockFs.restore(); mockFs({ [entityRootDir]: { + html: { + 'file.html': '', + }, img: { 'with spaces.png': 'found it', }, @@ -375,5 +378,20 @@ describe('publishing with valid credentials', () => { ); expect(jsResponse.text).toEqual('found it too'); }); + + it('should pass text/plain content-type for html', async () => { + const { + kind, + metadata: { namespace, name }, + } = entity; + + const response = await request(app).get( + `/${namespace}/${kind}/${name}/html/file.html`, + ); + expect(response.text).toEqual(''); + expect(response.header).toMatchObject({ + 'content-type': 'text/plain; charset=utf-8', + }); + }); }); }); diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts index c4c032015e..23110ebcf4 100644 --- a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts @@ -285,6 +285,9 @@ describe('GoogleGCSPublish', () => { mockFs.restore(); mockFs({ [entityRootDir]: { + html: { + 'file.html': '', + }, img: { 'with spaces.png': 'found it', }, @@ -315,7 +318,21 @@ describe('GoogleGCSPublish', () => { const jsResponse = await request(app).get( `/${namespace}/${kind}/${name}/some%20folder/also%20with%20spaces.js`, ); - expect(jsResponse.text).toEqual('found it too'); + }); + + it('should pass text/plain content-type for html', async () => { + const { + kind, + metadata: { namespace, name }, + } = entity; + + const response = await request(app).get( + `/${namespace}/${kind}/${name}/html/file.html`, + ); + expect(response.text).toEqual(''); + expect(response.header).toMatchObject({ + 'content-type': 'text/plain; charset=utf-8', + }); }); }); }); diff --git a/packages/techdocs-common/src/stages/publish/local.test.ts b/packages/techdocs-common/src/stages/publish/local.test.ts index cd1a17ab5f..a5dca5f057 100644 --- a/packages/techdocs-common/src/stages/publish/local.test.ts +++ b/packages/techdocs-common/src/stages/publish/local.test.ts @@ -16,8 +16,11 @@ import { getVoidLogger, PluginEndpointDiscovery, + resolvePackagePath, } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; +import express from 'express'; +import request from 'supertest'; import mockFs from 'mock-fs'; import * as os from 'os'; import { LocalPublish } from './local'; @@ -35,11 +38,21 @@ const createMockEntity = (annotations = {}) => { }; }; +const testDiscovery: jest.Mocked = { + getBaseUrl: jest.fn().mockResolvedValue('http://localhost:7000/api/techdocs'), + getExternalBaseUrl: jest.fn(), +}; + const logger = getVoidLogger(); const tmpDir = os.platform() === 'win32' ? 'C:\\tmp\\generatedDir' : '/tmp/generatedDir'; +const resolvedDir = resolvePackagePath( + '@backstage/plugin-techdocs-backend', + 'static/docs', +); + describe('local publisher', () => { it('should publish generated documentation dir', async () => { mockFs({ @@ -48,13 +61,6 @@ describe('local publisher', () => { }, }); - const testDiscovery: jest.Mocked = { - getBaseUrl: jest - .fn() - .mockResolvedValue('http://localhost:7000/api/techdocs'), - getExternalBaseUrl: jest.fn(), - }; - const mockConfig = new ConfigReader({}); const publisher = new LocalPublish(mockConfig, logger, testDiscovery); @@ -66,4 +72,33 @@ describe('local publisher', () => { mockFs.restore(); }); + + describe('docsRouter', () => { + const mockConfig = new ConfigReader({}); + const publisher = new LocalPublish(mockConfig, logger, testDiscovery); + let app: express.Express; + + beforeEach(() => { + app = express().use(publisher.docsRouter()); + + mockFs.restore(); + mockFs({ + [resolvedDir]: { + 'some-file.html': 'found it', + }, + }); + }); + + afterEach(() => { + mockFs.restore(); + }); + + it('should pass text/plain content-type for html', async () => { + const response = await request(app).get(`/some-file.html`); + expect(response.text).toEqual('found it'); + expect(response.header).toMatchObject({ + 'content-type': 'text/plain; charset=utf-8', + }); + }); + }); }); diff --git a/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts b/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts index aeaceeaaa6..35e5586ee4 100644 --- a/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts +++ b/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts @@ -291,6 +291,9 @@ describe('OpenStackSwiftPublish', () => { mockFs.restore(); mockFs({ [entityRootDir]: { + html: { + 'file.html': '', + }, img: { 'with spaces.png': 'found it', }, @@ -323,5 +326,20 @@ describe('OpenStackSwiftPublish', () => { ); expect(jsResponse.text).toEqual('found it too'); }); + + it('should pass text/plain content-type for html', async () => { + const { + kind, + metadata: { namespace, name }, + } = entity; + + const response = await request(app).get( + `/${namespace}/${kind}/${name}/html/file.html`, + ); + expect(response.text).toEqual(''); + expect(response.header).toMatchObject({ + 'content-type': 'text/plain; charset=utf-8', + }); + }); }); }); From 58ba10677a2762e0b5db168539d42518b5be5388 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 28 May 2021 14:13:30 +0200 Subject: [PATCH 003/102] Enforce plain text header for html files Signed-off-by: Eric Peterson --- .../src/stages/publish/helpers.test.ts | 2 +- .../techdocs-common/src/stages/publish/helpers.ts | 12 ++++++++++-- packages/techdocs-common/src/stages/publish/local.ts | 12 +++++++++++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/helpers.test.ts b/packages/techdocs-common/src/stages/publish/helpers.test.ts index 1fae66f3d2..ae872be016 100644 --- a/packages/techdocs-common/src/stages/publish/helpers.test.ts +++ b/packages/techdocs-common/src/stages/publish/helpers.test.ts @@ -20,7 +20,7 @@ import { getFileTreeRecursively, getHeadersForFileExtension } from './helpers'; describe('getHeadersForFileExtension', () => { const correctMapOfExtensions = [ - ['.html', 'text/html; charset=utf-8'], + ['.html', 'text/plain; charset=utf-8'], ['.css', 'text/css; charset=utf-8'], ['.png', 'image/png'], ['.jpg', 'image/jpeg'], diff --git a/packages/techdocs-common/src/stages/publish/helpers.ts b/packages/techdocs-common/src/stages/publish/helpers.ts index 138ec611e0..63568699c7 100644 --- a/packages/techdocs-common/src/stages/publish/helpers.ts +++ b/packages/techdocs-common/src/stages/publish/helpers.ts @@ -28,10 +28,18 @@ export type responseHeadersType = { export const getHeadersForFileExtension = ( fileExtension: string, ): responseHeadersType => { - return { + const headerType = { 'Content-Type': mime.contentType(fileExtension) || 'text/plain; charset=utf-8', - } as responseHeadersType; + }; + + // Prevent sanitization bypass by preventing browers from directly rendering + // the contents of HTML files kept in storage. + if (headerType['Content-Type'].match(/html/)) { + headerType['Content-Type'] = 'text/plain; charset=utf-8'; + } + + return headerType; }; /** diff --git a/packages/techdocs-common/src/stages/publish/local.ts b/packages/techdocs-common/src/stages/publish/local.ts index e09473cf60..036053627b 100644 --- a/packages/techdocs-common/src/stages/publish/local.ts +++ b/packages/techdocs-common/src/stages/publish/local.ts @@ -31,6 +31,7 @@ import { ReadinessResponse, TechDocsMetadata, } from './types'; +import { getHeadersForFileExtension } from './helpers'; // TODO: Use a more persistent storage than node_modules or /tmp directory. // Make it configurable with techdocs.publisher.local.publishDirectory @@ -132,7 +133,16 @@ export class LocalPublish implements PublisherBase { } docsRouter(): express.Handler { - return express.static(staticDocsDir); + return express.static(staticDocsDir, { + // Handle content-type header the same as all other publishers. + setHeaders: (res, filePath) => { + const fileExtension = path.extname(filePath); + const { 'Content-Type': header } = getHeadersForFileExtension( + fileExtension, + ); + res.setHeader('Content-Type', header); + }, + }); } async hasDocsBeenGenerated(entity: Entity): Promise { From 104d2d44ee8c8e2c558be663d214457cdc5dbeae Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 28 May 2021 17:40:52 +0200 Subject: [PATCH 004/102] Account for SVG/XML files too. Signed-off-by: Eric Peterson --- .../src/stages/publish/awsS3.test.ts | 19 ++++++++++++----- .../stages/publish/azureBlobStorage.test.ts | 19 ++++++++++++----- .../src/stages/publish/googleStorage.test.ts | 20 +++++++++++++----- .../src/stages/publish/helpers.test.ts | 2 +- .../src/stages/publish/helpers.ts | 4 ++-- .../src/stages/publish/local.test.ts | 17 ++++++++++----- .../src/stages/publish/openStackSwift.test.ts | 21 +++++++++++++------ 7 files changed, 73 insertions(+), 29 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/awsS3.test.ts b/packages/techdocs-common/src/stages/publish/awsS3.test.ts index ada3bf1b8f..d4fb0d2dc2 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.test.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.test.ts @@ -287,10 +287,11 @@ describe('AwsS3Publish', () => { mockFs({ [entityRootDir]: { html: { - 'file.html': '', + 'unsafe.html': '', }, img: { 'with spaces.png': 'found it', + 'unsafe.svg': '', }, 'some folder': { 'also with spaces.js': 'found it too', @@ -328,11 +329,19 @@ describe('AwsS3Publish', () => { metadata: { namespace, name }, } = entity; - const response = await request(app).get( - `/${namespace}/${kind}/${name}/html/file.html`, + const htmlResponse = await request(app).get( + `/${namespace}/${kind}/${name}/html/unsafe.html`, ); - expect(response.text).toEqual(''); - expect(response.header).toMatchObject({ + expect(htmlResponse.text).toEqual(''); + expect(htmlResponse.header).toMatchObject({ + 'content-type': 'text/plain; charset=utf-8', + }); + + const svgResponse = await request(app).get( + `/${namespace}/${kind}/${name}/img/unsafe.svg`, + ); + expect(svgResponse.text).toEqual(''); + expect(svgResponse.header).toMatchObject({ 'content-type': 'text/plain; charset=utf-8', }); }); diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts index 9ddf640abf..0bf720ae31 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts @@ -344,10 +344,11 @@ describe('publishing with valid credentials', () => { mockFs({ [entityRootDir]: { html: { - 'file.html': '', + 'unsafe.html': '', }, img: { 'with spaces.png': 'found it', + 'unsafe.svg': '', }, 'some folder': { 'also with spaces.js': 'found it too', @@ -385,11 +386,19 @@ describe('publishing with valid credentials', () => { metadata: { namespace, name }, } = entity; - const response = await request(app).get( - `/${namespace}/${kind}/${name}/html/file.html`, + const htmlResponse = await request(app).get( + `/${namespace}/${kind}/${name}/html/unsafe.html`, ); - expect(response.text).toEqual(''); - expect(response.header).toMatchObject({ + expect(htmlResponse.text).toEqual(''); + expect(htmlResponse.header).toMatchObject({ + 'content-type': 'text/plain; charset=utf-8', + }); + + const svgResponse = await request(app).get( + `/${namespace}/${kind}/${name}/img/unsafe.svg`, + ); + expect(svgResponse.text).toEqual(''); + expect(svgResponse.header).toMatchObject({ 'content-type': 'text/plain; charset=utf-8', }); }); diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts index 23110ebcf4..4db1d83c7e 100644 --- a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts @@ -286,10 +286,11 @@ describe('GoogleGCSPublish', () => { mockFs({ [entityRootDir]: { html: { - 'file.html': '', + 'unsafe.html': '', }, img: { 'with spaces.png': 'found it', + 'unsafe.svg': '', }, 'some folder': { 'also with spaces.js': 'found it too', @@ -318,6 +319,7 @@ describe('GoogleGCSPublish', () => { const jsResponse = await request(app).get( `/${namespace}/${kind}/${name}/some%20folder/also%20with%20spaces.js`, ); + expect(jsResponse.text).toEqual('found it too'); }); it('should pass text/plain content-type for html', async () => { @@ -326,11 +328,19 @@ describe('GoogleGCSPublish', () => { metadata: { namespace, name }, } = entity; - const response = await request(app).get( - `/${namespace}/${kind}/${name}/html/file.html`, + const htmlResponse = await request(app).get( + `/${namespace}/${kind}/${name}/html/unsafe.html`, ); - expect(response.text).toEqual(''); - expect(response.header).toMatchObject({ + expect(htmlResponse.text).toEqual(''); + expect(htmlResponse.header).toMatchObject({ + 'content-type': 'text/plain; charset=utf-8', + }); + + const svgResponse = await request(app).get( + `/${namespace}/${kind}/${name}/img/unsafe.svg`, + ); + expect(svgResponse.text).toEqual(''); + expect(svgResponse.header).toMatchObject({ 'content-type': 'text/plain; charset=utf-8', }); }); diff --git a/packages/techdocs-common/src/stages/publish/helpers.test.ts b/packages/techdocs-common/src/stages/publish/helpers.test.ts index ae872be016..d5af98e7e1 100644 --- a/packages/techdocs-common/src/stages/publish/helpers.test.ts +++ b/packages/techdocs-common/src/stages/publish/helpers.test.ts @@ -25,7 +25,7 @@ describe('getHeadersForFileExtension', () => { ['.png', 'image/png'], ['.jpg', 'image/jpeg'], ['.jpeg', 'image/jpeg'], - ['.svg', 'image/svg+xml'], + ['.svg', 'text/plain; charset=utf-8'], ['.json', 'application/json; charset=utf-8'], ['.this-in-not-an-extension', 'text/plain; charset=utf-8'], ]; diff --git a/packages/techdocs-common/src/stages/publish/helpers.ts b/packages/techdocs-common/src/stages/publish/helpers.ts index 63568699c7..3ca424055f 100644 --- a/packages/techdocs-common/src/stages/publish/helpers.ts +++ b/packages/techdocs-common/src/stages/publish/helpers.ts @@ -34,8 +34,8 @@ export const getHeadersForFileExtension = ( }; // Prevent sanitization bypass by preventing browers from directly rendering - // the contents of HTML files kept in storage. - if (headerType['Content-Type'].match(/html/)) { + // the contents of untrusted content. + if (headerType['Content-Type'].match(/html|xml/)) { headerType['Content-Type'] = 'text/plain; charset=utf-8'; } diff --git a/packages/techdocs-common/src/stages/publish/local.test.ts b/packages/techdocs-common/src/stages/publish/local.test.ts index a5dca5f057..114401f1db 100644 --- a/packages/techdocs-common/src/stages/publish/local.test.ts +++ b/packages/techdocs-common/src/stages/publish/local.test.ts @@ -84,7 +84,8 @@ describe('local publisher', () => { mockFs.restore(); mockFs({ [resolvedDir]: { - 'some-file.html': 'found it', + 'unsafe.html': '', + 'unsafe.svg': '', }, }); }); @@ -93,10 +94,16 @@ describe('local publisher', () => { mockFs.restore(); }); - it('should pass text/plain content-type for html', async () => { - const response = await request(app).get(`/some-file.html`); - expect(response.text).toEqual('found it'); - expect(response.header).toMatchObject({ + it('should pass text/plain content-type for unsafe types', async () => { + const htmlResponse = await request(app).get(`/unsafe.html`); + expect(htmlResponse.text).toEqual(''); + expect(htmlResponse.header).toMatchObject({ + 'content-type': 'text/plain; charset=utf-8', + }); + + const svgResponse = await request(app).get(`/unsafe.svg`); + expect(svgResponse.text).toEqual(''); + expect(svgResponse.header).toMatchObject({ 'content-type': 'text/plain; charset=utf-8', }); }); diff --git a/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts b/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts index 35e5586ee4..dfac7fb0f7 100644 --- a/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts +++ b/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts @@ -292,9 +292,10 @@ describe('OpenStackSwiftPublish', () => { mockFs({ [entityRootDir]: { html: { - 'file.html': '', + 'unsafe.html': '', }, img: { + 'unsafe.svg': '', 'with spaces.png': 'found it', }, 'some folder': { @@ -327,17 +328,25 @@ describe('OpenStackSwiftPublish', () => { expect(jsResponse.text).toEqual('found it too'); }); - it('should pass text/plain content-type for html', async () => { + it('should pass text/plain content-type for unsafe types', async () => { const { kind, metadata: { namespace, name }, } = entity; - const response = await request(app).get( - `/${namespace}/${kind}/${name}/html/file.html`, + const htmlResponse = await request(app).get( + `/${namespace}/${kind}/${name}/html/unsafe.html`, ); - expect(response.text).toEqual(''); - expect(response.header).toMatchObject({ + expect(htmlResponse.text).toEqual(''); + expect(htmlResponse.header).toMatchObject({ + 'content-type': 'text/plain; charset=utf-8', + }); + + const svgResponse = await request(app).get( + `/${namespace}/${kind}/${name}/img/unsafe.svg`, + ); + expect(svgResponse.text).toEqual(''); + expect(svgResponse.header).toMatchObject({ 'content-type': 'text/plain; charset=utf-8', }); }); From 1b24ae1c7fc7611213bb9d7a0fbbd66726835752 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 28 May 2021 17:45:16 +0200 Subject: [PATCH 005/102] Account for displaying SVGs in the frontend. Signed-off-by: Eric Peterson --- .../reader/transformers/addBaseUrl.test.ts | 41 ++++++++++++++++++- .../src/reader/transformers/addBaseUrl.ts | 23 +++++++++-- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts b/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts index afed2eff73..2b17d04144 100644 --- a/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts +++ b/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts @@ -47,8 +47,17 @@ const mockEntityId = { namespace: '', name: '', }; - describe('addBaseUrl', () => { + const originalFetch = global.fetch; + + beforeEach(() => { + global.fetch = jest.fn(); + }); + + afterEach(() => { + global.fetch = originalFetch; + }); + it('contains relative paths', () => { createTestShadowDom(fixture, { preTransformers: [ @@ -86,4 +95,34 @@ describe('addBaseUrl', () => { '', ); }); + + it('transforms svg img src to data uri', async () => { + const svgContent = ''; + const expectedSrc = `data:image/svg+xml;base64,${Buffer.from( + svgContent, + ).toString('base64')}`; + + (global.fetch as jest.Mock).mockReturnValue({ + text: jest.fn().mockResolvedValue(svgContent), + }); + + const root = createTestShadowDom('', { + preTransformers: [ + addBaseUrl({ + techdocsStorageApi, + entityId: mockEntityId, + path: '', + }), + ], + postTransformers: [], + }); + + await new Promise(done => { + process.nextTick(() => { + const actualSrc = root.getElementById('x')?.getAttribute('src'); + expect(expectedSrc).toEqual(actualSrc); + done(); + }); + }); + }); }); diff --git a/plugins/techdocs/src/reader/transformers/addBaseUrl.ts b/plugins/techdocs/src/reader/transformers/addBaseUrl.ts index 9bb5418bc2..98db162b46 100644 --- a/plugins/techdocs/src/reader/transformers/addBaseUrl.ts +++ b/plugins/techdocs/src/reader/transformers/addBaseUrl.ts @@ -38,10 +38,27 @@ export const addBaseUrl = ({ .forEach(async (elem: T) => { const elemAttribute = elem.getAttribute(attributeName); if (!elemAttribute) return; - elem.setAttribute( - attributeName, - await techdocsStorageApi.getBaseUrl(elemAttribute, entityId, path), + + // Special handling for SVG images. + const newValue = await techdocsStorageApi.getBaseUrl( + elemAttribute, + entityId, + path, ); + if (attributeName === 'src' && elemAttribute.endsWith('.svg')) { + try { + const svg = await fetch(newValue); + const svgContent = await svg.text(); + elem.setAttribute( + attributeName, + `data:image/svg+xml;base64,${btoa(svgContent)}`, + ); + } catch (e) { + elem.setAttribute('alt', `Error: ${elemAttribute}`); + } + } else { + elem.setAttribute(attributeName, newValue); + } }); }; From 348c46896fed3a0b92bd376ad5825323637e402a Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 28 May 2021 17:46:08 +0200 Subject: [PATCH 006/102] Disallow object tags Signed-off-by: Eric Peterson --- plugins/techdocs/src/reader/transformers/sanitizeDOM/tags.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/techdocs/src/reader/transformers/sanitizeDOM/tags.ts b/plugins/techdocs/src/reader/transformers/sanitizeDOM/tags.ts index 094c82b5ff..9bb641c09d 100644 --- a/plugins/techdocs/src/reader/transformers/sanitizeDOM/tags.ts +++ b/plugins/techdocs/src/reader/transformers/sanitizeDOM/tags.ts @@ -153,7 +153,6 @@ export const svg = [ 'mask', 'metadata', 'mpath', - 'object', 'path', 'pattern', 'polygon', From 33f6e986858b8620a30b415638b911c589a008ae Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 31 May 2021 17:51:33 +0200 Subject: [PATCH 007/102] More explicit tests, and tidier helper. Signed-off-by: Eric Peterson --- .../src/stages/publish/helpers.test.ts | 6 ++++ .../src/stages/publish/helpers.ts | 29 ++++++++++++------- .../src/stages/publish/local.ts | 8 ++--- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/helpers.test.ts b/packages/techdocs-common/src/stages/publish/helpers.test.ts index d5af98e7e1..7eb591bdb2 100644 --- a/packages/techdocs-common/src/stages/publish/helpers.test.ts +++ b/packages/techdocs-common/src/stages/publish/helpers.test.ts @@ -21,11 +21,17 @@ import { getFileTreeRecursively, getHeadersForFileExtension } from './helpers'; describe('getHeadersForFileExtension', () => { const correctMapOfExtensions = [ ['.html', 'text/plain; charset=utf-8'], + ['.htm', 'text/plain; charset=utf-8'], + ['.HTML', 'text/plain; charset=utf-8'], + ['.dhtml', 'text/plain; charset=utf-8'], + ['.xhtml', 'text/plain; charset=utf-8'], + ['.xml', 'text/plain; charset=utf-8'], ['.css', 'text/css; charset=utf-8'], ['.png', 'image/png'], ['.jpg', 'image/jpeg'], ['.jpeg', 'image/jpeg'], ['.svg', 'text/plain; charset=utf-8'], + ['.SVG', 'text/plain; charset=utf-8'], ['.json', 'application/json; charset=utf-8'], ['.this-in-not-an-extension', 'text/plain; charset=utf-8'], ]; diff --git a/packages/techdocs-common/src/stages/publish/helpers.ts b/packages/techdocs-common/src/stages/publish/helpers.ts index 3ca424055f..22da949aec 100644 --- a/packages/techdocs-common/src/stages/publish/helpers.ts +++ b/packages/techdocs-common/src/stages/publish/helpers.ts @@ -16,6 +16,22 @@ import mime from 'mime-types'; import recursiveReadDir from 'recursive-readdir'; +/** + * Helper to get the expected content-type for a given file extension. Also + * takes XSS mitigation into account. + */ +const getContentTypeForExtension = (ext: string): string => { + const defaultContentType = 'text/plain; charset=utf-8'; + + // Prevent sanitization bypass by preventing browsers from directly rendering + // the contents of untrusted files. + if (ext.match(/htm|xml|svg/i)) { + return defaultContentType; + } + + return mime.contentType(ext) || defaultContentType; +}; + export type responseHeadersType = { 'Content-Type': string; }; @@ -28,18 +44,9 @@ export type responseHeadersType = { export const getHeadersForFileExtension = ( fileExtension: string, ): responseHeadersType => { - const headerType = { - 'Content-Type': - mime.contentType(fileExtension) || 'text/plain; charset=utf-8', + return { + 'Content-Type': getContentTypeForExtension(fileExtension), }; - - // Prevent sanitization bypass by preventing browers from directly rendering - // the contents of untrusted content. - if (headerType['Content-Type'].match(/html|xml/)) { - headerType['Content-Type'] = 'text/plain; charset=utf-8'; - } - - return headerType; }; /** diff --git a/packages/techdocs-common/src/stages/publish/local.ts b/packages/techdocs-common/src/stages/publish/local.ts index 036053627b..bf49c5b926 100644 --- a/packages/techdocs-common/src/stages/publish/local.ts +++ b/packages/techdocs-common/src/stages/publish/local.ts @@ -137,10 +137,10 @@ export class LocalPublish implements PublisherBase { // Handle content-type header the same as all other publishers. setHeaders: (res, filePath) => { const fileExtension = path.extname(filePath); - const { 'Content-Type': header } = getHeadersForFileExtension( - fileExtension, - ); - res.setHeader('Content-Type', header); + const headers = getHeadersForFileExtension(fileExtension); + for (const [header, value] of Object.entries(headers)) { + res.setHeader(header, value); + } }, }); } From aad98c544e59369901fe9e0a85f6357644dceb5c Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 31 May 2021 18:01:38 +0200 Subject: [PATCH 008/102] Initial changeset. Signed-off-by: Eric Peterson --- .changeset/techdocs-fresh-and-clean.md | 6 ++++++ .github/styles/vocab.txt | 1 + 2 files changed, 7 insertions(+) create mode 100644 .changeset/techdocs-fresh-and-clean.md diff --git a/.changeset/techdocs-fresh-and-clean.md b/.changeset/techdocs-fresh-and-clean.md new file mode 100644 index 0000000000..52d9988c36 --- /dev/null +++ b/.changeset/techdocs-fresh-and-clean.md @@ -0,0 +1,6 @@ +--- +'@backstage/techdocs-common': patch +'@backstage/plugin-techdocs': patch +--- + +Fixes multiple XSS and sanitization bypass vulnerabilities in TechDocs. For details, see [ link to security advisories here ] ... diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index b8f028a2ae..d91b840292 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -210,6 +210,7 @@ rst rsync ruleset sam +sanitization scaffolded scaffolder Scaffolder From 84160313e737236047a7f5bc97513faa6200c679 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 1 Jun 2021 21:13:50 +0200 Subject: [PATCH 009/102] cli: mark create-github-app as ready for use Signed-off-by: Patrik Oldsberg --- .changeset/cool-poems-train.md | 5 +++++ packages/cli/src/commands/index.ts | 6 ++---- 2 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 .changeset/cool-poems-train.md diff --git a/.changeset/cool-poems-train.md b/.changeset/cool-poems-train.md new file mode 100644 index 0000000000..fb4ba6621e --- /dev/null +++ b/.changeset/cool-poems-train.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Mark the `create-github-app` command as ready for use and reveal it in the command list. diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index ae39941106..892c5459da 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -225,10 +225,8 @@ export function registerCommands(program: CommanderStatic) { .action(lazy(() => import('./buildWorkspace').then(m => m.default))); program - .command('create-github-app ', { hidden: true }) - .description( - 'Create new GitHub App in your organization. This command is experimental and may change in the future.', - ) + .command('create-github-app ') + .description('Create new GitHub App in your organization.') .action(lazy(() => import('./create-github-app').then(m => m.default))); } From 054bcd029ce4281f4e2b36fa8c5144d17c408cad Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 1 Jun 2021 21:20:11 +0200 Subject: [PATCH 010/102] cli: deprecate backend:build-image Signed-off-by: Patrik Oldsberg --- .changeset/strong-mails-drum.md | 5 +++++ packages/cli/src/commands/backend/buildImage.ts | 10 ++++++++++ packages/cli/src/commands/index.ts | 6 +----- 3 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 .changeset/strong-mails-drum.md diff --git a/.changeset/strong-mails-drum.md b/.changeset/strong-mails-drum.md new file mode 100644 index 0000000000..e1a069e953 --- /dev/null +++ b/.changeset/strong-mails-drum.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Deprecated the `backend:build-image` command, pointing to the newer `backend:bundle` command. diff --git a/packages/cli/src/commands/backend/buildImage.ts b/packages/cli/src/commands/backend/buildImage.ts index 654f51cd10..8434017cd9 100644 --- a/packages/cli/src/commands/backend/buildImage.ts +++ b/packages/cli/src/commands/backend/buildImage.ts @@ -15,6 +15,7 @@ */ import { Command } from 'commander'; +import { yellow } from 'chalk'; import fs from 'fs-extra'; import { join as joinPath, relative as relativePath } from 'path'; import { createDistWorkspace } from '../../lib/packager'; @@ -31,6 +32,15 @@ export default async (cmd: Command) => { return; } + console.warn( + yellow(` +The backend:build-image command is deprecated and will be removed in the future. +Please use the backend:bundle command instead along with your own Docker setup. + + https://backstage.io/docs/deployment/docker +`), + ); + const pkgPath = paths.resolveTarget(PKG_PATH); const pkg = await fs.readJson(pkgPath); const appConfigs = await findAppConfigs(); diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 892c5459da..431659a972 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -60,11 +60,7 @@ export function registerCommands(program: CommanderStatic) { .helpOption(', --backstage-cli-help') // Let docker handle --help .option('--build', 'Build packages before packing them into the image') .description( - // TODO: Add example use cases in Backstage documentation. - // For example, if a $NPM_TOKEN needs to be exposed, run `backend:build-image --secret - // id=NPM_TOKEN,src=/NPM_TOKEN.txt`. - 'Bundles the package into a docker image. All extra args are forwarded to ' + - '`docker image build`.', + 'Bundles the package into a docker image. This command is deprecated and will be removed.', ) .action(lazy(() => import('./backend/buildImage').then(m => m.default))); From e7a5a347403fb0cf315a8a48f214e9740ff9f958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 1 Jun 2021 16:17:25 +0200 Subject: [PATCH 011/102] Apply more deliberate validation of the entities throughout processing, making sure that errors bubble out to the `state` structure in the end. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/slimy-toys-fetch.md | 5 + .../next/DefaultCatalogProcessingEngine.ts | 31 ++- .../DefaultCatalogProcessingOrchestrator.ts | 187 ++++++++++++------ plugins/catalog-backend/src/next/Stitcher.ts | 2 +- .../database/DefaultProcessingDatabase.ts | 32 ++- plugins/catalog-backend/src/next/types.ts | 4 +- 6 files changed, 179 insertions(+), 82 deletions(-) create mode 100644 .changeset/slimy-toys-fetch.md diff --git a/.changeset/slimy-toys-fetch.md b/.changeset/slimy-toys-fetch.md new file mode 100644 index 0000000000..ac98de5325 --- /dev/null +++ b/.changeset/slimy-toys-fetch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Only validate the envelope for emitted entities, and defer full validation to when they get processed later on. diff --git a/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts b/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts index 4dda881963..8bc399c171 100644 --- a/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts +++ b/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { stringifyEntityRef } from '@backstage/catalog-model'; +import { + Entity, + entityEnvelopeSchemaValidator, + stringifyEntityRef, +} from '@backstage/catalog-model'; import { serializeError } from '@backstage/errors'; import { Logger } from 'winston'; import { ProcessingDatabase } from './database/types'; @@ -28,6 +32,8 @@ import { } from './types'; class Connection implements EntityProviderConnection { + readonly validateEntityEnvelope = entityEnvelopeSchemaValidator(); + constructor( private readonly config: { processingDatabase: ProcessingDatabase; @@ -37,7 +43,9 @@ class Connection implements EntityProviderConnection { async applyMutation(mutation: EntityProviderMutation): Promise { const db = this.config.processingDatabase; + if (mutation.type === 'full') { + this.check(mutation.entities); await db.transaction(async tx => { await db.replaceUnprocessedEntities(tx, { sourceKey: this.config.id, @@ -47,6 +55,9 @@ class Connection implements EntityProviderConnection { }); return; } + + this.check(mutation.added); + this.check(mutation.removed); await db.transaction(async tx => { await db.replaceUnprocessedEntities(tx, { sourceKey: this.config.id, @@ -56,6 +67,16 @@ class Connection implements EntityProviderConnection { }); }); } + + private check(entities: Entity[]) { + for (const entity of entities) { + try { + this.validateEntityEnvelope(entity); + } catch (e) { + throw new TypeError(`Malformed entity envelope, ${e}`); + } + } + } } export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine { @@ -113,14 +134,18 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine { // TODO: replace Promise.all with something more sophisticated for parallel processing. await Promise.all( items.map(async item => { - const { id, state, unprocessedEntity } = item; + const { id, state, unprocessedEntity, entityRef } = item; const result = await this.orchestrator.process({ entity: unprocessedEntity, state, }); for (const error of result.errors) { - this.logger.warn(error.message); + // TODO(freben): Try to extract the location out of the unprocessed + // entity and add as meta to the log lines + this.logger.warn(error.message, { + entity: entityRef, + }); } const errorsString = JSON.stringify( result.errors.map(e => serializeError(e)), diff --git a/plugins/catalog-backend/src/next/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/next/DefaultCatalogProcessingOrchestrator.ts index c2c001943c..917ac11cfb 100644 --- a/plugins/catalog-backend/src/next/DefaultCatalogProcessingOrchestrator.ts +++ b/plugins/catalog-backend/src/next/DefaultCatalogProcessingOrchestrator.ts @@ -16,37 +16,51 @@ import { Entity, - EntityRelationSpec, - stringifyEntityRef, - LOCATION_ANNOTATION, - LocationSpec, - LocationEntity, + entityEnvelopeSchemaValidator, EntityPolicy, + EntityRelationSpec, + entitySchemaValidator, + LocationEntity, + LocationSpec, + LOCATION_ANNOTATION, ORIGIN_LOCATION_ANNOTATION, - stringifyLocationReference, parseLocationReference, + stringifyEntityRef, + stringifyLocationReference, } from '@backstage/catalog-model'; +import { ConflictError, InputError } from '@backstage/errors'; +import { ScmIntegrationRegistry } from '@backstage/integration'; +import path from 'path'; +import { Logger } from 'winston'; import { CatalogProcessor, CatalogProcessorParser, CatalogProcessorResult, } from '../ingestion/processors'; +import * as results from '../ingestion/processors/results'; import { CatalogProcessingOrchestrator, EntityProcessingRequest, EntityProcessingResult, } from './types'; -import { Logger } from 'winston'; -import { InputError } from '@backstage/errors'; import { locationSpecToLocationEntity } from './util'; -import path from 'path'; -import * as results from '../ingestion/processors/results'; -import { ScmIntegrationRegistry } from '@backstage/integration'; + +const validateEntity = entitySchemaValidator(); +const validateEntityEnvelope = entityEnvelopeSchemaValidator(); function isLocationEntity(entity: Entity): entity is LocationEntity { return entity.kind === 'Location'; } +function getEntityLocationRef(entity: Entity): string { + const ref = entity.metadata.annotations?.[LOCATION_ANNOTATION]; + if (!ref) { + const entityRef = stringifyEntityRef(entity); + throw new InputError(`Entity '${entityRef}' does not have a location`); + } + return ref; +} + function getEntityOriginLocationRef(entity: Entity): string { const ref = entity.metadata.annotations?.[ORIGIN_LOCATION_ANNOTATION]; if (!ref) { @@ -104,23 +118,33 @@ export class DefaultCatalogProcessingOrchestrator private async processSingleEntity( unprocessedEntity: Entity, ): Promise { - // TODO: validate that this doesn't change during processing - const entityRef = stringifyEntityRef(unprocessedEntity); - // TODO: which one do we actually use here? source-location? - maybe probably doesn't exist yet? - const locationRef = - unprocessedEntity.metadata?.annotations?.[LOCATION_ANNOTATION]; - if (!locationRef) { - throw new InputError(`Entity '${entityRef}' does not have a location`); - } - const location = parseLocationReference(locationRef); - const originLocation = parseLocationReference( - getEntityOriginLocationRef(unprocessedEntity), - ); - const emitter = createEmitter(this.options.logger, unprocessedEntity); try { + // This will be checked and mutated step by step below + let entity: Entity = unprocessedEntity; + + // NOTE: At this early point, we can only rely on the envelope having to + // be valid; full entity + kind validation happens after the (potentially + // mutative) pre-steps. This means that the code below can't make a lot + // of assumptions about the data despite it using the Entity type. + try { + validateEntityEnvelope(entity); + } catch (e) { + throw new InputError( + `Entity envelope failed validation before processing`, + e, + ); + } + + const entityRef = stringifyEntityRef(entity); + // TODO: which one do we actually use here? source-location? - maybe probably doesn't exist yet? + const locationRef = getEntityLocationRef(entity); + const location = parseLocationReference(locationRef); + const originLocation = parseLocationReference( + getEntityOriginLocationRef(entity), + ); + // Pre-process phase, used to populate entities with data that is required during main processing step - let entity = unprocessedEntity; for (const processor of this.options.processors) { if (processor.preProcessEntity) { try { @@ -131,48 +155,64 @@ export class DefaultCatalogProcessingOrchestrator originLocation, ); } catch (e) { - throw new Error( - `Processor ${processor.constructor.name} threw an error while preprocessing entity ${entityRef} at ${locationRef}, ${e}`, + throw new InputError( + `Processor ${processor.constructor.name} threw an error while preprocessing`, + e, ); } } } // Enforce entity policies making sure that entities conform to a general schema - let policyEnforcedEntity; + let policyEnforcedEntity: Entity | undefined; try { policyEnforcedEntity = await this.options.policy.enforce(entity); } catch (e) { - throw new InputError( - `Policy check failed while analyzing entity ${entityRef} at ${locationRef}, ${e}`, - ); + throw new InputError('Policy check failed', e); } if (!policyEnforcedEntity) { - throw new Error( - `Policy unexpectedly returned no data while analyzing entity ${entityRef} at ${locationRef}`, - ); + throw new Error('Policy unexpectedly returned no data'); } entity = policyEnforcedEntity; + // Validate that the end result is a valid Entity at all + try { + validateEntity(entity); + } catch (e) { + throw new ConflictError( + `Entity envelope failed validation after preprocessing`, + e, + ); + } + // Validate the given entity kind against its schema - let handled = false; + let didValidate = false; for (const processor of this.options.processors) { if (processor.validateEntityKind) { try { - handled = await processor.validateEntityKind(entity); - if (handled) { + didValidate = await processor.validateEntityKind(entity); + if (didValidate) { break; } } catch (e) { throw new InputError( - `Processor ${processor.constructor.name} threw an error while validating the entity ${entityRef} at ${locationRef}, ${e}`, + `Processor ${processor.constructor.name} threw an error while validating the entity`, + e, ); } } } - if (!handled) { + if (!didValidate) { throw new InputError( - `No processor recognized the entity ${entityRef} at ${locationRef}`, + 'No processor recognized the entity as valid, possibly caused by a foreign kind or apiVersion', + ); + } + + // Double check that none of the previous steps tried to change something + // related to the entity ref, which would break downstream + if (stringifyEntityRef(entity) !== entityRef) { + throw new ConflictError( + 'Fatal: The entity kind, namespace, or name changed during processing', ); } @@ -204,6 +244,7 @@ export class DefaultCatalogProcessingOrchestrator maybeRelativeTarget, ); + let didRead = false; for (const processor of this.options.processors) { if (processor.readLocation) { try { @@ -218,15 +259,22 @@ export class DefaultCatalogProcessingOrchestrator this.options.parser, ); if (read) { + didRead = true; break; } } catch (e) { - throw new Error( - `Processor ${processor.constructor.name} threw an error while postprocessing entity ${entityRef} at ${locationRef}, ${e}`, + throw new InputError( + `Processor ${processor.constructor.name} threw an error while reading ${type}:${target}`, + e, ); } } } + if (!didRead) { + throw new InputError( + `No processor was able to handle reading of ${type}:${target}`, + ); + } } } @@ -240,8 +288,9 @@ export class DefaultCatalogProcessingOrchestrator emitter.emit, ); } catch (e) { - throw new Error( - `Processor ${processor.constructor.name} threw an error while postprocessing entity ${entityRef} at ${locationRef}, ${e}`, + throw new InputError( + `Processor ${processor.constructor.name} threw an error while postprocessing`, + e, ); } } @@ -255,7 +304,10 @@ export class DefaultCatalogProcessingOrchestrator }; } catch (error) { this.options.logger.warn(error.message); - return { ok: false, errors: emitter.results().errors.concat(error) }; + return { + ok: false, + errors: emitter.results().errors.concat(error), + }; } } } @@ -276,23 +328,40 @@ function createEmitter(logger: Logger, parentEntity: Entity) { ); return; } + if (i.type === 'entity') { - // TODO(freben): Perform the most basic validation here - // (apiVersion, kind, metadata, metadata.name, metadata.namespace, spec) + let entity: Entity; + try { + entity = validateEntityEnvelope(i.entity); + } catch (e) { + logger.debug(`Envelope validation failed at ${i.location}, ${e}`); + errors.push(e); + return; + } - const originLocation = getEntityOriginLocationRef(parentEntity); - - deferredEntities.push({ - ...i.entity, - metadata: { - ...i.entity.metadata, - annotations: { - ...i.entity.metadata.annotations, - [ORIGIN_LOCATION_ANNOTATION]: originLocation, - [LOCATION_ANNOTATION]: stringifyLocationReference(i.location), + // Note that at this point, we have only validated the envelope part of + // the entity data. Annotations are not part of that, so we have to be + // defensive. If the annotations were malformed (e.g. were not a valid + // object), we just skip over this step and let the full entity + // validation at the next step of processing catch that. + const annotations = entity.metadata.annotations || {}; + if (typeof annotations === 'object' && !Array.isArray(annotations)) { + const originLocation = getEntityOriginLocationRef(parentEntity); + const location = stringifyLocationReference(i.location); + entity = { + ...entity, + metadata: { + ...entity.metadata, + annotations: { + ...annotations, + [ORIGIN_LOCATION_ANNOTATION]: originLocation, + [LOCATION_ANNOTATION]: location, + }, }, - }, - }); + }; + } + + deferredEntities.push(entity); } else if (i.type === 'location') { deferredEntities.push( locationSpecToLocationEntity(i.location, parentEntity), diff --git a/plugins/catalog-backend/src/next/Stitcher.ts b/plugins/catalog-backend/src/next/Stitcher.ts index 3090c4a971..7a2eeac594 100644 --- a/plugins/catalog-backend/src/next/Stitcher.ts +++ b/plugins/catalog-backend/src/next/Stitcher.ts @@ -176,7 +176,7 @@ export class Stitcher { statusItems = parsedErrors.map(e => ({ type: ENTITY_STATUS_CATALOG_PROCESSING_TYPE, level: 'error', - message: e.toString(), + message: `${e.name}: ${e.message}`, error: e, })); } diff --git a/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.ts b/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.ts index cd3ddf037b..3150480108 100644 --- a/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.ts +++ b/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.ts @@ -14,24 +14,22 @@ * limitations under the License. */ -import { ConflictError, NotFoundError } from '@backstage/errors'; -import { stringifyEntityRef, Entity } from '@backstage/catalog-model'; -import { Knex } from 'knex'; -import { Transaction } from '../../database'; -import lodash from 'lodash'; - -import { - ProcessingDatabase, - AddUnprocessedEntitiesOptions, - UpdateProcessedEntityOptions, - GetProcessableEntitiesResult, - ReplaceUnprocessedEntitiesOptions, - RefreshStateItem, -} from './types'; -import type { Logger } from 'winston'; - -import { v4 as uuid } from 'uuid'; +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; import { JsonObject } from '@backstage/config'; +import { ConflictError, NotFoundError } from '@backstage/errors'; +import { Knex } from 'knex'; +import lodash from 'lodash'; +import { v4 as uuid } from 'uuid'; +import type { Logger } from 'winston'; +import { Transaction } from '../../database'; +import { + AddUnprocessedEntitiesOptions, + GetProcessableEntitiesResult, + ProcessingDatabase, + RefreshStateItem, + ReplaceUnprocessedEntitiesOptions, + UpdateProcessedEntityOptions, +} from './types'; export type DbRefreshStateRow = { entity_id: string; diff --git a/plugins/catalog-backend/src/next/types.ts b/plugins/catalog-backend/src/next/types.ts index fbc243ce69..a5d85feb46 100644 --- a/plugins/catalog-backend/src/next/types.ts +++ b/plugins/catalog-backend/src/next/types.ts @@ -16,9 +16,9 @@ import { Entity, - LocationSpec, - Location, EntityRelationSpec, + Location, + LocationSpec, } from '@backstage/catalog-model'; import { JsonObject } from '@backstage/config'; From 9b4b4050958b822d8647190d0a38a101fd8f84b2 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 2 Jun 2021 17:35:35 +0200 Subject: [PATCH 012/102] feat: reworking the techRadar plugin to support providing your own TechRadarApi to override how the data get's into your plugin Signed-off-by: blam --- plugins/tech-radar/README.md | 1 - plugins/tech-radar/src/api.ts | 22 +++++------ .../src/components/RadarComponent.tsx | 37 ++++++++----------- plugins/tech-radar/src/plugin.ts | 5 +++ .../src/{sampleData.ts => sample.ts} | 21 +++++++---- 5 files changed, 44 insertions(+), 42 deletions(-) rename plugins/tech-radar/src/{sampleData.ts => sample.ts} (94%) diff --git a/plugins/tech-radar/README.md b/plugins/tech-radar/README.md index 814ab6cba5..149ab54840 100644 --- a/plugins/tech-radar/README.md +++ b/plugins/tech-radar/README.md @@ -60,7 +60,6 @@ export type TechRadarPageProps = TechRadarComponentProps & { export interface TechRadarPageProps { width: number; height: number; - getData?: () => Promise; svgProps?: object; } ``` diff --git a/plugins/tech-radar/src/api.ts b/plugins/tech-radar/src/api.ts index eca80420bf..6f2a1a5cb1 100644 --- a/plugins/tech-radar/src/api.ts +++ b/plugins/tech-radar/src/api.ts @@ -15,6 +15,17 @@ */ import { MovedState } from './utils/types'; +import { createApiRef } from '@backstage/core'; + +export const techRadarApiRef = createApiRef({ + id: 'plugin.techradar.service', + description: 'Used to populate data in the TechRadar plugin', +}); + +export interface TechRadarApi { + // Loads the TechRadar response data to pass through to the TechRadar component + load: () => Promise; +} /** * Types related to the Radar's visualization. @@ -65,16 +76,5 @@ export interface TechRadarLoaderResponse { export interface TechRadarComponentProps { width: number; height: number; - getData?: () => Promise; svgProps?: object; } - -/** - * Set up the Radar as a Backstage plugin. - */ - -export interface TechRadarApi extends TechRadarComponentProps { - title?: string; - subtitle?: string; - pageTitle?: string; -} diff --git a/plugins/tech-radar/src/components/RadarComponent.tsx b/plugins/tech-radar/src/components/RadarComponent.tsx index a6e033a08e..39746e48fe 100644 --- a/plugins/tech-radar/src/components/RadarComponent.tsx +++ b/plugins/tech-radar/src/components/RadarComponent.tsx @@ -15,38 +15,35 @@ */ import React, { useEffect } from 'react'; -import { Progress, useApi, errorApiRef, ErrorApi } from '@backstage/core'; +import { Progress, useApi, errorApiRef } from '@backstage/core'; import { useAsync } from 'react-use'; import Radar from '../components/Radar'; -import { TechRadarComponentProps, TechRadarLoaderResponse } from '../api'; -import getSampleData from '../sampleData'; +import { + techRadarApiRef, + TechRadarComponentProps, + TechRadarLoaderResponse, +} from '../api'; import { Entry } from '../utils/types'; -const useTechRadarLoader = (props: TechRadarComponentProps) => { - const errorApi = useApi(errorApiRef); +const useTechRadarLoader = () => { + const errorApi = useApi(errorApiRef); + const techRadarApi = useApi(techRadarApiRef); - const { getData } = props; - - const state = useAsync(async () => { - if (getData) { - const response: TechRadarLoaderResponse = await getData(); - return response; - } - return undefined; - }, [getData, errorApi]); + const { error, value, loading } = useAsync(async () => techRadarApi.load(), [ + techRadarApi, + ]); useEffect(() => { - const { error } = state; if (error) { errorApi.post(error); } - }, [errorApi, state]); + }, [error, errorApi]); - return state; + return { loading, value, error }; }; const RadarComponent = (props: TechRadarComponentProps): JSX.Element => { - const { loading, error, value: data } = useTechRadarLoader(props); + const { loading, error, value: data } = useTechRadarLoader(); const mapToEntries = ( loaderResponse: TechRadarLoaderResponse | undefined, @@ -89,8 +86,4 @@ const RadarComponent = (props: TechRadarComponentProps): JSX.Element => { ); }; -RadarComponent.defaultProps = { - getData: getSampleData, -}; - export default RadarComponent; diff --git a/plugins/tech-radar/src/plugin.ts b/plugins/tech-radar/src/plugin.ts index 63af1f390f..e128600094 100644 --- a/plugins/tech-radar/src/plugin.ts +++ b/plugins/tech-radar/src/plugin.ts @@ -18,8 +18,12 @@ import { createPlugin, createRouteRef, createRoutableExtension, + createApiFactory, } from '@backstage/core'; +import { techRadarApiRef } from './api'; +import { SampleTechRadarApi } from './sample'; + const rootRouteRef = createRouteRef({ title: 'Tech Radar', }); @@ -29,6 +33,7 @@ export const techRadarPlugin = createPlugin({ routes: { root: rootRouteRef, }, + apis: [createApiFactory(techRadarApiRef, new SampleTechRadarApi())], }); export const TechRadarPage = techRadarPlugin.provide( diff --git a/plugins/tech-radar/src/sampleData.ts b/plugins/tech-radar/src/sample.ts similarity index 94% rename from plugins/tech-radar/src/sampleData.ts rename to plugins/tech-radar/src/sample.ts index 70b9303f39..ff895bcdb7 100644 --- a/plugins/tech-radar/src/sampleData.ts +++ b/plugins/tech-radar/src/sample.ts @@ -19,6 +19,7 @@ import { RadarQuadrant, RadarEntry, TechRadarLoaderResponse, + TechRadarApi, } from './api'; const rings = new Array(); @@ -164,15 +165,19 @@ entries.push({ ], url: '#', key: 'github-actions', - id: 'github-actions', - title: 'GitHub Actions', + id: 'github-actiosns', + title: 'GitHub Acssstions', quadrant: 'infrastructure', }); -export default function getSampleData(): Promise { - return Promise.resolve({ - rings, - quadrants, - entries, - }); +export const mock: TechRadarLoaderResponse = { + entries, + quadrants, + rings, +}; + +export class SampleTechRadarApi implements TechRadarApi { + async load() { + return mock; + } } From 2d1bc6e6d4ec85bb9d351809ca1ba9ab5fabbbd0 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 2 Jun 2021 19:01:16 +0200 Subject: [PATCH 013/102] Remove reference to advisories. We'll add manually to docs after they are published. Signed-off-by: Eric Peterson --- .changeset/techdocs-fresh-and-clean.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/techdocs-fresh-and-clean.md b/.changeset/techdocs-fresh-and-clean.md index 52d9988c36..615294c3c4 100644 --- a/.changeset/techdocs-fresh-and-clean.md +++ b/.changeset/techdocs-fresh-and-clean.md @@ -3,4 +3,4 @@ '@backstage/plugin-techdocs': patch --- -Fixes multiple XSS and sanitization bypass vulnerabilities in TechDocs. For details, see [ link to security advisories here ] ... +Fixes multiple XSS and sanitization bypass vulnerabilities in TechDocs. From d317e1d4d9e5ced8205710e599064548969d1641 Mon Sep 17 00:00:00 2001 From: Nikhil Unni Date: Wed, 2 Jun 2021 13:35:24 -0400 Subject: [PATCH 014/102] Add Cortex Plugin to Marketplace Signed-off-by: Nikhil Unni --- microsite/data/plugins/cortex.yaml | 13 +++++++++++++ microsite/static/img/cortex.png | Bin 0 -> 35492 bytes 2 files changed, 13 insertions(+) create mode 100644 microsite/data/plugins/cortex.yaml create mode 100644 microsite/static/img/cortex.png diff --git a/microsite/data/plugins/cortex.yaml b/microsite/data/plugins/cortex.yaml new file mode 100644 index 0000000000..d377ec39f2 --- /dev/null +++ b/microsite/data/plugins/cortex.yaml @@ -0,0 +1,13 @@ +--- +title: Service Quality Scorecards +author: Cortex +authorUrl: https://github.com/cortexapps +category: Monitoring +description: Grade the quality of your Backstage services using Scorecards. Automate production readiness, migrations, security audits, and more with CQL (Cortex Query Language). +documentation: +iconUrl: img/cortex.png +npmPackageName: '@cortexapps/backstage-plugin' +tags: + - web + - monitoring + - sre diff --git a/microsite/static/img/cortex.png b/microsite/static/img/cortex.png new file mode 100644 index 0000000000000000000000000000000000000000..6a45d0ca06f601ca49ddaaacc90fec8e940cc2d0 GIT binary patch literal 35492 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

1#avJ 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

5TKU;g`=gq-8-b zZ2^e|y*J?)?0p_7l$AIYgxmDA3C z{=ZN-{Z{;?t+~99uO@NICV;r#dg}XO+hY1Xzv)CGujzENpI7ipM>1eiIIHnkUB2Eq z@ZL>fl5T*9O-c4jyhIY}5~vbJvEqc9gVFU%7chCMM@cqpqLYv~B$3`xN`YBmd-Vl2d{j z0yTW!2c7Is{@jbF@z88as6^a9+<6dml$4F?`wG|7KD2}hTwW9oD?f9PDdjX3T|C|E z4c%_4hPpnRQvV^562B)`SGD}i4*}f65((XKzuOA`uHGu&z|a$xjwyw1$~47*G;o?&UmH1%P&t<-05{YfW0y0qDM)Z9FwB{UEK%J;Eif5r2|KJ%UJbe z(n;10s{uv%r!ntN2M(yVt+9=;D_K0xc$n^%Jv^kcPQ$TKwZ0Nui@%Q-1GmVNRR0<9 z+y9*9ZoUdbHkyK{3!@Prkt5kRWx@ai?_rm6CsgzaPnh3{0i3XjPb&c$>ojhw6giu8 za=pB}r-i-h$!Q)VbN*l1&*MK}pv5al)d|54+avLxV_d}R3o4$W`@evb>H2RGvvqD7 z)&jwtN^64nV7k_a#&MY6t@D&Vyc>woIJT8w?%@VCGP%%dD>epP@&T+dekp<65n1 zC_`YK008PqfS0yep)PFGUvOV8E1X(nH;sNn0^MzbM`Wd}quya#lThNsFq3yKuc!%Q zkeJAebl2$D8{Sb5RQX+bFw0nTD zM%40SkFb++N=1i)T#({~1357Cq1t9plK9%eD=B z;|2Il;=+F_At3Tx@TjJ6$-LTwXt>bBpQCL2ipStV7R<%a?FZ6I zH@`lV-t{SeYN^$OwsflbZ-eh+0@PTXC~6}n>oTaB)n?P$`i+R`W^)}W4iLQ{wDZs# zS)HB$w(76o1P@lJS>I?l{RT7iP;~YUp33oZXJfzM{zld-i9uHR%9c66*ZZ$G>F^k# zL<$&7Su+bI0QU-3RO3%vU+)S02-=`aI;#$#Ovb=bp~-UFbh@gH2>KGB~3`|$L$ zg|SsP$AF;k7h+)Oo@^I#=93Cn5rY8Z6eFs^?u4!?3w!6WAPU106(Yd*cZE@NUPry~ zqo(a|?jh9b%`F#2*b>qr+g|^f$o_lquqa#JW=^NKjUVQC!>xc`N8!Rgs`KXR; zo$#}aZq8t9FkeUz;eCgbg?;MR56(sR-Irz2wO+;0x_DW9XU7Xh z0RL7NuJd4KR6JT` z4!rbOfw5EFLn|1?_Ig4V7s9jJ!8y4U@03Z|yf(d3Sdwl$HDoy3H{Tz*V%7vs>*scq zCf&}PebaGZsXOsx1<&Nc9!K7e5#g9TSt*X2bCiThDx@X*dip2y8Ax|7ESa?~2oC|@ zu+M2~$a3v%9Ju~Y6+@3z#_(3{?($KeHARxKkw9qoD>rXlxS|9l4-QL*$QOi8V9Xt_ zS7`f4p592FXrR{1F4jcz|CaqDo$E#Npof2LMwR%vafyV1wG3=>A;9pq@@sw{xb*dVr-?$q2CKyN;w0l2sX0npk($A&_U-0MR!dm3QI8eYb8w zt}X^p9<3o>%kf1k$hlBQIHwFKbOq+6p1#%ZSqpg<1+*IvHYd*ICqG+mDEDhEmZyCK z=ey2PYI#j;AXGuA{CzB1oYbR*QZY9JP;>Ggqzh@TiSiK0nO`OVN?{Z z!iS0Z>b;~Zx~blwy|l8eyi3`&%DVtbYI5Lh2qCbJGK`_8e=cn$E^*K3H71Jp{V+Pg z<4U*GlN@%x27`qe;^Q|2(F`4AW7PHtSKWO-#M+p50nauv%Q%L$)1c1o6Ig4pi&P%| z>ox!D)V+#ZSj<7C5m0ii#K|Qcf=&58;X?xSO^UJ3%boc~(>6yPx!R?8odxG|UHAr% z#S8w3CEM7Me#Aulhx|8~15XN=JwgnD`Pd9BexW|dKGTGVt?OSM$Ntsipk<;4S zuv!_Vn9Bnf#u({+k!`9D?7DFn2y2syKo49HA)2W*!H#eE_WSBMLqpf^0b+L>KT&b1 zp(LCKroHr+-@P$2wp6U51XgbQ^3a0=l;zMdS}XG zB?%7s0^O1(sb$1Z4yiqyc!*hnf8Rn+6rMSTl6fOBAU0eoJW_PJw=wiZU;4v?b5}74 z12b(4RltkggMzP=e19?|%mZcsZ?YumeImB=itZsE^3L%TvExb6yy6E7Hxa^g_YRY8 z6XFueCu1{Amj$Lyu4^OEBoil%#|po{@aKF!VAme-?U&FUds5r&IJQu^sE){m)3m)q zNpq44-2z8dRt{7KEEUH)8BiaqN@$f!giUrucASY5u`5yNzF4pvZU`0c#WV_jXcR3o z^H+#&;-2bMtW{=*J^E4MfKf~Y0`xj1WhNTn^i6yk3EU_Cw zrpkdeuQ&%k{Qd8g{bjQ|s|e_xC|CAP-FfwYm6I=7?I|Y-2sRShM=e3l8d#My}EfdzY#&s5f@+s&!C2j zdkgmXrWW=|2i^=+qU*m1mZzp;^ zV$78Mww3OzVZk;kh(Jkm(|AvC;e&iB=V~V6k8f#)W6pKDPd>CuxpDbiHmgetE$ep1O~j>%Tp^&4){SUKbV#DN>CTy~l^aCCDxeBBJT) zbS@6eK2$uWLw;>0cK1biDnRNcFr}VTgL-qFLpKrB?YZ9D3i9csdb7H3#w8u7{YdE0 zzJp1jpCYH2g+F%UhRZ!9eZqnAAp@4nY_>du~` z?zYTkoYOmec}XW)RQLB!oXOeR&GKJ;AI4ADOxFW~mvy;mY9JL(#XLmb*H|-(qgdK_ zp-O!!!#v}Z%AyyPy>NQKvLwl)h5_IQ2XifU7}kL!VM$08hNwuMl)LV1mY5UMitZ)8 zoPKQXiLH&K9ySu|`2dS*jIxwI@?;5=;0+d8@xhtf=YC9ff4LOkky##iOT!O=B=e5Z zcM2lOCCKadLGUC-EURmG`}+GzPcMV%!z)*c6VAy_H5IoJOP*M1&nK>*b!X>N0MC z1mmyh0O##QsE3AvkIhaIi@xvZjJA}BxVA=To56Tzu?KY2PG-`saJxs-uXR(yL|53M-;d3MJtXgddgaN?8u@|1oOSGaCI*NacY`qxI$Q4!U2 zHV;JhKs2G~SlzaJljbT^^OmQU;-JMfa|dab13l+}H{4}>Bn8)}u+Qzy8A;{vRsq7t zWPoFz=k^+NvH!e9oe;49=?+Fe7%pT2r?))vsO>5U1Q0P>t1BZzjvDVA$J$W~D}=oj zdubi%L}(^O&4;YO7+IW#L&eUdczIPT$+>3mm@*dO0|KX>VPtrl$!(3C^Zt4Gs-0h9 zkw87Vbzc0q86}U~nBTv;D7sbD(QP2jMoNL2IwuCu>#PBaZO}sm(-}}9LDFgoA)H71PpBJ=7XskV#5HxStB%7$0T zl?RK*&_r>14K^AFlRiV4$c{dsbV@jcdeMon;Ds`>qn26*0}ja1^ee2LA}|}Kdp=a1 z4<-FATeWd>MIBLv%3Q&ZVxsIygo0#tET6q~rA_rh7bZ^*6}gsXfIcCB({E}feG(f{1>x7E7)z1+{_55 zR|k>rOX!rXW+cRC)GCEF&B7xro6^vB$R9l6ddetZb+u!w;1 zlb~R5AT)_pLr6C_aEk17Pc?hZHkd8oZ66clp~hJ$&po?flu~2YBlp0Bp+VpO?Wq6zHGlr6b}{?=#{@Yby!|Ti z1jjIHTZtfN_j476I!(8jwArImq2OGZmt{q{x4_co5_F#;4%-%lXmgF+TW`3GV%VMs z3=4XIK#U6a@2MCCQv4sgmZh|ed zNUkIDPDR4bO+9o6=;u3YAY6t$q9`KtRJa1qg4bKO56x^)JQ<2wNiDAe z2fL0yEyXI;>R&4migKm+UDJ*AJ>Gbsxs}??q`wuG(NuWG=X^!S4vBEnWR|N9$9}|3 z{rzu@vGdi+bLk2nODsa@+a#_(j)Ox>XyEyU(SdqbKORBpg%V?_q$t~gXQ8ni+9BUa z{zE5ht;9Cqu-U5L6rKdH5KURayMGF>ESYxo{Rc6K&5d#MU^($c=fsUD8qe=^kT^`l zJqaNSRAJ{=F>d!Uz4%A5OWm)*^B{4CE)5T^z~?%BuffrfHtVWCFa z5Up13>zPawF(P$C<6o?0df1%C-kUP)4*gzO8{Bz+1mtR7Qu+*(1eaGeVbnfeo z5K~0mLYFMh_{-n;hQ80ZCdlGACgHuDK&P7qjg$$EMt7UlM6zvX1RZqcFw;Q6_c3-~ z6M3xfTo(9{n~)jUe*X|BbE&UunQhoZZGDsHCP%|<0Nax{WAq_Tb@6%qC+Wk~0B8oE zUXvpou?FaehuX0o8X_jz&8Re}dA7&7xhsmZ$9mw$X<^)R4X_G&0wO9~qBmf7}^p1yc$L!ZAJ$$n4brvY zu>zubS{o~#@#E3r+3;v&M$UY;yu%(&2JE06P3XFspo}`6 zrnU8Pn67a~&4j~^&|B|(s^;%+`x62J9!hB%3;}`HlQAGi%>`o-Cr~)P{dv|z!!6Lr z9N2FEyWZrl6@lt4n;a-v(oEIN98knD@zg9`Q^W~~IuMSc4kFKJ*clo7m6{@ zQjOz=fS#$B;|2k08`L^p<7Cd-0U&Vwjb)94sR5r?gJ@hhR_6#xh?EVJ1KGS9?&BB+ zmyQ|+JTW=&D{X-Kd-l3d3h0dRP=Dur9G&=J#Idx^#pBF@O@cZ=yukw7RnR=S0je#~ zselRT*Zsa;_TPI1;`G1}Y8w~>c`wHuwImwWIwqKB!((AP6VJ}bX!!TY*nfRc@7VZ_ z8lVsh*wMH&ZNk}Y$kRt|vh`_!2@!GNs#UWA+WrR3b}esHI9+IZ!XQZ7pRv9H$umRW zX668CH*qKDeT~!p_U`#>MWE@HE*1btXm)gFNkceb4Ny345EyfyZFL$;ZLK~3vN!k} z5YVIAc*~4|*aSs=lTtOB1Ho&YQnVSqf^@C;7o*N-h^E1n;~Sr|z9Edz5m9)`hL$-X z=0KcRGfO%SdR92Aw%dDnqif|nje!_#UtF$ZGA5s)x!ot-@Y6Mxkk{VfTo-g-Rbt{UnaG(l6u zL6mvQC^HteN0hE@k1$m$wnwfMJ0n-ZPVI~Gb@kEC^@|>H^i-M#;h3|=tJB`c$p<~F zhOY1S4$gPX|7Ny(>5j&3Y~-eUIk+9|f)N7OxN5$W<6}4PQ?=dR+I@Wv0@^gbno>2h zqzy<&t{S3^kvtvLLGeI1+{c+*->S%M+v^_z0X?qOTNI7i(J%;HKoF1DIEXYb2X;nA ziyb}j@x@)g_E_ioMOe{v)`-8glZHsq%uS1IHJco$64tTnH<^7ByEG7xi~)hy=WSk1 zDVp;J7I_1dmNZQQLJ8kTob0`Pl@BE#&~2{TjU0ond#VQEyby7|G~rAgxN!X6=mL6b zDVwEfmadsGAWItNKtb^!;*4CragCk4bw=@fBtb3kha&Q+`S%*U?ZVB04lpc~zb z5|EAE&>*l{$L;7S9Ih9U~M&WD^UkN)yFAiTAv2Zv$~NR|33EnbOCAP zMnFP02iOEPOPc4}-=}K(eOvea=df;op-c67D@`reNA zH|z~RAt3P8P~)%x%38+-G$ru*Arg-N^C6&T!=syVdVuMgr)sQscCT^#3+(l*A6vc9 zIKK9L;&?ps5hjnX&Q-OKv)A{r-_JupH@lYB=N*A-lLNO-km&io#@X*1`HFuo3MgW5 zfx~60_WUqKoc4bn)(y6YM_XuqssReo?Bc4m|A#O8b-iQdNd4&A$Mw$J9ra_Y2THzt z+4r{B?=SQ$oxj=BY~Ac>hMPUNS__AerMB1i{!9P-R)77-*14%mTewJYZt6Ke9a{sF y=MUOddzJSQBR5ZnE_dwI{^ygO{!1@KBL4?jXegy66Le1i0000 Date: Wed, 2 Jun 2021 15:17:18 -0400 Subject: [PATCH 015/102] Update documentation Signed-off-by: Nikhil Unni --- microsite/data/plugins/cortex.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/data/plugins/cortex.yaml b/microsite/data/plugins/cortex.yaml index d377ec39f2..1ebf8ba567 100644 --- a/microsite/data/plugins/cortex.yaml +++ b/microsite/data/plugins/cortex.yaml @@ -4,7 +4,7 @@ author: Cortex authorUrl: https://github.com/cortexapps category: Monitoring description: Grade the quality of your Backstage services using Scorecards. Automate production readiness, migrations, security audits, and more with CQL (Cortex Query Language). -documentation: +documentation: https://github.com/cortexapps/backstage-plugin iconUrl: img/cortex.png npmPackageName: '@cortexapps/backstage-plugin' tags: From 0160678b18e53901c369a148ab9895c10b5bec40 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 3 Jun 2021 00:35:33 +0200 Subject: [PATCH 016/102] core-api: bring in RouteRef type from core-plugin-api to make them type compatible Signed-off-by: Patrik Oldsberg --- .changeset/unlucky-lemons-sip.md | 5 +++++ packages/core-api/package.json | 1 + packages/core-api/src/routing/types.ts | 7 ++++++- 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 .changeset/unlucky-lemons-sip.md diff --git a/.changeset/unlucky-lemons-sip.md b/.changeset/unlucky-lemons-sip.md new file mode 100644 index 0000000000..585d091e65 --- /dev/null +++ b/.changeset/unlucky-lemons-sip.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-api': patch +--- + +Made the `RouteRef*` types compatible with the ones exported from `@backstage/core-plugin-api`. diff --git a/packages/core-api/package.json b/packages/core-api/package.json index 6ecb942c28..99a9cd2b0f 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -30,6 +30,7 @@ }, "dependencies": { "@backstage/config": "^0.1.4", + "@backstage/core-plugin-api": "^0.1.0", "@backstage/theme": "^0.2.6", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", diff --git a/packages/core-api/src/routing/types.ts b/packages/core-api/src/routing/types.ts index 0089e29e4b..e88b2f748c 100644 --- a/packages/core-api/src/routing/types.ts +++ b/packages/core-api/src/routing/types.ts @@ -16,6 +16,7 @@ import { IconComponent } from '../icons/types'; import { getOrCreateGlobalSingleton } from '../lib/globalObject'; +import { RouteRef as NewRouteRef } from '@backstage/core-plugin-api'; export type AnyParams = { [param in string]: string } | undefined; export type ParamKeys = keyof Params extends never @@ -34,7 +35,11 @@ export type RouteFunc = ( ...[params]: Params extends undefined ? readonly [] : readonly [Params] ) => string; -export const routeRefType: unique symbol = getOrCreateGlobalSingleton( +type RouteRefType = Exclude< + keyof NewRouteRef, + 'params' | 'path' | 'title' | 'icon' +>; +export const routeRefType: RouteRefType = getOrCreateGlobalSingleton( 'route-ref-type', () => Symbol('route-ref-type'), ); From f1bbc6da109400f085d33e30ec30803578f64fe3 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 3 Jun 2021 09:50:37 +0200 Subject: [PATCH 017/102] catalog/next: Keep bootstrap location Signed-off-by: Johan Haals --- .../migrationsv2/20210302150147_refresh_state.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/plugins/catalog-backend/migrationsv2/20210302150147_refresh_state.js b/plugins/catalog-backend/migrationsv2/20210302150147_refresh_state.js index 2258017965..4e6cf4f282 100644 --- a/plugins/catalog-backend/migrationsv2/20210302150147_refresh_state.js +++ b/plugins/catalog-backend/migrationsv2/20210302150147_refresh_state.js @@ -191,11 +191,6 @@ exports.up = async function up(knex) { table.index(['key'], 'search_key_idx'); table.index(['value'], 'search_value_idx'); }); - - // Delete bootstrap location which is no longer required. - await knex('locations') - .where({ type: 'bootstrap', target: 'bootstrap' }) - .delete(); }; /** From 63a432e9cac3a8868120c79a3bed1bf92893cac7 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 3 Jun 2021 09:56:59 +0200 Subject: [PATCH 018/102] Add changeset Signed-off-by: Johan Haals --- .changeset/tender-months-count.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tender-months-count.md diff --git a/.changeset/tender-months-count.md b/.changeset/tender-months-count.md new file mode 100644 index 0000000000..9f76186409 --- /dev/null +++ b/.changeset/tender-months-count.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Skip deletion of bootstrap location when running the new catalog. From 031ccd45f5b988e8ac0ae6168997184c0577b375 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 3 Jun 2021 00:39:07 +0200 Subject: [PATCH 019/102] core-plugin-api: make route ref icon field backwards compatible Signed-off-by: Patrik Oldsberg --- .changeset/little-eggs-change.md | 5 +++++ packages/core-plugin-api/package.json | 1 + packages/core-plugin-api/src/icons/index.ts | 2 +- packages/core-plugin-api/src/icons/types.ts | 8 ++++++++ packages/core-plugin-api/src/routing/RouteRef.ts | 8 ++++---- packages/core-plugin-api/src/routing/types.ts | 4 ++-- 6 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 .changeset/little-eggs-change.md diff --git a/.changeset/little-eggs-change.md b/.changeset/little-eggs-change.md new file mode 100644 index 0000000000..d19f17d665 --- /dev/null +++ b/.changeset/little-eggs-change.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-plugin-api': patch +--- + +Made the deprecated `icon` fields compatible with the `IconComponent` type from `@backstage/core` in order to smooth out the migration. diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index bb6dfdc4bd..cc0fe07872 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -31,6 +31,7 @@ "dependencies": { "@backstage/config": "^0.1.3", "@backstage/theme": "^0.2.3", + "@material-ui/core": "^4.11.0", "@types/react": "^16.9", "history": "^5.0.0", "prop-types": "^15.7.2", diff --git a/packages/core-plugin-api/src/icons/index.ts b/packages/core-plugin-api/src/icons/index.ts index 50e9534751..953c6ef1ed 100644 --- a/packages/core-plugin-api/src/icons/index.ts +++ b/packages/core-plugin-api/src/icons/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export * from './types'; +export type { IconComponent } from './types'; diff --git a/packages/core-plugin-api/src/icons/types.ts b/packages/core-plugin-api/src/icons/types.ts index b0932bdb3a..53743edc59 100644 --- a/packages/core-plugin-api/src/icons/types.ts +++ b/packages/core-plugin-api/src/icons/types.ts @@ -15,6 +15,7 @@ */ import { ComponentType } from 'react'; +import { SvgIconProps } from '@material-ui/core'; /** * IconComponent is the common icon type used throughout Backstage when @@ -31,3 +32,10 @@ import { ComponentType } from 'react'; export type IconComponent = ComponentType<{ fontSize?: 'default' | 'small' | 'large'; }>; + +/** + * This exists for backwards compatibility with the old core package. + * It's used in some parts of this package in order to smooth out the + * migration, but it is not exported. + */ +export type OldIconComponent = ComponentType; diff --git a/packages/core-plugin-api/src/routing/RouteRef.ts b/packages/core-plugin-api/src/routing/RouteRef.ts index f6499ccc9f..fa4c936bff 100644 --- a/packages/core-plugin-api/src/routing/RouteRef.ts +++ b/packages/core-plugin-api/src/routing/RouteRef.ts @@ -21,13 +21,13 @@ import { ParamKeys, OptionalParams, } from './types'; -import { IconComponent } from '../icons/types'; +import { OldIconComponent } from '../icons/types'; // TODO(Rugvip): Remove this in the next breaking release, it's exported but unused export type RouteRefConfig = { params?: ParamKeys; path?: string; - icon?: IconComponent; + icon?: OldIconComponent; title: string; }; @@ -40,7 +40,7 @@ export class RouteRefImpl readonly params: ParamKeys, private readonly config: { path?: string; - icon?: IconComponent; + icon?: OldIconComponent; title?: string; }, ) {} @@ -79,7 +79,7 @@ export function createRouteRef< /** @deprecated Route refs no longer decide their own path */ path?: string; /** @deprecated Route refs no longer decide their own icon */ - icon?: IconComponent; + icon?: OldIconComponent; /** @deprecated Route refs no longer decide their own title */ title?: string; }): RouteRef> { diff --git a/packages/core-plugin-api/src/routing/types.ts b/packages/core-plugin-api/src/routing/types.ts index 0089e29e4b..0e1c817a39 100644 --- a/packages/core-plugin-api/src/routing/types.ts +++ b/packages/core-plugin-api/src/routing/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { IconComponent } from '../icons/types'; +import { OldIconComponent } from '../icons/types'; import { getOrCreateGlobalSingleton } from '../lib/globalObject'; export type AnyParams = { [param in string]: string } | undefined; @@ -48,7 +48,7 @@ export type RouteRef = { /** @deprecated paths are no longer accessed directly from RouteRefs, use useRouteRef instead */ path: string; /** @deprecated icons are no longer accessed via RouteRefs */ - icon?: IconComponent; + icon?: OldIconComponent; /** @deprecated titles are no longer accessed via RouteRefs */ title?: string; }; From bc9c809745fb5752b94fb4b9ffbf91c97611ecb4 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 3 Jun 2021 00:44:33 +0200 Subject: [PATCH 020/102] core-plugin-api: udpate api-report Signed-off-by: Patrik Oldsberg --- packages/core-plugin-api/api-report.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core-plugin-api/api-report.md b/packages/core-plugin-api/api-report.md index 339873cd85..dcf53d8aed 100644 --- a/packages/core-plugin-api/api-report.md +++ b/packages/core-plugin-api/api-report.md @@ -9,6 +9,7 @@ import { ComponentType } from 'react'; import { Config } from '@backstage/config'; import { default as React_2 } from 'react'; import { ReactNode } from 'react'; +import { SvgIconProps } from '@material-ui/core'; // @public export type AlertApi = { @@ -206,7 +207,7 @@ export function createRouteRef>; @@ -424,7 +425,7 @@ export type RouteRef = { readonly [routeRefType]: 'absolute'; params: ParamKeys; path: string; - icon?: IconComponent; + icon?: OldIconComponent; title?: string; }; From 9c63be54544e65c61a21a4b1b3119dbcf6d18504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 3 Jun 2021 10:38:59 +0200 Subject: [PATCH 021/102] Restructure the next catalog types and files a bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/empty-berries-melt.md | 5 + .../DefaultCatalogProcessingEngine.test.ts | 9 +- .../next/DefaultCatalogProcessingEngine.ts | 4 +- .../DefaultCatalogProcessingOrchestrator.ts | 387 ------------------ .../src/next/DefaultLocationService.test.ts | 11 +- .../src/next/DefaultLocationService.ts | 11 +- .../src/next/DefaultLocationStore.test.ts | 1 - .../src/next/DefaultLocationStore.ts | 23 +- .../src/next/NextCatalogBuilder.ts | 4 +- .../src/next/NextEntitiesCatalog.ts | 24 +- .../catalog-backend/src/next/NextRouter.ts | 2 +- .../DefaultProcessingDatabase.test.ts | 15 +- .../database/DefaultProcessingDatabase.ts | 29 +- .../src/next/database/tables.ts | 58 +++ plugins/catalog-backend/src/next/index.ts | 2 + .../DefaultCatalogProcessingOrchestrator.ts | 333 +++++++++++++++ .../processing/ProcessorOutputCollector.ts | 109 +++++ .../src/next/processing/index.ts | 22 + .../src/next/processing/types.ts | 41 ++ .../src/next/processing/util.ts | 81 ++++ .../src/next/{ => stitching}/Stitcher.test.ts | 9 +- .../src/next/{ => stitching}/Stitcher.ts | 32 +- .../buildEntitySearch.test.ts} | 4 +- .../buildEntitySearch.ts} | 7 +- .../src/next/stitching/index.ts | 17 + .../src/next/stitching/util.ts | 31 ++ plugins/catalog-backend/src/next/types.ts | 31 +- plugins/catalog-backend/src/next/util.ts | 3 +- 28 files changed, 772 insertions(+), 533 deletions(-) create mode 100644 .changeset/empty-berries-melt.md delete mode 100644 plugins/catalog-backend/src/next/DefaultCatalogProcessingOrchestrator.ts create mode 100644 plugins/catalog-backend/src/next/database/tables.ts create mode 100644 plugins/catalog-backend/src/next/processing/DefaultCatalogProcessingOrchestrator.ts create mode 100644 plugins/catalog-backend/src/next/processing/ProcessorOutputCollector.ts create mode 100644 plugins/catalog-backend/src/next/processing/index.ts create mode 100644 plugins/catalog-backend/src/next/processing/types.ts create mode 100644 plugins/catalog-backend/src/next/processing/util.ts rename plugins/catalog-backend/src/next/{ => stitching}/Stitcher.test.ts (96%) rename plugins/catalog-backend/src/next/{ => stitching}/Stitcher.ts (91%) rename plugins/catalog-backend/src/next/{search.test.ts => stitching/buildEntitySearch.test.ts} (97%) rename plugins/catalog-backend/src/next/{search.ts => stitching/buildEntitySearch.ts} (98%) create mode 100644 plugins/catalog-backend/src/next/stitching/index.ts create mode 100644 plugins/catalog-backend/src/next/stitching/util.ts diff --git a/.changeset/empty-berries-melt.md b/.changeset/empty-berries-melt.md new file mode 100644 index 0000000000..ab1125262a --- /dev/null +++ b/.changeset/empty-berries-melt.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Restructure the next catalog types and files a bit diff --git a/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.test.ts b/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.test.ts index 376af6e748..0532342c5d 100644 --- a/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.test.ts +++ b/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.test.ts @@ -15,12 +15,11 @@ */ import { getVoidLogger } from '@backstage/backend-common'; -import { DefaultProcessingDatabase } from './database/DefaultProcessingDatabase'; - -import { DefaultCatalogProcessingEngine } from './DefaultCatalogProcessingEngine'; -import { Stitcher } from './Stitcher'; -import { CatalogProcessingOrchestrator } from './types'; import waitForExpect from 'wait-for-expect'; +import { DefaultProcessingDatabase } from './database/DefaultProcessingDatabase'; +import { DefaultCatalogProcessingEngine } from './DefaultCatalogProcessingEngine'; +import { CatalogProcessingOrchestrator } from './processing/types'; +import { Stitcher } from './stitching/Stitcher'; describe('DefaultCatalogProcessingEngine', () => { const db = ({ diff --git a/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts b/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts index 8bc399c171..5de012e936 100644 --- a/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts +++ b/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts @@ -22,10 +22,10 @@ import { import { serializeError } from '@backstage/errors'; import { Logger } from 'winston'; import { ProcessingDatabase } from './database/types'; -import { Stitcher } from './Stitcher'; +import { CatalogProcessingOrchestrator } from './processing/types'; +import { Stitcher } from './stitching/Stitcher'; import { CatalogProcessingEngine, - CatalogProcessingOrchestrator, EntityProvider, EntityProviderConnection, EntityProviderMutation, diff --git a/plugins/catalog-backend/src/next/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/next/DefaultCatalogProcessingOrchestrator.ts deleted file mode 100644 index 917ac11cfb..0000000000 --- a/plugins/catalog-backend/src/next/DefaultCatalogProcessingOrchestrator.ts +++ /dev/null @@ -1,387 +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 { - Entity, - entityEnvelopeSchemaValidator, - EntityPolicy, - EntityRelationSpec, - entitySchemaValidator, - LocationEntity, - LocationSpec, - LOCATION_ANNOTATION, - ORIGIN_LOCATION_ANNOTATION, - parseLocationReference, - stringifyEntityRef, - stringifyLocationReference, -} from '@backstage/catalog-model'; -import { ConflictError, InputError } from '@backstage/errors'; -import { ScmIntegrationRegistry } from '@backstage/integration'; -import path from 'path'; -import { Logger } from 'winston'; -import { - CatalogProcessor, - CatalogProcessorParser, - CatalogProcessorResult, -} from '../ingestion/processors'; -import * as results from '../ingestion/processors/results'; -import { - CatalogProcessingOrchestrator, - EntityProcessingRequest, - EntityProcessingResult, -} from './types'; -import { locationSpecToLocationEntity } from './util'; - -const validateEntity = entitySchemaValidator(); -const validateEntityEnvelope = entityEnvelopeSchemaValidator(); - -function isLocationEntity(entity: Entity): entity is LocationEntity { - return entity.kind === 'Location'; -} - -function getEntityLocationRef(entity: Entity): string { - const ref = entity.metadata.annotations?.[LOCATION_ANNOTATION]; - if (!ref) { - const entityRef = stringifyEntityRef(entity); - throw new InputError(`Entity '${entityRef}' does not have a location`); - } - return ref; -} - -function getEntityOriginLocationRef(entity: Entity): string { - const ref = entity.metadata.annotations?.[ORIGIN_LOCATION_ANNOTATION]; - if (!ref) { - const entityRef = stringifyEntityRef(entity); - throw new InputError( - `Entity '${entityRef}' does not have an origin location`, - ); - } - return ref; -} - -function toAbsoluteUrl( - integrations: ScmIntegrationRegistry, - base: LocationSpec, - type: string, - target: string, -): string { - if (base.type !== type) { - return target; - } - try { - if (type === 'file') { - if (target.startsWith('.')) { - return path.join(path.dirname(base.target), target); - } - return target; - } else if (type === 'url') { - return integrations.resolveUrl({ url: target, base: base.target }); - } - return target; - } catch (e) { - return target; - } -} - -export class DefaultCatalogProcessingOrchestrator - implements CatalogProcessingOrchestrator { - constructor( - private readonly options: { - processors: CatalogProcessor[]; - integrations: ScmIntegrationRegistry; - logger: Logger; - parser: CatalogProcessorParser; - policy: EntityPolicy; - }, - ) {} - - async process( - request: EntityProcessingRequest, - ): Promise { - // TODO: implement dryRun/eager - return this.processSingleEntity(request.entity); - } - - private async processSingleEntity( - unprocessedEntity: Entity, - ): Promise { - const emitter = createEmitter(this.options.logger, unprocessedEntity); - try { - // This will be checked and mutated step by step below - let entity: Entity = unprocessedEntity; - - // NOTE: At this early point, we can only rely on the envelope having to - // be valid; full entity + kind validation happens after the (potentially - // mutative) pre-steps. This means that the code below can't make a lot - // of assumptions about the data despite it using the Entity type. - try { - validateEntityEnvelope(entity); - } catch (e) { - throw new InputError( - `Entity envelope failed validation before processing`, - e, - ); - } - - const entityRef = stringifyEntityRef(entity); - // TODO: which one do we actually use here? source-location? - maybe probably doesn't exist yet? - const locationRef = getEntityLocationRef(entity); - const location = parseLocationReference(locationRef); - const originLocation = parseLocationReference( - getEntityOriginLocationRef(entity), - ); - - // Pre-process phase, used to populate entities with data that is required during main processing step - for (const processor of this.options.processors) { - if (processor.preProcessEntity) { - try { - entity = await processor.preProcessEntity( - entity, - location, - emitter.emit, - originLocation, - ); - } catch (e) { - throw new InputError( - `Processor ${processor.constructor.name} threw an error while preprocessing`, - e, - ); - } - } - } - - // Enforce entity policies making sure that entities conform to a general schema - let policyEnforcedEntity: Entity | undefined; - try { - policyEnforcedEntity = await this.options.policy.enforce(entity); - } catch (e) { - throw new InputError('Policy check failed', e); - } - if (!policyEnforcedEntity) { - throw new Error('Policy unexpectedly returned no data'); - } - entity = policyEnforcedEntity; - - // Validate that the end result is a valid Entity at all - try { - validateEntity(entity); - } catch (e) { - throw new ConflictError( - `Entity envelope failed validation after preprocessing`, - e, - ); - } - - // Validate the given entity kind against its schema - let didValidate = false; - for (const processor of this.options.processors) { - if (processor.validateEntityKind) { - try { - didValidate = await processor.validateEntityKind(entity); - if (didValidate) { - break; - } - } catch (e) { - throw new InputError( - `Processor ${processor.constructor.name} threw an error while validating the entity`, - e, - ); - } - } - } - if (!didValidate) { - throw new InputError( - 'No processor recognized the entity as valid, possibly caused by a foreign kind or apiVersion', - ); - } - - // Double check that none of the previous steps tried to change something - // related to the entity ref, which would break downstream - if (stringifyEntityRef(entity) !== entityRef) { - throw new ConflictError( - 'Fatal: The entity kind, namespace, or name changed during processing', - ); - } - - // Backwards compatible processing of location entities - if (isLocationEntity(entity)) { - const { type = location.type } = entity.spec; - const targets = new Array(); - if (entity.spec.target) { - targets.push(entity.spec.target); - } - if (entity.spec.targets) { - targets.push(...entity.spec.targets); - } - - for (const maybeRelativeTarget of targets) { - if (type === 'file' && maybeRelativeTarget.endsWith(path.sep)) { - emitter.emit( - results.inputError( - location, - `LocationEntityProcessor cannot handle ${type} type location with target ${location.target} that ends with a path separator`, - ), - ); - continue; - } - const target = toAbsoluteUrl( - this.options.integrations, - location, - type, - maybeRelativeTarget, - ); - - let didRead = false; - for (const processor of this.options.processors) { - if (processor.readLocation) { - try { - const read = await processor.readLocation( - { - type, - target, - presence: 'required', - }, - false, - emitter.emit, - this.options.parser, - ); - if (read) { - didRead = true; - break; - } - } catch (e) { - throw new InputError( - `Processor ${processor.constructor.name} threw an error while reading ${type}:${target}`, - e, - ); - } - } - } - if (!didRead) { - throw new InputError( - `No processor was able to handle reading of ${type}:${target}`, - ); - } - } - } - - // Main processing step of the entity - for (const processor of this.options.processors) { - if (processor.postProcessEntity) { - try { - entity = await processor.postProcessEntity( - entity, - location, - emitter.emit, - ); - } catch (e) { - throw new InputError( - `Processor ${processor.constructor.name} threw an error while postprocessing`, - e, - ); - } - } - } - - return { - ...emitter.results(), - completedEntity: entity, - state: new Map(), - ok: true, - }; - } catch (error) { - this.options.logger.warn(error.message); - return { - ok: false, - errors: emitter.results().errors.concat(error), - }; - } - } -} - -function createEmitter(logger: Logger, parentEntity: Entity) { - let done = false; - - const errors = new Array(); - const relations = new Array(); - const deferredEntities = new Array(); - - const emit = (i: CatalogProcessorResult) => { - if (done) { - logger.warn( - `Item if type ${i.type} was emitted after processing had completed at ${ - new Error().stack - }`, - ); - return; - } - - if (i.type === 'entity') { - let entity: Entity; - try { - entity = validateEntityEnvelope(i.entity); - } catch (e) { - logger.debug(`Envelope validation failed at ${i.location}, ${e}`); - errors.push(e); - return; - } - - // Note that at this point, we have only validated the envelope part of - // the entity data. Annotations are not part of that, so we have to be - // defensive. If the annotations were malformed (e.g. were not a valid - // object), we just skip over this step and let the full entity - // validation at the next step of processing catch that. - const annotations = entity.metadata.annotations || {}; - if (typeof annotations === 'object' && !Array.isArray(annotations)) { - const originLocation = getEntityOriginLocationRef(parentEntity); - const location = stringifyLocationReference(i.location); - entity = { - ...entity, - metadata: { - ...entity.metadata, - annotations: { - ...annotations, - [ORIGIN_LOCATION_ANNOTATION]: originLocation, - [LOCATION_ANNOTATION]: location, - }, - }, - }; - } - - deferredEntities.push(entity); - } else if (i.type === 'location') { - deferredEntities.push( - locationSpecToLocationEntity(i.location, parentEntity), - ); - } else if (i.type === 'relation') { - relations.push(i.relation); - } else if (i.type === 'error') { - errors.push(i.error); - } - }; - - return { - emit, - results() { - done = true; - return { - errors, - relations, - deferredEntities, - }; - }, - }; -} diff --git a/plugins/catalog-backend/src/next/DefaultLocationService.test.ts b/plugins/catalog-backend/src/next/DefaultLocationService.test.ts index 662be5d3a6..2958028c5d 100644 --- a/plugins/catalog-backend/src/next/DefaultLocationService.test.ts +++ b/plugins/catalog-backend/src/next/DefaultLocationService.test.ts @@ -15,7 +15,8 @@ */ import { DefaultLocationService } from './DefaultLocationService'; -import { CatalogProcessingOrchestrator, LocationStore } from './types'; +import { CatalogProcessingOrchestrator } from './processing/types'; +import { LocationStore } from './types'; describe('DefaultLocationServiceTest', () => { const orchestrator: jest.Mocked = { @@ -27,9 +28,12 @@ describe('DefaultLocationServiceTest', () => { listLocations: jest.fn(), getLocation: jest.fn(), }; - - beforeEach(() => jest.resetAllMocks()); const locationService = new DefaultLocationService(store, orchestrator); + + beforeEach(() => { + jest.resetAllMocks(); + }); + describe('createLocation', () => { it('should support dry run', async () => { orchestrator.process.mockResolvedValueOnce({ @@ -136,6 +140,7 @@ describe('DefaultLocationServiceTest', () => { }); }); }); + describe('listLocations', () => { it('should call locationStore.deleteLocation', async () => { await locationService.listLocations(); diff --git a/plugins/catalog-backend/src/next/DefaultLocationService.ts b/plugins/catalog-backend/src/next/DefaultLocationService.ts index e628b1d77d..9cc60682de 100644 --- a/plugins/catalog-backend/src/next/DefaultLocationService.ts +++ b/plugins/catalog-backend/src/next/DefaultLocationService.ts @@ -14,17 +14,14 @@ * limitations under the License. */ import { - LocationSpec, - Location, Entity, + Location, + LocationSpec, LOCATION_ANNOTATION, ORIGIN_LOCATION_ANNOTATION, } from '@backstage/catalog-model'; -import { - LocationService, - LocationStore, - CatalogProcessingOrchestrator, -} from './types'; +import { CatalogProcessingOrchestrator } from './processing/types'; +import { LocationService, LocationStore } from './types'; import { locationSpecToMetadataName } from './util'; export class DefaultLocationService implements LocationService { diff --git a/plugins/catalog-backend/src/next/DefaultLocationStore.test.ts b/plugins/catalog-backend/src/next/DefaultLocationStore.test.ts index 88806d0ace..382f0ccaf5 100644 --- a/plugins/catalog-backend/src/next/DefaultLocationStore.test.ts +++ b/plugins/catalog-backend/src/next/DefaultLocationStore.test.ts @@ -23,7 +23,6 @@ const createLocationStore = async () => { const connection = { applyMutation: jest.fn() }; const store = new DefaultLocationStore(knex); await store.connect(connection); - return { store, connection }; }; diff --git a/plugins/catalog-backend/src/next/DefaultLocationStore.ts b/plugins/catalog-backend/src/next/DefaultLocationStore.ts index fe1eb30467..7421b24782 100644 --- a/plugins/catalog-backend/src/next/DefaultLocationStore.ts +++ b/plugins/catalog-backend/src/next/DefaultLocationStore.ts @@ -14,22 +14,17 @@ * limitations under the License. */ -import { LocationSpec, Location } from '@backstage/catalog-model'; -import { - LocationStore, - EntityProvider, - EntityProviderConnection, -} from './types'; -import { v4 as uuid } from 'uuid'; -import { locationSpecToLocationEntity } from './util'; +import { Location, LocationSpec } from '@backstage/catalog-model'; import { ConflictError, NotFoundError } from '@backstage/errors'; import { Knex } from 'knex'; - -type DbLocationsRow = { - id: string; - type: string; - target: string; -}; +import { v4 as uuid } from 'uuid'; +import { DbLocationsRow } from './database/tables'; +import { + EntityProvider, + EntityProviderConnection, + LocationStore, +} from './types'; +import { locationSpecToLocationEntity } from './util'; export class DefaultLocationStore implements LocationStore, EntityProvider { private _connection: EntityProviderConnection | undefined; diff --git a/plugins/catalog-backend/src/next/NextCatalogBuilder.ts b/plugins/catalog-backend/src/next/NextCatalogBuilder.ts index e10d13c129..7a74576ea7 100644 --- a/plugins/catalog-backend/src/next/NextCatalogBuilder.ts +++ b/plugins/catalog-backend/src/next/NextCatalogBuilder.ts @@ -67,11 +67,11 @@ import { CatalogProcessingEngine, LocationService } from '../next/types'; import { ConfigLocationEntityProvider } from './ConfigLocationEntityProvider'; import { DefaultProcessingDatabase } from './database/DefaultProcessingDatabase'; import { DefaultCatalogProcessingEngine } from './DefaultCatalogProcessingEngine'; -import { DefaultCatalogProcessingOrchestrator } from './DefaultCatalogProcessingOrchestrator'; import { DefaultLocationService } from './DefaultLocationService'; import { DefaultLocationStore } from './DefaultLocationStore'; import { NextEntitiesCatalog } from './NextEntitiesCatalog'; -import { Stitcher } from './Stitcher'; +import { DefaultCatalogProcessingOrchestrator } from './processing/DefaultCatalogProcessingOrchestrator'; +import { Stitcher } from './stitching/Stitcher'; export type CatalogEnvironment = { logger: Logger; diff --git a/plugins/catalog-backend/src/next/NextEntitiesCatalog.ts b/plugins/catalog-backend/src/next/NextEntitiesCatalog.ts index 9400bf664f..537ea17eef 100644 --- a/plugins/catalog-backend/src/next/NextEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/next/NextEntitiesCatalog.ts @@ -14,17 +14,19 @@ * limitations under the License. */ -import { Knex } from 'knex'; -import { DbFinalEntitiesRow } from './Stitcher'; -import { EntitiesCatalog } from '../catalog'; -import { EntitiesRequest, EntitiesResponse } from '../catalog/types'; -import { DbRefreshStateRow } from './database/DefaultProcessingDatabase'; -import { - DbEntitiesSearchRow, - DbPageInfo, - EntityPagination, -} from '../database/types'; import { InputError } from '@backstage/errors'; +import { Knex } from 'knex'; +import { + EntitiesCatalog, + EntitiesRequest, + EntitiesResponse, +} from '../catalog/types'; +import { DbPageInfo, EntityPagination } from '../database/types'; +import { + DbFinalEntitiesRow, + DbRefreshStateRow, + DbSearchRow, +} from './database/tables'; function parsePagination( input?: EntityPagination, @@ -80,7 +82,7 @@ export class NextEntitiesCatalog implements EntitiesCatalog { // NOTE(freben): This used to be a set of OUTER JOIN, which may seem to // make a lot of sense. However, it had abysmal performance on sqlite // when datasets grew large, so we're using IN instead. - const matchQuery = db('search') + const matchQuery = db('search') .select('entity_id') .where(function keyFilter() { this.andWhere({ key: key.toLowerCase() }); diff --git a/plugins/catalog-backend/src/next/NextRouter.ts b/plugins/catalog-backend/src/next/NextRouter.ts index b4660edea0..dc1eef74be 100644 --- a/plugins/catalog-backend/src/next/NextRouter.ts +++ b/plugins/catalog-backend/src/next/NextRouter.ts @@ -33,8 +33,8 @@ import { parseEntityPaginationParams, parseEntityTransformParams, } from '../service/request'; -import { LocationService } from './types'; import { disallowReadonlyMode, validateRequestBody } from '../service/util'; +import { LocationService } from './types'; export interface RouterOptions { entitiesCatalog?: EntitiesCatalog; diff --git a/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.test.ts b/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.test.ts index 7a0094dbf0..4a390058b8 100644 --- a/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.test.ts +++ b/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.test.ts @@ -14,19 +14,18 @@ * limitations under the License. */ // import { DefaultProcessingDatabase } from './DefaultProcessingDatabase'; -import { DatabaseManager } from './DatabaseManager'; +import { getVoidLogger } from '@backstage/backend-common'; +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; +import { JsonObject } from '@backstage/config'; import { Knex } from 'knex'; +import * as uuid from 'uuid'; +import { DatabaseManager } from './DatabaseManager'; +import { DefaultProcessingDatabase } from './DefaultProcessingDatabase'; import { DbRefreshStateReferencesRow, DbRefreshStateRow, DbRelationsRow, - DefaultProcessingDatabase, -} from './DefaultProcessingDatabase'; - -import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; -import * as uuid from 'uuid'; -import { getVoidLogger } from '@backstage/backend-common'; -import { JsonObject } from '@backstage/config'; +} from './tables'; describe('Default Processing Database', () => { let db: Knex; diff --git a/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.ts b/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.ts index 3150480108..7eb2cd1a74 100644 --- a/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.ts +++ b/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.ts @@ -22,6 +22,11 @@ import lodash from 'lodash'; import { v4 as uuid } from 'uuid'; import type { Logger } from 'winston'; import { Transaction } from '../../database'; +import { + DbRefreshStateReferencesRow, + DbRefreshStateRow, + DbRelationsRow, +} from './tables'; import { AddUnprocessedEntitiesOptions, GetProcessableEntitiesResult, @@ -31,30 +36,6 @@ import { UpdateProcessedEntityOptions, } from './types'; -export type DbRefreshStateRow = { - entity_id: string; - entity_ref: string; - unprocessed_entity: string; - processed_entity?: string; - cache?: string; - next_update_at: string; - last_discovery_at: string; // remove? - errors?: string; -}; - -export type DbRelationsRow = { - originating_entity_id: string; - source_entity_ref: string; - target_entity_ref: string; - type: string; -}; - -export type DbRefreshStateReferencesRow = { - source_key?: string; - source_entity_ref?: string; - target_entity_ref: string; -}; - // The number of items that are sent per batch to the database layer, when // doing .batchInsert calls to knex. This needs to be low enough to not cause // errors in the underlying engine due to exceeding query limits, but large diff --git a/plugins/catalog-backend/src/next/database/tables.ts b/plugins/catalog-backend/src/next/database/tables.ts new file mode 100644 index 0000000000..eddd6d12b0 --- /dev/null +++ b/plugins/catalog-backend/src/next/database/tables.ts @@ -0,0 +1,58 @@ +/* + * 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 DbLocationsRow = { + id: string; + type: string; + target: string; +}; + +export type DbRefreshStateRow = { + entity_id: string; + entity_ref: string; + unprocessed_entity: string; + processed_entity?: string; + cache?: string; + next_update_at: string; + last_discovery_at: string; // remove? + errors?: string; +}; + +export type DbRefreshStateReferencesRow = { + source_key?: string; + source_entity_ref?: string; + target_entity_ref: string; +}; + +export type DbRelationsRow = { + originating_entity_id: string; + source_entity_ref: string; + target_entity_ref: string; + type: string; +}; + +export type DbFinalEntitiesRow = { + entity_id: string; + hash: string; + stitch_ticket: string; + final_entity?: string; +}; + +export type DbSearchRow = { + entity_id: string; + key: string; + value: string | null; +}; diff --git a/plugins/catalog-backend/src/next/index.ts b/plugins/catalog-backend/src/next/index.ts index 76867e05c8..8a1e6e4a2c 100644 --- a/plugins/catalog-backend/src/next/index.ts +++ b/plugins/catalog-backend/src/next/index.ts @@ -16,3 +16,5 @@ export { NextCatalogBuilder } from './NextCatalogBuilder'; export { createNextRouter } from './NextRouter'; +export * from './processing'; +export * from './stitching'; diff --git a/plugins/catalog-backend/src/next/processing/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/next/processing/DefaultCatalogProcessingOrchestrator.ts new file mode 100644 index 0000000000..c3eeea3d8e --- /dev/null +++ b/plugins/catalog-backend/src/next/processing/DefaultCatalogProcessingOrchestrator.ts @@ -0,0 +1,333 @@ +/* + * 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 { + Entity, + EntityPolicy, + LocationEntity, + LocationSpec, + parseLocationReference, + stringifyEntityRef, +} from '@backstage/catalog-model'; +import { ConflictError, InputError } from '@backstage/errors'; +import { ScmIntegrationRegistry } from '@backstage/integration'; +import path from 'path'; +import { Logger } from 'winston'; +import { + CatalogProcessor, + CatalogProcessorParser, +} from '../../ingestion/processors'; +import * as results from '../../ingestion/processors/results'; +import { + CatalogProcessingOrchestrator, + EntityProcessingRequest, + EntityProcessingResult, +} from './types'; +import { ProcessorOutputCollector } from './ProcessorOutputCollector'; +import { + getEntityLocationRef, + getEntityOriginLocationRef, + isLocationEntity, + toAbsoluteUrl, + validateEntity, + validateEntityEnvelope, +} from './util'; + +type Context = { + entityRef: string; + location: LocationSpec; + originLocation: LocationSpec; + collector: ProcessorOutputCollector; +}; + +export class DefaultCatalogProcessingOrchestrator + implements CatalogProcessingOrchestrator { + constructor( + private readonly options: { + processors: CatalogProcessor[]; + integrations: ScmIntegrationRegistry; + logger: Logger; + parser: CatalogProcessorParser; + policy: EntityPolicy; + }, + ) {} + + async process( + request: EntityProcessingRequest, + ): Promise { + // TODO: implement dryRun/eager + return this.processSingleEntity(request.entity); + } + + private async processSingleEntity( + unprocessedEntity: Entity, + ): Promise { + const collector = new ProcessorOutputCollector( + this.options.logger, + unprocessedEntity, + ); + + try { + // This will be checked and mutated step by step below + let entity: Entity = unprocessedEntity; + + // NOTE: At this early point, we can only rely on the envelope having to + // be valid; full entity + kind validation happens after the (potentially + // mutative) pre-steps. This means that the code below can't make a lot + // of assumptions about the data despite it using the Entity type. + try { + validateEntityEnvelope(entity); + } catch (e) { + throw new InputError( + `Entity envelope failed validation before processing`, + e, + ); + } + + // TODO: which one do we actually use for the location? + // source-location? - maybe probably doesn't exist yet? + const context: Context = { + entityRef: stringifyEntityRef(entity), + location: parseLocationReference(getEntityLocationRef(entity)), + originLocation: parseLocationReference( + getEntityOriginLocationRef(entity), + ), + collector, + }; + + // Run the steps + entity = await this.runPreProcessStep(entity, context); + entity = await this.runPolicyStep(entity); + await this.runValidateStep(entity, context); + if (isLocationEntity(entity)) { + await this.runSpecialLocationStep(entity, context); + } + entity = await this.runPostProcessStep(entity, context); + + return { + ...context.collector.results(), + completedEntity: entity, + state: new Map(), + ok: true, + }; + } catch (error) { + this.options.logger.warn(error.message); + return { + ok: false, + errors: collector.results().errors.concat(error), + }; + } + } + + // Pre-process phase, used to populate entities with data that is required + // during the main processing step + private async runPreProcessStep( + entity: Entity, + context: Context, + ): Promise { + let result = entity; + + for (const processor of this.options.processors) { + if (processor.preProcessEntity) { + try { + result = await processor.preProcessEntity( + result, + context.location, + context.collector.onEmit, + context.originLocation, + ); + } catch (e) { + throw new InputError( + `Processor ${processor.constructor.name} threw an error while preprocessing`, + e, + ); + } + } + } + + return result; + } + + /** + * Enforce entity policies making sure that entities conform to a general schema + */ + private async runPolicyStep(entity: Entity): Promise { + let policyEnforcedEntity: Entity | undefined; + + try { + policyEnforcedEntity = await this.options.policy.enforce(entity); + } catch (e) { + throw new InputError('Policy check failed', e); + } + + if (!policyEnforcedEntity) { + throw new Error('Policy unexpectedly returned no data'); + } + + return policyEnforcedEntity; + } + + /** + * Validate the given entity + */ + private async runValidateStep( + entity: Entity, + context: Context, + ): Promise { + // Double check that none of the previous steps tried to change something + // related to the entity ref, which would break downstream + if (stringifyEntityRef(entity) !== context.entityRef) { + throw new ConflictError( + 'Fatal: The entity kind, namespace, or name changed during processing', + ); + } + + // Validate that the end result is a valid Entity at all + try { + validateEntity(entity); + } catch (e) { + throw new ConflictError( + `Entity envelope failed validation after preprocessing`, + e, + ); + } + + let foundKind = false; + + for (const processor of this.options.processors) { + if (processor.validateEntityKind) { + try { + foundKind = await processor.validateEntityKind(entity); + if (foundKind) { + // TODO(freben): It would make sense to keep running, so that + // multiple processors could have a go at making checks. For + // example, an org may want to add additional rules on top of the + // provided ones. But that would be a breaking change, so we'll + // postpone that to a future processors rewrite. + break; + } + } catch (e) { + throw new InputError( + `Processor ${processor.constructor.name} threw an error while validating the entity`, + e, + ); + } + } + } + + if (!foundKind) { + throw new InputError( + 'No processor recognized the entity as valid, possibly caused by a foreign kind or apiVersion', + ); + } + } + + /** + * Backwards compatible processing of location entities + */ + private async runSpecialLocationStep( + entity: LocationEntity, + context: Context, + ): Promise { + const { type = context.location.type } = entity.spec; + const targets = new Array(); + if (entity.spec.target) { + targets.push(entity.spec.target); + } + if (entity.spec.targets) { + targets.push(...entity.spec.targets); + } + + for (const maybeRelativeTarget of targets) { + if (type === 'file' && maybeRelativeTarget.endsWith(path.sep)) { + context.collector.onEmit( + results.inputError( + context.location, + `LocationEntityProcessor cannot handle ${type} type location with target ${context.location.target} that ends with a path separator`, + ), + ); + continue; + } + const target = toAbsoluteUrl( + this.options.integrations, + context.location, + type, + maybeRelativeTarget, + ); + + let didRead = false; + for (const processor of this.options.processors) { + if (processor.readLocation) { + try { + const read = await processor.readLocation( + { + type, + target, + presence: 'required', + }, + false, + context.collector.onEmit, + this.options.parser, + ); + if (read) { + didRead = true; + break; + } + } catch (e) { + throw new InputError( + `Processor ${processor.constructor.name} threw an error while reading ${type}:${target}`, + e, + ); + } + } + } + if (!didRead) { + throw new InputError( + `No processor was able to handle reading of ${type}:${target}`, + ); + } + } + } + + /** + * Main processing step of the entity + */ + private async runPostProcessStep( + entity: Entity, + context: Context, + ): Promise { + let result = entity; + + for (const processor of this.options.processors) { + if (processor.postProcessEntity) { + try { + result = await processor.postProcessEntity( + result, + context.location, + context.collector.onEmit, + ); + } catch (e) { + throw new InputError( + `Processor ${processor.constructor.name} threw an error while postprocessing`, + e, + ); + } + } + } + + return result; + } +} diff --git a/plugins/catalog-backend/src/next/processing/ProcessorOutputCollector.ts b/plugins/catalog-backend/src/next/processing/ProcessorOutputCollector.ts new file mode 100644 index 0000000000..0c3d74aaa1 --- /dev/null +++ b/plugins/catalog-backend/src/next/processing/ProcessorOutputCollector.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 { + Entity, + EntityRelationSpec, + LOCATION_ANNOTATION, + ORIGIN_LOCATION_ANNOTATION, + stringifyLocationReference, +} from '@backstage/catalog-model'; +import { Logger } from 'winston'; +import { CatalogProcessorResult } from '../../ingestion'; +import { locationSpecToLocationEntity } from '../util'; +import { getEntityOriginLocationRef, validateEntityEnvelope } from './util'; + +/** + * Helper class for aggregating all of the emitted data from processors. + */ +export class ProcessorOutputCollector { + private readonly errors = new Array(); + private readonly relations = new Array(); + private readonly deferredEntities = new Array(); + private done = false; + + constructor( + private readonly logger: Logger, + private readonly parentEntity: Entity, + ) {} + + get onEmit(): (i: CatalogProcessorResult) => void { + return i => this.receive(i); + } + + results() { + this.done = true; + return { + errors: this.errors, + relations: this.relations, + deferredEntities: this.deferredEntities, + }; + } + + private receive(i: CatalogProcessorResult) { + if (this.done) { + this.logger.warn( + `Item if type ${i.type} was emitted after processing had completed at ${ + new Error().stack + }`, + ); + return; + } + + if (i.type === 'entity') { + let entity: Entity; + try { + entity = validateEntityEnvelope(i.entity); + } catch (e) { + this.logger.debug(`Envelope validation failed at ${i.location}, ${e}`); + this.errors.push(e); + return; + } + + // Note that at this point, we have only validated the envelope part of + // the entity data. Annotations are not part of that, so we have to be + // defensive. If the annotations were malformed (e.g. were not a valid + // object), we just skip over this step and let the full entity + // validation at the next step of processing catch that. + const annotations = entity.metadata.annotations || {}; + if (typeof annotations === 'object' && !Array.isArray(annotations)) { + const originLocation = getEntityOriginLocationRef(this.parentEntity); + const location = stringifyLocationReference(i.location); + entity = { + ...entity, + metadata: { + ...entity.metadata, + annotations: { + ...annotations, + [ORIGIN_LOCATION_ANNOTATION]: originLocation, + [LOCATION_ANNOTATION]: location, + }, + }, + }; + } + + this.deferredEntities.push(entity); + } else if (i.type === 'location') { + this.deferredEntities.push( + locationSpecToLocationEntity(i.location, this.parentEntity), + ); + } else if (i.type === 'relation') { + this.relations.push(i.relation); + } else if (i.type === 'error') { + this.errors.push(i.error); + } + } +} diff --git a/plugins/catalog-backend/src/next/processing/index.ts b/plugins/catalog-backend/src/next/processing/index.ts new file mode 100644 index 0000000000..7ac4c6a35e --- /dev/null +++ b/plugins/catalog-backend/src/next/processing/index.ts @@ -0,0 +1,22 @@ +/* + * 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 { + CatalogProcessingOrchestrator, + EntityProcessingRequest, + EntityProcessingResult, +} from './types'; +export { DefaultCatalogProcessingOrchestrator } from './DefaultCatalogProcessingOrchestrator'; diff --git a/plugins/catalog-backend/src/next/processing/types.ts b/plugins/catalog-backend/src/next/processing/types.ts new file mode 100644 index 0000000000..a287ff7bd7 --- /dev/null +++ b/plugins/catalog-backend/src/next/processing/types.ts @@ -0,0 +1,41 @@ +/* + * 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 { Entity, EntityRelationSpec } from '@backstage/catalog-model'; +import { JsonObject } from '@backstage/config'; + +export type EntityProcessingRequest = { + entity: Entity; + state: Map; // Versions for multiple deployments etc +}; + +export type EntityProcessingResult = + | { + ok: true; + state: Map; + completedEntity: Entity; + deferredEntities: Entity[]; + relations: EntityRelationSpec[]; + errors: Error[]; + } + | { + ok: false; + errors: Error[]; + }; + +export interface CatalogProcessingOrchestrator { + process(request: EntityProcessingRequest): Promise; +} diff --git a/plugins/catalog-backend/src/next/processing/util.ts b/plugins/catalog-backend/src/next/processing/util.ts new file mode 100644 index 0000000000..b1fff2449e --- /dev/null +++ b/plugins/catalog-backend/src/next/processing/util.ts @@ -0,0 +1,81 @@ +/* + * 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 { + Entity, + entityEnvelopeSchemaValidator, + entitySchemaValidator, + LocationEntity, + LocationSpec, + LOCATION_ANNOTATION, + ORIGIN_LOCATION_ANNOTATION, + stringifyEntityRef, +} from '@backstage/catalog-model'; +import { InputError } from '@backstage/errors'; +import { ScmIntegrationRegistry } from '@backstage/integration'; +import path from 'path'; + +export function isLocationEntity(entity: Entity): entity is LocationEntity { + return entity.kind === 'Location'; +} + +export function getEntityLocationRef(entity: Entity): string { + const ref = entity.metadata.annotations?.[LOCATION_ANNOTATION]; + if (!ref) { + const entityRef = stringifyEntityRef(entity); + throw new InputError(`Entity '${entityRef}' does not have a location`); + } + return ref; +} + +export function getEntityOriginLocationRef(entity: Entity): string { + const ref = entity.metadata.annotations?.[ORIGIN_LOCATION_ANNOTATION]; + if (!ref) { + const entityRef = stringifyEntityRef(entity); + throw new InputError( + `Entity '${entityRef}' does not have an origin location`, + ); + } + return ref; +} + +export function toAbsoluteUrl( + integrations: ScmIntegrationRegistry, + base: LocationSpec, + type: string, + target: string, +): string { + if (base.type !== type) { + return target; + } + try { + if (type === 'file') { + if (target.startsWith('.')) { + return path.join(path.dirname(base.target), target); + } + return target; + } else if (type === 'url') { + return integrations.resolveUrl({ url: target, base: base.target }); + } + return target; + } catch (e) { + return target; + } +} + +export const validateEntity = entitySchemaValidator(); + +export const validateEntityEnvelope = entityEnvelopeSchemaValidator(); diff --git a/plugins/catalog-backend/src/next/Stitcher.test.ts b/plugins/catalog-backend/src/next/stitching/Stitcher.test.ts similarity index 96% rename from plugins/catalog-backend/src/next/Stitcher.test.ts rename to plugins/catalog-backend/src/next/stitching/Stitcher.test.ts index a6af6f01a1..89f1fda0b3 100644 --- a/plugins/catalog-backend/src/next/Stitcher.test.ts +++ b/plugins/catalog-backend/src/next/stitching/Stitcher.test.ts @@ -17,14 +17,15 @@ import { getVoidLogger } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { Knex } from 'knex'; -import { DatabaseManager } from './database/DatabaseManager'; +import { DatabaseManager } from '../database/DatabaseManager'; import { + DbFinalEntitiesRow, DbRefreshStateReferencesRow, DbRefreshStateRow, DbRelationsRow, -} from './database/DefaultProcessingDatabase'; -import { DbSearchRow } from './search'; -import { DbFinalEntitiesRow, Stitcher } from './Stitcher'; + DbSearchRow, +} from '../database/tables'; +import { Stitcher } from './Stitcher'; describe('Stitcher', () => { let db: Knex; diff --git a/plugins/catalog-backend/src/next/Stitcher.ts b/plugins/catalog-backend/src/next/stitching/Stitcher.ts similarity index 91% rename from plugins/catalog-backend/src/next/Stitcher.ts rename to plugins/catalog-backend/src/next/stitching/Stitcher.ts index 7a2eeac594..c735696d7f 100644 --- a/plugins/catalog-backend/src/next/Stitcher.ts +++ b/plugins/catalog-backend/src/next/stitching/Stitcher.ts @@ -21,32 +21,16 @@ import { UNSTABLE_EntityStatusItem, } from '@backstage/catalog-model'; import { SerializedError } from '@backstage/errors'; -import { createHash } from 'crypto'; -import stableStringify from 'fast-json-stable-stringify'; import { Knex } from 'knex'; -import { Logger } from 'winston'; -import { buildEntitySearch, DbSearchRow } from './search'; import { v4 as uuid } from 'uuid'; -import { DbRefreshStateRow } from './database/DefaultProcessingDatabase'; - -// The number of items that are sent per batch to the database layer, when -// doing .batchInsert calls to knex. This needs to be low enough to not cause -// errors in the underlying engine due to exceeding query limits, but large -// enough to get the speed benefits. -const BATCH_SIZE = 50; - -export type DbFinalEntitiesRow = { - entity_id: string; - hash: string; - stitch_ticket: string; - final_entity?: string; -}; - -function generateStableHash(entity: Entity) { - return createHash('sha1') - .update(stableStringify({ ...entity })) - .digest('hex'); -} +import { Logger } from 'winston'; +import { + DbFinalEntitiesRow, + DbRefreshStateRow, + DbSearchRow, +} from '../database/tables'; +import { buildEntitySearch } from './buildEntitySearch'; +import { BATCH_SIZE, generateStableHash } from './util'; /** * Performs the act of stitching - to take all of the various outputs from the diff --git a/plugins/catalog-backend/src/next/search.test.ts b/plugins/catalog-backend/src/next/stitching/buildEntitySearch.test.ts similarity index 97% rename from plugins/catalog-backend/src/next/search.test.ts rename to plugins/catalog-backend/src/next/stitching/buildEntitySearch.test.ts index be896319b7..3f04ed79d4 100644 --- a/plugins/catalog-backend/src/next/search.test.ts +++ b/plugins/catalog-backend/src/next/stitching/buildEntitySearch.test.ts @@ -15,9 +15,9 @@ */ import { Entity, ENTITY_DEFAULT_NAMESPACE } from '@backstage/catalog-model'; -import { buildEntitySearch, mapToRows, traverse } from './search'; +import { buildEntitySearch, mapToRows, traverse } from './buildEntitySearch'; -describe('search', () => { +describe('buildEntitySearch', () => { describe('traverse', () => { it('expands lists of strings to several rows', () => { const input = { a: ['b', 'c', 'd'] }; diff --git a/plugins/catalog-backend/src/next/search.ts b/plugins/catalog-backend/src/next/stitching/buildEntitySearch.ts similarity index 98% rename from plugins/catalog-backend/src/next/search.ts rename to plugins/catalog-backend/src/next/stitching/buildEntitySearch.ts index 0681bffa17..48a4e779bd 100644 --- a/plugins/catalog-backend/src/next/search.ts +++ b/plugins/catalog-backend/src/next/stitching/buildEntitySearch.ts @@ -19,12 +19,7 @@ import { ENTITY_DEFAULT_NAMESPACE, stringifyEntityRef, } from '@backstage/catalog-model'; - -export type DbSearchRow = { - entity_id: string; - key: string; - value: string | null; -}; +import { DbSearchRow } from '../database/tables'; // These are excluded in the generic loop, either because they do not make sense // to index, or because they are special-case always inserted whether they are diff --git a/plugins/catalog-backend/src/next/stitching/index.ts b/plugins/catalog-backend/src/next/stitching/index.ts new file mode 100644 index 0000000000..4e230aca20 --- /dev/null +++ b/plugins/catalog-backend/src/next/stitching/index.ts @@ -0,0 +1,17 @@ +/* + * 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 {}; diff --git a/plugins/catalog-backend/src/next/stitching/util.ts b/plugins/catalog-backend/src/next/stitching/util.ts new file mode 100644 index 0000000000..a72f22d19d --- /dev/null +++ b/plugins/catalog-backend/src/next/stitching/util.ts @@ -0,0 +1,31 @@ +/* + * 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 { Entity } from '@backstage/catalog-model'; +import { createHash } from 'crypto'; +import stableStringify from 'fast-json-stable-stringify'; + +// The number of items that are sent per batch to the database layer, when +// doing .batchInsert calls to knex. This needs to be low enough to not cause +// errors in the underlying engine due to exceeding query limits, but large +// enough to get the speed benefits. +export const BATCH_SIZE = 50; + +export function generateStableHash(entity: Entity) { + return createHash('sha1') + .update(stableStringify({ ...entity })) + .digest('hex'); +} diff --git a/plugins/catalog-backend/src/next/types.ts b/plugins/catalog-backend/src/next/types.ts index a5d85feb46..f189f3e975 100644 --- a/plugins/catalog-backend/src/next/types.ts +++ b/plugins/catalog-backend/src/next/types.ts @@ -14,13 +14,7 @@ * limitations under the License. */ -import { - Entity, - EntityRelationSpec, - Location, - LocationSpec, -} from '@backstage/catalog-model'; -import { JsonObject } from '@backstage/config'; +import { Entity, Location, LocationSpec } from '@backstage/catalog-model'; export interface LocationService { createLocation( @@ -56,26 +50,3 @@ export interface EntityProvider { getProviderName(): string; connect(connection: EntityProviderConnection): Promise; } - -export type EntityProcessingRequest = { - entity: Entity; - state: Map; // Versions for multiple deployments etc -}; - -export type EntityProcessingResult = - | { - ok: true; - state: Map; - completedEntity: Entity; - deferredEntities: Entity[]; - relations: EntityRelationSpec[]; - errors: Error[]; - } - | { - ok: false; - errors: Error[]; - }; - -export interface CatalogProcessingOrchestrator { - process(request: EntityProcessingRequest): Promise; -} diff --git a/plugins/catalog-backend/src/next/util.ts b/plugins/catalog-backend/src/next/util.ts index f8e6f1f978..ef3a953384 100644 --- a/plugins/catalog-backend/src/next/util.ts +++ b/plugins/catalog-backend/src/next/util.ts @@ -16,8 +16,8 @@ import { Entity, - LocationSpec, LocationEntityV1alpha1, + LocationSpec, LOCATION_ANNOTATION, ORIGIN_LOCATION_ANNOTATION, stringifyEntityRef, @@ -29,7 +29,6 @@ export function locationSpecToMetadataName(location: LocationSpec) { const hash = createHash('sha1') .update(`${location.type}:${location.target}`) .digest('hex'); - return `generated-${hash}`; } From 313071b974ca70056e149684a6e531cbbd8394b3 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 3 Jun 2021 10:54:06 +0200 Subject: [PATCH 022/102] chore: adding documentation for loading data Signed-off-by: blam --- packages/app/src/apis.ts | 9 ++++++ plugins/tech-radar/README.md | 53 ++++++++++++++++---------------- plugins/tech-radar/src/sample.ts | 4 +-- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts index ccc576e727..1b303a8aa3 100644 --- a/packages/app/src/apis.ts +++ b/packages/app/src/apis.ts @@ -34,6 +34,13 @@ import { GraphQLEndpoints, } from '@backstage/plugin-graphiql'; +import { techRadarApiRef, TechRadarApi } from '@backstage/plugin-tech-radar'; + +class MyOwnClient implements TechRadarApi { + async load() { + throw new Error('blah'); + } +} export const apis: AnyApiFactory[] = [ createApiFactory({ api: scmIntegrationsApiRef, @@ -61,4 +68,6 @@ export const apis: AnyApiFactory[] = [ }), createApiFactory(costInsightsApiRef, new ExampleCostInsightsClient()), + + createApiFactory(), ]; diff --git a/plugins/tech-radar/README.md b/plugins/tech-radar/README.md index 149ab54840..2781acabd9 100644 --- a/plugins/tech-radar/README.md +++ b/plugins/tech-radar/README.md @@ -72,36 +72,35 @@ export interface TechRadarPageProps { ### How do I load in my own data? -It's simple, you can pass through a `getData` prop which expects a `Promise` signature. +The `TechRadar` plugin uses the `TechRadarApiRef` to get a client which implements the `TechRadarApi` interface. The default sample one is located here: https://github.com/backstage/backstage/blob/master/plugins/tech-radar/src/sample.ts. To load your own data, you'll need to provide a class that implements the `TechRadarApi` and override the `TechRadarApiRef` in the `app/src/apis.ts`. -Here's an example: +```ts +// app/src/lib/MyClient.ts +import { + TechRadarApi, + TechRadarLoaderResponse, +} from '@backstage/plugin-tech-radar'; -```tsx -const getHardCodedData = () => - Promise.resolve({ - quadrants: [{ id: 'infrastructure', name: 'Infrastructure' }], - rings: [{ id: 'use', name: 'USE', color: '#93c47d' }], - entries: [ - { - url: '#', - key: 'github-actions', - id: 'github-actions', - title: 'GitHub Actions', - quadrant: 'infrastructure', - timeline: [ - { - moved: 0, - ringId: 'use', - date: new Date('2020-08-06'), - description: - 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat', - }, - ], - }, - ], - }); +class MyOwnClient implements TechRadarApi { + async load(): Promise { + const data = await fetch('https://mydata.json').then(res => res.json()); -; + // maybe you'll need to do some data transformation here to make it look like TechRadarLoaderResponse + + return data; + } +} + +// app/src/apis.ts +import { MyOwnClient } from './lib/MyClient'; +import { techRadarApiRef } from '@backstage/plugin-tech-radar'; + +export const apis: AnyApiFactory[] = [ + /* + ... + */ + createApiFactory(techRadarApiRef, new MyOwnClient()), +]; ``` ### How do I write tests? diff --git a/plugins/tech-radar/src/sample.ts b/plugins/tech-radar/src/sample.ts index ff895bcdb7..1e924d85d0 100644 --- a/plugins/tech-radar/src/sample.ts +++ b/plugins/tech-radar/src/sample.ts @@ -165,8 +165,8 @@ entries.push({ ], url: '#', key: 'github-actions', - id: 'github-actiosns', - title: 'GitHub Acssstions', + id: 'github-actions', + title: 'GitHub Actions', quadrant: 'infrastructure', }); From 89e2944e1f8578f221e87908184b5daf0eea7b9b Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 3 Jun 2021 11:03:20 +0200 Subject: [PATCH 023/102] chore: fixing tests and making the thing nice Signed-off-by: blam --- .../src/components/RadarComponent.test.tsx | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/plugins/tech-radar/src/components/RadarComponent.test.tsx b/plugins/tech-radar/src/components/RadarComponent.test.tsx index 59cababbc9..4b740597b5 100644 --- a/plugins/tech-radar/src/components/RadarComponent.test.tsx +++ b/plugins/tech-radar/src/components/RadarComponent.test.tsx @@ -24,6 +24,7 @@ import { withLogCollector } from '@backstage/test-utils'; import GetBBoxPolyfill from '../utils/polyfills/getBBox'; import RadarComponent from './RadarComponent'; +import { TechRadarLoaderResponse, techRadarApiRef, TechRadarApi } from '../api'; describe('RadarComponent', () => { beforeAll(() => { @@ -34,13 +35,30 @@ describe('RadarComponent', () => { GetBBoxPolyfill.remove(); }); + class MockClient implements TechRadarApi { + async load(): Promise { + return { + entries: [], + quadrants: [], + rings: [], + }; + } + } + + const mockClient = new MockClient(); + it('should render a progress bar', async () => { jest.useFakeTimers(); const errorApi = { post: () => {} }; const { getByTestId, queryByTestId } = render( - + { it('should call the errorApi if load fails', async () => { const errorApi = { post: jest.fn() }; - const techRadarLoadFail = () => - Promise.reject(new Error('404 Page Not Found')); + jest + .spyOn(mockClient, 'load') + .mockRejectedValue(new Error('404 Page Not Found')); const { queryByTestId } = render( - + From 90a505a77f3da68f0522d6623c3b847052968d4e Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 3 Jun 2021 11:09:15 +0200 Subject: [PATCH 024/102] chore: added changeset Signed-off-by: blam --- .changeset/proud-bottles-dream.md | 5 +++++ packages/app/src/apis.ts | 9 --------- 2 files changed, 5 insertions(+), 9 deletions(-) create mode 100644 .changeset/proud-bottles-dream.md diff --git a/.changeset/proud-bottles-dream.md b/.changeset/proud-bottles-dream.md new file mode 100644 index 0000000000..42cff9db46 --- /dev/null +++ b/.changeset/proud-bottles-dream.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-tech-radar': minor +--- + +Migrating the Tech Radar to support using `ApiRefs` to go fetch data diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts index 1b303a8aa3..ccc576e727 100644 --- a/packages/app/src/apis.ts +++ b/packages/app/src/apis.ts @@ -34,13 +34,6 @@ import { GraphQLEndpoints, } from '@backstage/plugin-graphiql'; -import { techRadarApiRef, TechRadarApi } from '@backstage/plugin-tech-radar'; - -class MyOwnClient implements TechRadarApi { - async load() { - throw new Error('blah'); - } -} export const apis: AnyApiFactory[] = [ createApiFactory({ api: scmIntegrationsApiRef, @@ -68,6 +61,4 @@ export const apis: AnyApiFactory[] = [ }), createApiFactory(costInsightsApiRef, new ExampleCostInsightsClient()), - - createApiFactory(), ]; From 1ff7d88c57c271864cbcdb590d97fb54314481bb Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 3 Jun 2021 11:10:04 +0200 Subject: [PATCH 025/102] chore: reword the changeset Signed-off-by: blam --- .changeset/proud-bottles-dream.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/.changeset/proud-bottles-dream.md b/.changeset/proud-bottles-dream.md index 42cff9db46..d9c7259758 100644 --- a/.changeset/proud-bottles-dream.md +++ b/.changeset/proud-bottles-dream.md @@ -2,4 +2,31 @@ '@backstage/plugin-tech-radar': minor --- -Migrating the Tech Radar to support using `ApiRefs` to go fetch data +Migrating the Tech Radar to support using `ApiRefs` to load custom data. + +If you had a `getData` function, you'll now need to encapsulate that logic in a class that can override the `techRadarApiRef`. + +```ts +// app/src/lib/MyClient.ts +import { + TechRadarApi, + TechRadarLoaderResponse, +} from '@backstage/plugin-tech-radar'; + +class MyOwnClient implements TechRadarApi { + async load(): Promise { + // here's where you would put you logic to load the response that was previously passed into getData + } +} + +// app/src/apis.ts +import { MyOwnClient } from './lib/MyClient'; +import { techRadarApiRef } from '@backstage/plugin-tech-radar'; + +export const apis: AnyApiFactory[] = [ + /* + ... + */ + createApiFactory(techRadarApiRef, new MyOwnClient()), +]; +``` From 76f99a1a0be84c8bd90937ccb4cb4d39a1568c3a Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Thu, 3 Jun 2021 11:13:45 +0200 Subject: [PATCH 026/102] Export createScaffolderFieldExtension to enable the creation of new field extensions Signed-off-by: Dominik Henneke --- .changeset/quick-dancers-approve.md | 5 +++++ plugins/scaffolder/src/index.ts | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 .changeset/quick-dancers-approve.md diff --git a/.changeset/quick-dancers-approve.md b/.changeset/quick-dancers-approve.md new file mode 100644 index 0000000000..929996afda --- /dev/null +++ b/.changeset/quick-dancers-approve.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Export createScaffolderFieldExtension to enable the creation of new field extensions diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index 2e0fd96f08..c49edd2f60 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -16,7 +16,10 @@ export { scaffolderApiRef, ScaffolderClient } from './api'; export type { ScaffolderApi } from './api'; -export { ScaffolderFieldExtensions } from './extensions'; +export { + createScaffolderFieldExtension, + ScaffolderFieldExtensions, +} from './extensions'; export { EntityPickerFieldExtension, OwnerPickerFieldExtension, From 7ec8ea240f21f3abd2119bdfe8e54100842ba1f2 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Thu, 3 Jun 2021 11:22:31 +0200 Subject: [PATCH 027/102] Update changeset Signed-off-by: Dominik Henneke --- .changeset/quick-dancers-approve.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/quick-dancers-approve.md b/.changeset/quick-dancers-approve.md index 929996afda..82c91a61f9 100644 --- a/.changeset/quick-dancers-approve.md +++ b/.changeset/quick-dancers-approve.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder': patch --- -Export createScaffolderFieldExtension to enable the creation of new field extensions +Export `createScaffolderFieldExtension` to enable the creation of new field extensions. From 5312c627f87b14d7cc5ceec2b1079d718747b714 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 3 Jun 2021 11:34:13 +0200 Subject: [PATCH 028/102] chore: missed updating some of those tests Signed-off-by: blam --- .../src/components/RadarPage.test.tsx | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/plugins/tech-radar/src/components/RadarPage.test.tsx b/plugins/tech-radar/src/components/RadarPage.test.tsx index 918015001b..b5169fadd9 100644 --- a/plugins/tech-radar/src/components/RadarPage.test.tsx +++ b/plugins/tech-radar/src/components/RadarPage.test.tsx @@ -27,6 +27,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import GetBBoxPolyfill from '../utils/polyfills/getBBox'; import { RadarPage } from './RadarPage'; +import { TechRadarLoaderResponse, techRadarApiRef, TechRadarApi } from '../api'; describe('RadarPage', () => { beforeAll(() => { @@ -36,6 +37,17 @@ describe('RadarPage', () => { afterAll(() => { GetBBoxPolyfill.remove(); }); + class MockClient implements TechRadarApi { + async load(): Promise { + return { + entries: [], + quadrants: [], + rings: [], + }; + } + } + + const mockClient = new MockClient(); it('should render a progress bar', async () => { jest.useFakeTimers(); @@ -49,7 +61,9 @@ describe('RadarPage', () => { const { getByTestId, queryByTestId } = render( wrapInTestApp( - + + + , ), ); @@ -72,7 +86,9 @@ describe('RadarPage', () => { const { getByText, getByTestId } = await renderInTestApp( - + + + , ); @@ -86,18 +102,25 @@ describe('RadarPage', () => { it('should call the errorApi if load fails', async () => { const errorApi = new MockErrorApi({ collect: true }); - const techRadarLoadFail = () => - Promise.reject(new Error('404 Page Not Found')); + + jest + .spyOn(mockClient, 'load') + .mockRejectedValue(new Error('404 Page Not Found')); + const techRadarProps = { width: 1200, height: 800, - getData: techRadarLoadFail, svgProps: { 'data-testid': 'tech-radar-svg' }, }; const { queryByTestId } = await renderInTestApp( - + , From eca9b7e8915c0d1511ce8203d3201f77f07d78e6 Mon Sep 17 00:00:00 2001 From: Ben Lambert Date: Thu, 3 Jun 2021 11:35:52 +0200 Subject: [PATCH 029/102] Update plugins/tech-radar/README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: blam --- plugins/tech-radar/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/tech-radar/README.md b/plugins/tech-radar/README.md index 2781acabd9..35c510ddd3 100644 --- a/plugins/tech-radar/README.md +++ b/plugins/tech-radar/README.md @@ -72,7 +72,7 @@ export interface TechRadarPageProps { ### How do I load in my own data? -The `TechRadar` plugin uses the `TechRadarApiRef` to get a client which implements the `TechRadarApi` interface. The default sample one is located here: https://github.com/backstage/backstage/blob/master/plugins/tech-radar/src/sample.ts. To load your own data, you'll need to provide a class that implements the `TechRadarApi` and override the `TechRadarApiRef` in the `app/src/apis.ts`. +The `TechRadar` plugin uses the `techRadarApiRef` to get a client which implements the `TechRadarApi` interface. The default sample one is located [here](https://github.com/backstage/backstage/blob/master/plugins/tech-radar/src/sample.ts). To load your own data, you'll need to provide a class that implements the `TechRadarApi` and override the `techRadarApiRef` in the `app/src/apis.ts`. ```ts // app/src/lib/MyClient.ts From fa92d70fba62a81a61f9361b56c67dd0c6f1d0d6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 3 Jun 2021 10:04:56 +0000 Subject: [PATCH 030/102] Version Packages --- .changeset/beige-garlics-doubt.md | 5 --- .changeset/brave-lemons-hope.md | 32 ---------------- .changeset/brown-lobsters-enjoy.md | 5 --- .changeset/cool-poems-train.md | 5 --- .changeset/cyan-weeks-lie.md | 5 --- .changeset/early-colts-stare.md | 5 --- .changeset/eighty-rabbits-fail.md | 5 --- .changeset/little-bobcats-explode.md | 5 --- .changeset/little-eggs-change.md | 5 --- .changeset/long-jokes-end.md | 11 ------ .changeset/mean-boats-speak.md | 5 --- .changeset/mean-tigers-brake.md | 5 --- .changeset/poor-buttons-sparkle.md | 5 --- .changeset/proud-bottles-dream.md | 32 ---------------- .changeset/quick-dancers-approve.md | 5 --- .changeset/real-hats-fail.md | 5 --- .changeset/red-ducks-yawn.md | 5 --- .changeset/seven-badgers-marry.md | 5 --- .changeset/seven-ligers-watch.md | 5 --- .changeset/slimy-kids-attack.md | 5 --- .changeset/slimy-toys-fetch.md | 5 --- .changeset/smart-bugs-argue.md | 5 --- .changeset/strong-mails-drum.md | 5 --- .changeset/techdocs-fresh-and-clean.md | 6 --- .changeset/techdocs-nice-forks-flow.md | 6 --- .changeset/tender-months-count.md | 5 --- .changeset/thin-cougars-cheer.md | 7 ---- .changeset/unlucky-lemons-sip.md | 5 --- packages/app/CHANGELOG.md | 33 ++++++++++++++++ packages/app/package.json | 22 +++++------ packages/catalog-model/CHANGELOG.md | 6 +++ packages/catalog-model/package.json | 4 +- packages/cli/CHANGELOG.md | 11 ++++++ packages/cli/package.json | 10 ++--- packages/core-api/CHANGELOG.md | 10 +++++ packages/core-api/package.json | 10 ++--- packages/core-app-api/CHANGELOG.md | 12 ++++++ packages/core-app-api/package.json | 12 +++--- packages/core-components/CHANGELOG.md | 11 ++++++ packages/core-components/package.json | 12 +++--- packages/core-plugin-api/CHANGELOG.md | 10 +++++ packages/core-plugin-api/package.json | 8 ++-- packages/core/CHANGELOG.md | 10 +++++ packages/core/package.json | 10 ++--- packages/create-app/CHANGELOG.md | 38 ++++++++++++++++++ packages/create-app/package.json | 2 +- packages/dev-utils/CHANGELOG.md | 17 ++++++++ packages/dev-utils/package.json | 14 +++---- packages/integration/CHANGELOG.md | 6 +++ packages/integration/package.json | 6 +-- packages/techdocs-common/CHANGELOG.md | 12 ++++++ packages/techdocs-common/package.json | 8 ++-- packages/test-utils/CHANGELOG.md | 11 ++++++ packages/test-utils/package.json | 8 ++-- packages/theme/CHANGELOG.md | 6 +++ packages/theme/package.json | 4 +- plugins/api-docs/package.json | 8 ++-- plugins/badges/package.json | 8 ++-- plugins/bitrise/package.json | 8 ++-- plugins/catalog-backend/CHANGELOG.md | 13 +++++++ plugins/catalog-backend/package.json | 10 ++--- plugins/catalog-import/package.json | 8 ++-- plugins/catalog-react/CHANGELOG.md | 14 +++++++ plugins/catalog-react/package.json | 16 ++++---- plugins/catalog/CHANGELOG.md | 19 +++++++++ plugins/catalog/package.json | 18 ++++----- plugins/circleci/package.json | 8 ++-- plugins/cloudbuild/package.json | 8 ++-- plugins/code-coverage/package.json | 8 ++-- plugins/config-schema/package.json | 8 ++-- plugins/cost-insights/package.json | 8 ++-- plugins/explore/package.json | 8 ++-- plugins/fossa/package.json | 8 ++-- plugins/gcp-projects/package.json | 8 ++-- plugins/git-release-manager/package.json | 8 ++-- plugins/github-actions/package.json | 8 ++-- plugins/github-deployments/package.json | 8 ++-- plugins/gitops-profiles/package.json | 8 ++-- plugins/graphiql/package.json | 8 ++-- plugins/ilert/package.json | 8 ++-- plugins/jenkins/package.json | 8 ++-- plugins/kafka/package.json | 8 ++-- plugins/kubernetes/package.json | 8 ++-- plugins/lighthouse/package.json | 8 ++-- plugins/newrelic/package.json | 8 ++-- plugins/org/package.json | 8 ++-- plugins/pagerduty/package.json | 8 ++-- plugins/register-component/package.json | 8 ++-- plugins/rollbar/package.json | 8 ++-- plugins/scaffolder-backend/CHANGELOG.md | 10 +++++ plugins/scaffolder-backend/package.json | 10 ++--- plugins/scaffolder/CHANGELOG.md | 49 ++++++++++++++++++++++++ plugins/scaffolder/package.json | 18 ++++----- plugins/search/package.json | 8 ++-- plugins/sentry/package.json | 8 ++-- plugins/shortcuts/package.json | 8 ++-- plugins/sonarqube/package.json | 8 ++-- plugins/splunk-on-call/package.json | 8 ++-- plugins/tech-radar/CHANGELOG.md | 40 +++++++++++++++++++ plugins/tech-radar/package.json | 12 +++--- plugins/techdocs/CHANGELOG.md | 17 ++++++++ plugins/techdocs/package.json | 18 ++++----- plugins/todo/package.json | 8 ++-- plugins/user-settings/package.json | 8 ++-- plugins/welcome/package.json | 8 ++-- 105 files changed, 611 insertions(+), 460 deletions(-) delete mode 100644 .changeset/beige-garlics-doubt.md delete mode 100644 .changeset/brave-lemons-hope.md delete mode 100644 .changeset/brown-lobsters-enjoy.md delete mode 100644 .changeset/cool-poems-train.md delete mode 100644 .changeset/cyan-weeks-lie.md delete mode 100644 .changeset/early-colts-stare.md delete mode 100644 .changeset/eighty-rabbits-fail.md delete mode 100644 .changeset/little-bobcats-explode.md delete mode 100644 .changeset/little-eggs-change.md delete mode 100644 .changeset/long-jokes-end.md delete mode 100644 .changeset/mean-boats-speak.md delete mode 100644 .changeset/mean-tigers-brake.md delete mode 100644 .changeset/poor-buttons-sparkle.md delete mode 100644 .changeset/proud-bottles-dream.md delete mode 100644 .changeset/quick-dancers-approve.md delete mode 100644 .changeset/real-hats-fail.md delete mode 100644 .changeset/red-ducks-yawn.md delete mode 100644 .changeset/seven-badgers-marry.md delete mode 100644 .changeset/seven-ligers-watch.md delete mode 100644 .changeset/slimy-kids-attack.md delete mode 100644 .changeset/slimy-toys-fetch.md delete mode 100644 .changeset/smart-bugs-argue.md delete mode 100644 .changeset/strong-mails-drum.md delete mode 100644 .changeset/techdocs-fresh-and-clean.md delete mode 100644 .changeset/techdocs-nice-forks-flow.md delete mode 100644 .changeset/tender-months-count.md delete mode 100644 .changeset/thin-cougars-cheer.md delete mode 100644 .changeset/unlucky-lemons-sip.md create mode 100644 packages/core-app-api/CHANGELOG.md create mode 100644 packages/core-components/CHANGELOG.md create mode 100644 packages/core-plugin-api/CHANGELOG.md diff --git a/.changeset/beige-garlics-doubt.md b/.changeset/beige-garlics-doubt.md deleted file mode 100644 index 436abce45e..0000000000 --- a/.changeset/beige-garlics-doubt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/test-utils': patch ---- - -Fix a bug in `MockStorageApi` where it unhelpfully returned new empty buckets every single time diff --git a/.changeset/brave-lemons-hope.md b/.changeset/brave-lemons-hope.md deleted file mode 100644 index 64c0d59538..0000000000 --- a/.changeset/brave-lemons-hope.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Scaffolder Field Extensions are here! This means you'll now the ability to create custom field extensions and have the Scaffolder use the components when collecting information from the user in the wizard. By default we supply the `RepoUrlPicker` and the `OwnerPicker`, but if you want to provide some more extensions or override the built on ones you will have to change how the `ScaffolderPage` is wired up in your `app/src/App.tsx` to pass in the custom fields to the Scaffolder. - -You'll need to move this: - -```tsx -} /> -``` - -To this: - -```tsx -import { - ScaffolderFieldExtensions, - RepoUrlPickerFieldExtension, - OwnerPickerFieldExtension, -} from '@backstage/plugin-scaffolder'; - -}> - - - - - {/*Any other extensions you want to provide*/} - -; -``` - -More documentation on how to write your own `FieldExtensions` to follow. diff --git a/.changeset/brown-lobsters-enjoy.md b/.changeset/brown-lobsters-enjoy.md deleted file mode 100644 index bc6a1771d8..0000000000 --- a/.changeset/brown-lobsters-enjoy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Adjust the Webpack `devtool` module filename template to correctly resolve via the source maps to the source files. diff --git a/.changeset/cool-poems-train.md b/.changeset/cool-poems-train.md deleted file mode 100644 index fb4ba6621e..0000000000 --- a/.changeset/cool-poems-train.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Mark the `create-github-app` command as ready for use and reveal it in the command list. diff --git a/.changeset/cyan-weeks-lie.md b/.changeset/cyan-weeks-lie.md deleted file mode 100644 index 6b313d6912..0000000000 --- a/.changeset/cyan-weeks-lie.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Remove the trailing space from a the aria-label of the Template "CHOOSE" button. diff --git a/.changeset/early-colts-stare.md b/.changeset/early-colts-stare.md deleted file mode 100644 index 7eb279d67e..0000000000 --- a/.changeset/early-colts-stare.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Exclude core packages from package dependency diff. diff --git a/.changeset/eighty-rabbits-fail.md b/.changeset/eighty-rabbits-fail.md deleted file mode 100644 index 5f15d79ef4..0000000000 --- a/.changeset/eighty-rabbits-fail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Don't merge with previous from state on form changes. diff --git a/.changeset/little-bobcats-explode.md b/.changeset/little-bobcats-explode.md deleted file mode 100644 index c031fde505..0000000000 --- a/.changeset/little-bobcats-explode.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -invert logic for when to show type column diff --git a/.changeset/little-eggs-change.md b/.changeset/little-eggs-change.md deleted file mode 100644 index d19f17d665..0000000000 --- a/.changeset/little-eggs-change.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-plugin-api': patch ---- - -Made the deprecated `icon` fields compatible with the `IconComponent` type from `@backstage/core` in order to smooth out the migration. diff --git a/.changeset/long-jokes-end.md b/.changeset/long-jokes-end.md deleted file mode 100644 index 4c88d232fa..0000000000 --- a/.changeset/long-jokes-end.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@backstage/cli': patch -'@backstage/core-app-api': patch -'@backstage/core-components': patch -'@backstage/core-plugin-api': patch -'@backstage/dev-utils': patch -'@backstage/test-utils': patch -'@backstage/theme': patch ---- - -Update installation instructions in README. diff --git a/.changeset/mean-boats-speak.md b/.changeset/mean-boats-speak.md deleted file mode 100644 index 94b873039f..0000000000 --- a/.changeset/mean-boats-speak.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-model': patch ---- - -Remove the explicit connection from `EntityEnvelope` and `Entity`. diff --git a/.changeset/mean-tigers-brake.md b/.changeset/mean-tigers-brake.md deleted file mode 100644 index 07871a590b..0000000000 --- a/.changeset/mean-tigers-brake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/integration': patch ---- - -GitHub App ID can be a string too for environment variables otherwise it will fail validation diff --git a/.changeset/poor-buttons-sparkle.md b/.changeset/poor-buttons-sparkle.md deleted file mode 100644 index c73becd930..0000000000 --- a/.changeset/poor-buttons-sparkle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core': patch ---- - -Export `CheckboxTree` as we have a storybook for it diff --git a/.changeset/proud-bottles-dream.md b/.changeset/proud-bottles-dream.md deleted file mode 100644 index d9c7259758..0000000000 --- a/.changeset/proud-bottles-dream.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -'@backstage/plugin-tech-radar': minor ---- - -Migrating the Tech Radar to support using `ApiRefs` to load custom data. - -If you had a `getData` function, you'll now need to encapsulate that logic in a class that can override the `techRadarApiRef`. - -```ts -// app/src/lib/MyClient.ts -import { - TechRadarApi, - TechRadarLoaderResponse, -} from '@backstage/plugin-tech-radar'; - -class MyOwnClient implements TechRadarApi { - async load(): Promise { - // here's where you would put you logic to load the response that was previously passed into getData - } -} - -// app/src/apis.ts -import { MyOwnClient } from './lib/MyClient'; -import { techRadarApiRef } from '@backstage/plugin-tech-radar'; - -export const apis: AnyApiFactory[] = [ - /* - ... - */ - createApiFactory(techRadarApiRef, new MyOwnClient()), -]; -``` diff --git a/.changeset/quick-dancers-approve.md b/.changeset/quick-dancers-approve.md deleted file mode 100644 index 82c91a61f9..0000000000 --- a/.changeset/quick-dancers-approve.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Export `createScaffolderFieldExtension` to enable the creation of new field extensions. diff --git a/.changeset/real-hats-fail.md b/.changeset/real-hats-fail.md deleted file mode 100644 index f0f3c9aaa6..0000000000 --- a/.changeset/real-hats-fail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Sort `EntityTagPicker` entries. diff --git a/.changeset/red-ducks-yawn.md b/.changeset/red-ducks-yawn.md deleted file mode 100644 index 81d18a6eab..0000000000 --- a/.changeset/red-ducks-yawn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Add a `` field to the scaffolder to pick arbitrary entity kinds, like systems. diff --git a/.changeset/seven-badgers-marry.md b/.changeset/seven-badgers-marry.md deleted file mode 100644 index de9431c41b..0000000000 --- a/.changeset/seven-badgers-marry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -This adds a configuration option to the scaffolder plugin router, so we can allow for multiple `TaskWorkers`. Currently with only one `TaskWorker` you are limited to scaffolding one thing at a time. Set the `taskWorkers?: number` option in your scaffolder router to get more than 1 `TaskWorker` diff --git a/.changeset/seven-ligers-watch.md b/.changeset/seven-ligers-watch.md deleted file mode 100644 index 3742dca690..0000000000 --- a/.changeset/seven-ligers-watch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Add CLI output and README how to start app after create-app CLI diff --git a/.changeset/slimy-kids-attack.md b/.changeset/slimy-kids-attack.md deleted file mode 100644 index ddb7735571..0000000000 --- a/.changeset/slimy-kids-attack.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Tweak the `EntityListProvider` to do single-cycle updates diff --git a/.changeset/slimy-toys-fetch.md b/.changeset/slimy-toys-fetch.md deleted file mode 100644 index ac98de5325..0000000000 --- a/.changeset/slimy-toys-fetch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -Only validate the envelope for emitted entities, and defer full validation to when they get processed later on. diff --git a/.changeset/smart-bugs-argue.md b/.changeset/smart-bugs-argue.md deleted file mode 100644 index 373b419de7..0000000000 --- a/.changeset/smart-bugs-argue.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -chore(deps): bump `@spotify/eslint-config-react` from 9.0.0 to 10.0.0 diff --git a/.changeset/strong-mails-drum.md b/.changeset/strong-mails-drum.md deleted file mode 100644 index e1a069e953..0000000000 --- a/.changeset/strong-mails-drum.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Deprecated the `backend:build-image` command, pointing to the newer `backend:bundle` command. diff --git a/.changeset/techdocs-fresh-and-clean.md b/.changeset/techdocs-fresh-and-clean.md deleted file mode 100644 index 615294c3c4..0000000000 --- a/.changeset/techdocs-fresh-and-clean.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/techdocs-common': patch -'@backstage/plugin-techdocs': patch ---- - -Fixes multiple XSS and sanitization bypass vulnerabilities in TechDocs. diff --git a/.changeset/techdocs-nice-forks-flow.md b/.changeset/techdocs-nice-forks-flow.md deleted file mode 100644 index 3afc628a7d..0000000000 --- a/.changeset/techdocs-nice-forks-flow.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/techdocs-common': patch ---- - -Support parsing `mkdocs.yml` files that are using custom yaml tags like -`!!python/name:materialx.emoji.twemoji`. diff --git a/.changeset/tender-months-count.md b/.changeset/tender-months-count.md deleted file mode 100644 index 9f76186409..0000000000 --- a/.changeset/tender-months-count.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -Skip deletion of bootstrap location when running the new catalog. diff --git a/.changeset/thin-cougars-cheer.md b/.changeset/thin-cougars-cheer.md deleted file mode 100644 index 50c8a1ae05..0000000000 --- a/.changeset/thin-cougars-cheer.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-catalog': patch -'@backstage/plugin-catalog-backend': patch ---- - -Move dependency to `@microsoft/microsoft-graph-types` from `@backstage/plugin-catalog` -to `@backstage/plugin-catalog-backend`. diff --git a/.changeset/unlucky-lemons-sip.md b/.changeset/unlucky-lemons-sip.md deleted file mode 100644 index 585d091e65..0000000000 --- a/.changeset/unlucky-lemons-sip.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-api': patch ---- - -Made the `RouteRef*` types compatible with the ones exported from `@backstage/core-plugin-api`. diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index b909295afc..7f9e9e554d 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,38 @@ # example-app +## 0.2.31 + +### Patch Changes + +- Updated dependencies [497f4ce18] +- Updated dependencies [ee4eb5b40] +- Updated dependencies [84160313e] +- Updated dependencies [3772de8ba] +- Updated dependencies [7e7c71417] +- Updated dependencies [f430b6c6f] +- Updated dependencies [2a942cc9e] +- Updated dependencies [e7c5e4b30] +- Updated dependencies [ebe802bc4] +- Updated dependencies [1cf1d351f] +- Updated dependencies [90a505a77] +- Updated dependencies [76f99a1a0] +- Updated dependencies [deaba2e13] +- Updated dependencies [1157fa307] +- Updated dependencies [8e919a6f8] +- Updated dependencies [2305ab8fc] +- Updated dependencies [054bcd029] +- Updated dependencies [aad98c544] +- Updated dependencies [f46a9e82d] + - @backstage/plugin-scaffolder@0.9.7 + - @backstage/cli@0.6.14 + - @backstage/plugin-catalog@0.6.1 + - @backstage/theme@0.2.8 + - @backstage/catalog-model@0.8.1 + - @backstage/core@0.7.12 + - @backstage/plugin-tech-radar@0.4.0 + - @backstage/plugin-catalog-react@0.2.1 + - @backstage/plugin-techdocs@0.9.5 + ## 0.2.30 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index be53a2b992..03d87dc243 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,18 +1,18 @@ { "name": "example-app", - "version": "0.2.30", + "version": "0.2.31", "private": true, "bundled": true, "dependencies": { - "@backstage/catalog-model": "^0.8.0", - "@backstage/cli": "^0.6.13", - "@backstage/core": "^0.7.11", + "@backstage/catalog-model": "^0.8.1", + "@backstage/cli": "^0.6.14", + "@backstage/core": "^0.7.12", "@backstage/integration-react": "^0.1.2", "@backstage/plugin-api-docs": "^0.4.15", "@backstage/plugin-badges": "^0.2.2", - "@backstage/plugin-catalog": "^0.6.0", + "@backstage/plugin-catalog": "^0.6.1", "@backstage/plugin-catalog-import": "^0.5.8", - "@backstage/plugin-catalog-react": "^0.2.0", + "@backstage/plugin-catalog-react": "^0.2.1", "@backstage/plugin-circleci": "^0.2.15", "@backstage/plugin-cloudbuild": "^0.2.16", "@backstage/plugin-code-coverage": "^0.1.4", @@ -29,15 +29,15 @@ "@backstage/plugin-org": "^0.3.14", "@backstage/plugin-pagerduty": "0.3.5", "@backstage/plugin-rollbar": "^0.3.6", - "@backstage/plugin-scaffolder": "^0.9.6", + "@backstage/plugin-scaffolder": "^0.9.7", "@backstage/plugin-search": "^0.3.7", "@backstage/plugin-sentry": "^0.3.11", "@backstage/plugin-shortcuts": "^0.1.2", - "@backstage/plugin-tech-radar": "^0.3.11", - "@backstage/plugin-techdocs": "^0.9.4", + "@backstage/plugin-tech-radar": "^0.4.0", + "@backstage/plugin-techdocs": "^0.9.5", "@backstage/plugin-todo": "^0.1.2", "@backstage/plugin-user-settings": "^0.2.10", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@octokit/rest": "^18.5.3", @@ -56,7 +56,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/test-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.13", "@testing-library/cypress": "^7.0.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index 8598a9ec8c..94b4662a97 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/catalog-model +## 0.8.1 + +### Patch Changes + +- ebe802bc4: Remove the explicit connection from `EntityEnvelope` and `Entity`. + ## 0.8.0 ### Minor Changes diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index d924d895a6..edcffaa691 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-model", - "version": "0.8.0", + "version": "0.8.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -41,7 +41,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.6.13", + "@backstage/cli": "^0.6.14", "@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 5e5842fc16..da06bfea17 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/cli +## 0.6.14 + +### Patch Changes + +- ee4eb5b40: Adjust the Webpack `devtool` module filename template to correctly resolve via the source maps to the source files. +- 84160313e: Mark the `create-github-app` command as ready for use and reveal it in the command list. +- 7e7c71417: Exclude core packages from package dependency diff. +- e7c5e4b30: Update installation instructions in README. +- 2305ab8fc: chore(deps): bump `@spotify/eslint-config-react` from 9.0.0 to 10.0.0 +- 054bcd029: Deprecated the `backend:build-image` command, pointing to the newer `backend:bundle` command. + ## 0.6.13 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index e7126e5fe4..4485f045e2 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.6.13", + "version": "0.6.14", "private": false, "publishConfig": { "access": "public" @@ -120,10 +120,10 @@ "devDependencies": { "@backstage/backend-common": "^0.8.1", "@backstage/config": "^0.1.5", - "@backstage/core": "^0.7.11", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", - "@backstage/theme": "^0.2.7", + "@backstage/core": "^0.7.12", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", + "@backstage/theme": "^0.2.8", "@types/diff": "^5.0.0", "@types/express": "^4.17.6", "@types/fs-extra": "^9.0.1", diff --git a/packages/core-api/CHANGELOG.md b/packages/core-api/CHANGELOG.md index b6bc5060e4..c06e2b6789 100644 --- a/packages/core-api/CHANGELOG.md +++ b/packages/core-api/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/core-api +## 0.2.21 + +### Patch Changes + +- 0160678b1: Made the `RouteRef*` types compatible with the ones exported from `@backstage/core-plugin-api`. +- Updated dependencies [031ccd45f] +- Updated dependencies [e7c5e4b30] + - @backstage/core-plugin-api@0.1.1 + - @backstage/theme@0.2.8 + ## 0.2.20 ### Patch Changes diff --git a/packages/core-api/package.json b/packages/core-api/package.json index 99a9cd2b0f..1c3e707a29 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-api", "description": "Internal Core API used by Backstage plugins and apps", - "version": "0.2.20", + "version": "0.2.21", "private": false, "publishConfig": { "access": "public", @@ -30,8 +30,8 @@ }, "dependencies": { "@backstage/config": "^0.1.4", - "@backstage/core-plugin-api": "^0.1.0", - "@backstage/theme": "^0.2.6", + "@backstage/core-plugin-api": "^0.1.1", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@types/react": "^16.9", @@ -43,8 +43,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/test-utils": "^0.1.13", "@backstage/test-utils-core": "^0.1.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md new file mode 100644 index 0000000000..f98cf7eeb1 --- /dev/null +++ b/packages/core-app-api/CHANGELOG.md @@ -0,0 +1,12 @@ +# @backstage/core-app-api + +## 0.1.1 + +### Patch Changes + +- e7c5e4b30: Update installation instructions in README. +- Updated dependencies [031ccd45f] +- Updated dependencies [e7c5e4b30] + - @backstage/core-plugin-api@0.1.1 + - @backstage/core-components@0.1.1 + - @backstage/theme@0.2.8 diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 296a80ce94..ddb598ce9b 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-app-api", "description": "Core app API used by Backstage apps", - "version": "0.1.0", + "version": "0.1.1", "private": false, "publishConfig": { "access": "public", @@ -29,10 +29,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.1.0", + "@backstage/core-components": "^0.1.1", "@backstage/config": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.0", - "@backstage/theme": "^0.2.3", + "@backstage/core-plugin-api": "^0.1.1", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@types/react": "^16.9", @@ -44,8 +44,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.6.3", - "@backstage/test-utils": "^0.1.8", + "@backstage/cli": "^0.6.14", + "@backstage/test-utils": "^0.1.13", "@backstage/test-utils-core": "^0.1.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md new file mode 100644 index 0000000000..93941494c8 --- /dev/null +++ b/packages/core-components/CHANGELOG.md @@ -0,0 +1,11 @@ +# @backstage/core-components + +## 0.1.1 + +### Patch Changes + +- e7c5e4b30: Update installation instructions in README. +- Updated dependencies [031ccd45f] +- Updated dependencies [e7c5e4b30] + - @backstage/core-plugin-api@0.1.1 + - @backstage/theme@0.2.8 diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 003b4bdac6..1ce4adf2b7 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-components", "description": "Core components used by Backstage plugins and apps", - "version": "0.1.0", + "version": "0.1.1", "private": false, "publishConfig": { "access": "public", @@ -30,9 +30,9 @@ }, "dependencies": { "@backstage/config": "^0.1.5", - "@backstage/core-plugin-api": "^0.1.0", + "@backstage/core-plugin-api": "^0.1.1", "@backstage/errors": "^0.1.1", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -70,9 +70,9 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/core-app-api": "^0.1.0", - "@backstage/cli": "^0.6.11", - "@backstage/test-utils": "^0.1.11", + "@backstage/core-app-api": "^0.1.1", + "@backstage/cli": "^0.6.14", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/packages/core-plugin-api/CHANGELOG.md b/packages/core-plugin-api/CHANGELOG.md new file mode 100644 index 0000000000..fcb4b00a4b --- /dev/null +++ b/packages/core-plugin-api/CHANGELOG.md @@ -0,0 +1,10 @@ +# @backstage/core-plugin-api + +## 0.1.1 + +### Patch Changes + +- 031ccd45f: Made the deprecated `icon` fields compatible with the `IconComponent` type from `@backstage/core` in order to smooth out the migration. +- e7c5e4b30: Update installation instructions in README. +- Updated dependencies [e7c5e4b30] + - @backstage/theme@0.2.8 diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index cc0fe07872..64c2154ce9 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-plugin-api", "description": "Core API used by Backstage plugins", - "version": "0.1.0", + "version": "0.1.1", "private": false, "publishConfig": { "access": "public", @@ -30,7 +30,7 @@ }, "dependencies": { "@backstage/config": "^0.1.3", - "@backstage/theme": "^0.2.3", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@types/react": "^16.9", "history": "^5.0.0", @@ -41,8 +41,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.6.12", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/test-utils": "^0.1.13", "@backstage/test-utils-core": "^0.1.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 4c7d1ff5a7..d06dbab3d5 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/core +## 0.7.12 + +### Patch Changes + +- 1cf1d351f: Export `CheckboxTree` as we have a storybook for it +- Updated dependencies [e7c5e4b30] +- Updated dependencies [0160678b1] + - @backstage/theme@0.2.8 + - @backstage/core-api@0.2.21 + ## 0.7.11 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index d759110997..ee7e56741d 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.7.11", + "version": "0.7.12", "private": false, "publishConfig": { "access": "public", @@ -30,9 +30,9 @@ }, "dependencies": { "@backstage/config": "^0.1.5", - "@backstage/core-api": "^0.2.20", + "@backstage/core-api": "^0.2.21", "@backstage/errors": "^0.1.1", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -71,8 +71,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index e2759e7583..5d8e1c3d71 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,43 @@ # @backstage/create-app +## 0.3.24 + +### Patch Changes + +- 1ddf551f4: Add CLI output and README how to start app after create-app CLI +- Updated dependencies [7af9cef07] +- Updated dependencies [497f4ce18] +- Updated dependencies [ee4eb5b40] +- Updated dependencies [84160313e] +- Updated dependencies [3772de8ba] +- Updated dependencies [7e7c71417] +- Updated dependencies [f430b6c6f] +- Updated dependencies [2a942cc9e] +- Updated dependencies [e7c5e4b30] +- Updated dependencies [ebe802bc4] +- Updated dependencies [1cf1d351f] +- Updated dependencies [90a505a77] +- Updated dependencies [76f99a1a0] +- Updated dependencies [1157fa307] +- Updated dependencies [6fe1567a7] +- Updated dependencies [e7a5a3474] +- Updated dependencies [2305ab8fc] +- Updated dependencies [054bcd029] +- Updated dependencies [aad98c544] +- Updated dependencies [63a432e9c] +- Updated dependencies [f46a9e82d] + - @backstage/test-utils@0.1.13 + - @backstage/plugin-scaffolder@0.9.7 + - @backstage/cli@0.6.14 + - @backstage/plugin-catalog@0.6.1 + - @backstage/theme@0.2.8 + - @backstage/catalog-model@0.8.1 + - @backstage/core@0.7.12 + - @backstage/plugin-tech-radar@0.4.0 + - @backstage/plugin-scaffolder-backend@0.11.5 + - @backstage/plugin-catalog-backend@0.10.1 + - @backstage/plugin-techdocs@0.9.5 + ## 0.3.23 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index e0780ec2c1..a0fb9e6d4a 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.23", + "version": "0.3.24", "private": false, "publishConfig": { "access": "public" diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 6390650897..6f8b164371 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/dev-utils +## 0.1.17 + +### Patch Changes + +- e7c5e4b30: Update installation instructions in README. +- Updated dependencies [7af9cef07] +- Updated dependencies [e7c5e4b30] +- Updated dependencies [ebe802bc4] +- Updated dependencies [1cf1d351f] +- Updated dependencies [deaba2e13] +- Updated dependencies [8e919a6f8] + - @backstage/test-utils@0.1.13 + - @backstage/theme@0.2.8 + - @backstage/catalog-model@0.8.1 + - @backstage/core@0.7.12 + - @backstage/plugin-catalog-react@0.2.1 + ## 0.1.16 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 5812543a8b..91c34c8ef2 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.16", + "version": "0.1.17", "private": false, "publishConfig": { "access": "public", @@ -29,12 +29,12 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.7.11", - "@backstage/catalog-model": "^0.8.0", + "@backstage/core": "^0.7.12", + "@backstage/catalog-model": "^0.8.1", "@backstage/integration-react": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/test-utils": "^0.1.12", - "@backstage/theme": "^0.2.3", + "@backstage/plugin-catalog-react": "^0.2.1", + "@backstage/test-utils": "^0.1.13", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@testing-library/jest-dom": "^5.10.1", @@ -48,7 +48,7 @@ "react-router-dom": "6.0.0-beta.0" }, "devDependencies": { - "@backstage/cli": "^0.6.13", + "@backstage/cli": "^0.6.14", "@types/jest": "^26.0.7", "@types/node": "^14.14.32" }, diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index 9c1f25ded3..14b1d75441 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/integration +## 0.5.5 + +### Patch Changes + +- 49d7ec169: GitHub App ID can be a string too for environment variables otherwise it will fail validation + ## 0.5.4 ### Patch Changes diff --git a/packages/integration/package.json b/packages/integration/package.json index ab4a2055dd..1220493eea 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration", - "version": "0.5.4", + "version": "0.5.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -37,9 +37,9 @@ "luxon": "^1.25.0" }, "devDependencies": { - "@backstage/cli": "^0.6.13", + "@backstage/cli": "^0.6.14", "@backstage/config-loader": "^0.6.3", - "@backstage/test-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.13", "@types/jest": "^26.0.7", "@types/luxon": "^1.25.0", "msw": "^0.21.2" diff --git a/packages/techdocs-common/CHANGELOG.md b/packages/techdocs-common/CHANGELOG.md index 0f45c7cebe..3d38612851 100644 --- a/packages/techdocs-common/CHANGELOG.md +++ b/packages/techdocs-common/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/techdocs-common +## 0.6.4 + +### Patch Changes + +- aad98c544: Fixes multiple XSS and sanitization bypass vulnerabilities in TechDocs. +- 090594755: Support parsing `mkdocs.yml` files that are using custom yaml tags like + `!!python/name:materialx.emoji.twemoji`. +- Updated dependencies [ebe802bc4] +- Updated dependencies [49d7ec169] + - @backstage/catalog-model@0.8.1 + - @backstage/integration@0.5.5 + ## 0.6.3 ### Patch Changes diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json index 0e7b2efd3d..d1ec21060b 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.6.3", + "version": "0.6.4", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -39,10 +39,10 @@ "@azure/identity": "^1.2.2", "@azure/storage-blob": "^12.4.0", "@backstage/backend-common": "^0.8.1", - "@backstage/catalog-model": "^0.8.0", + "@backstage/catalog-model": "^0.8.1", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.4", + "@backstage/integration": "^0.5.5", "@google-cloud/storage": "^5.6.0", "@types/express": "^4.17.6", "aws-sdk": "^2.840.0", @@ -60,7 +60,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.6.13", + "@backstage/cli": "^0.6.14", "@types/fs-extra": "^9.0.5", "@types/git-url-parse": "^9.0.0", "@types/js-yaml": "^4.0.0", diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index 7fe7efe490..6ff92e79ae 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/test-utils +## 0.1.13 + +### Patch Changes + +- 7af9cef07: Fix a bug in `MockStorageApi` where it unhelpfully returned new empty buckets every single time +- e7c5e4b30: Update installation instructions in README. +- Updated dependencies [e7c5e4b30] +- Updated dependencies [0160678b1] + - @backstage/theme@0.2.8 + - @backstage/core-api@0.2.21 + ## 0.1.12 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 429d616572..8f906965b4 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils", "description": "Utilities to test Backstage plugins and apps.", - "version": "0.1.12", + "version": "0.1.13", "private": false, "publishConfig": { "access": "public", @@ -29,9 +29,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-api": "^0.2.19", + "@backstage/core-api": "^0.2.21", "@backstage/test-utils-core": "^0.1.1", - "@backstage/theme": "^0.2.3", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", @@ -45,7 +45,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.6.12", + "@backstage/cli": "^0.6.14", "@types/jest": "^26.0.7", "@types/node": "^14.14.32" }, diff --git a/packages/theme/CHANGELOG.md b/packages/theme/CHANGELOG.md index e70364016d..e19e044977 100644 --- a/packages/theme/CHANGELOG.md +++ b/packages/theme/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/theme +## 0.2.8 + +### Patch Changes + +- e7c5e4b30: Update installation instructions in README. + ## 0.2.7 ### Patch Changes diff --git a/packages/theme/package.json b/packages/theme/package.json index f93c793ff8..2575af6c97 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.7", + "version": "0.2.8", "private": false, "publishConfig": { "access": "public", @@ -31,7 +31,7 @@ "@material-ui/core": "^4.11.0" }, "devDependencies": { - "@backstage/cli": "^0.6.10" + "@backstage/cli": "^0.6.14" }, "files": [ "dist" diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index a64553b4fc..54588303e1 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -33,7 +33,7 @@ "@backstage/catalog-model": "^0.8.0", "@backstage/core": "^0.7.11", "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-icons/font": "^1.0.2", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -49,9 +49,9 @@ "swagger-ui-react": "^3.37.2" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/badges/package.json b/plugins/badges/package.json index 56896fb379..23c914c9ab 100644 --- a/plugins/badges/package.json +++ b/plugins/badges/package.json @@ -24,7 +24,7 @@ "@backstage/core": "^0.7.11", "@backstage/errors": "^0.1.1", "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -34,9 +34,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index 5181bd106b..8f3fa016c6 100644 --- a/plugins/bitrise/package.json +++ b/plugins/bitrise/package.json @@ -23,7 +23,7 @@ "@backstage/catalog-model": "^0.8.0", "@backstage/core": "^0.7.11", "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -37,9 +37,9 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 1fef6c7143..fe0a72a902 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend +## 0.10.1 + +### Patch Changes + +- e7a5a3474: Only validate the envelope for emitted entities, and defer full validation to when they get processed later on. +- 63a432e9c: Skip deletion of bootstrap location when running the new catalog. +- f46a9e82d: Move dependency to `@microsoft/microsoft-graph-types` from `@backstage/plugin-catalog` + to `@backstage/plugin-catalog-backend`. +- Updated dependencies [ebe802bc4] +- Updated dependencies [49d7ec169] + - @backstage/catalog-model@0.8.1 + - @backstage/integration@0.5.5 + ## 0.10.0 ### Minor Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index cacb8839c2..ca9c95409c 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "0.10.0", + "version": "0.10.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,10 +32,10 @@ "@azure/msal-node": "^1.0.0-beta.3", "@backstage/backend-common": "^0.8.1", "@backstage/catalog-client": "^0.3.12", - "@backstage/catalog-model": "^0.8.0", + "@backstage/catalog-model": "^0.8.1", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.4", + "@backstage/integration": "^0.5.5", "@backstage/plugin-search-backend-node": "^0.1.4", "@backstage/search-common": "^0.1.1", "@microsoft/microsoft-graph-types": "^1.25.0", @@ -65,8 +65,8 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/test-utils": "^0.1.13", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", "@types/lodash": "^4.14.151", diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 7b4278393f..e2db1968d8 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -36,7 +36,7 @@ "@backstage/integration": "^0.5.4", "@backstage/integration-react": "^0.1.2", "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -53,9 +53,9 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index 4eb61c43a9..16f2d3e1b1 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-react +## 0.2.1 + +### Patch Changes + +- deaba2e13: Sort `EntityTagPicker` entries. +- 8e919a6f8: Tweak the `EntityListProvider` to do single-cycle updates +- Updated dependencies [031ccd45f] +- Updated dependencies [e7c5e4b30] +- Updated dependencies [ebe802bc4] +- Updated dependencies [1cf1d351f] + - @backstage/core-plugin-api@0.1.1 + - @backstage/catalog-model@0.8.1 + - @backstage/core@0.7.12 + ## 0.2.0 ### Minor Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 3d8dcd22c1..eb01aba8af 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-react", - "version": "0.2.0", + "version": "0.2.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,9 +29,9 @@ }, "dependencies": { "@backstage/catalog-client": "^0.3.12", - "@backstage/catalog-model": "^0.8.0", - "@backstage/core": "^0.7.11", - "@backstage/core-plugin-api": "^0.1.0", + "@backstage/catalog-model": "^0.8.1", + "@backstage/core": "^0.7.12", + "@backstage/core-plugin-api": "^0.1.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -43,10 +43,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/core": "^0.7.11", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.11", + "@backstage/cli": "^0.6.14", + "@backstage/core": "^0.7.12", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 3c40fa33c8..c04e205715 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-catalog +## 0.6.1 + +### Patch Changes + +- 2a942cc9e: invert logic for when to show type column +- f46a9e82d: Move dependency to `@microsoft/microsoft-graph-types` from `@backstage/plugin-catalog` + to `@backstage/plugin-catalog-backend`. +- Updated dependencies [e7c5e4b30] +- Updated dependencies [ebe802bc4] +- Updated dependencies [49d7ec169] +- Updated dependencies [1cf1d351f] +- Updated dependencies [deaba2e13] +- Updated dependencies [8e919a6f8] + - @backstage/theme@0.2.8 + - @backstage/catalog-model@0.8.1 + - @backstage/integration@0.5.5 + - @backstage/core@0.7.12 + - @backstage/plugin-catalog-react@0.2.1 + ## 0.6.0 ### Minor Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 0836efc829..0133ae3bbb 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "0.6.0", + "version": "0.6.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,13 +31,13 @@ }, "dependencies": { "@backstage/catalog-client": "^0.3.12", - "@backstage/catalog-model": "^0.8.0", - "@backstage/core": "^0.7.11", + "@backstage/catalog-model": "^0.8.1", + "@backstage/core": "^0.7.12", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.4", + "@backstage/integration": "^0.5.5", "@backstage/integration-react": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/theme": "^0.2.7", + "@backstage/plugin-catalog-react": "^0.2.1", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -53,9 +53,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index b3633e7cf9..20cac43ac9 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -34,7 +34,7 @@ "@backstage/catalog-model": "^0.8.0", "@backstage/core": "^0.7.11", "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -50,9 +50,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index 686d88334c..03408ac6c1 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -33,7 +33,7 @@ "@backstage/catalog-model": "^0.8.0", "@backstage/plugin-catalog-react": "^0.2.0", "@backstage/core": "^0.7.11", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -47,9 +47,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index 9c69015640..a7d7126646 100644 --- a/plugins/code-coverage/package.json +++ b/plugins/code-coverage/package.json @@ -25,7 +25,7 @@ "@backstage/core": "^0.7.11", "@backstage/errors": "^0.1.1", "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/styles": "^4.11.0", @@ -39,9 +39,9 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index d5fdbe2ee8..12e1c65664 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -23,7 +23,7 @@ "@backstage/config": "^0.1.4", "@backstage/core": "^0.7.11", "@backstage/errors": "^0.1.1", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -34,9 +34,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index 3069ffb322..8901ea96eb 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -32,7 +32,7 @@ "dependencies": { "@backstage/config": "^0.1.5", "@backstage/core": "^0.7.11", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -55,9 +55,9 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/explore/package.json b/plugins/explore/package.json index 4511b13716..a05ed2185d 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -34,7 +34,7 @@ "@backstage/core": "^0.7.11", "@backstage/plugin-catalog-react": "^0.2.0", "@backstage/plugin-explore-react": "^0.0.5", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -45,9 +45,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index 2cdfd3d946..fe0057b200 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -35,7 +35,7 @@ "@backstage/core": "^0.7.11", "@backstage/errors": "^0.1.1", "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -47,9 +47,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index 5e40d20f64..affb0bf0c6 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/core": "^0.7.11", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -41,9 +41,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index b744223a9f..1c075c420c 100644 --- a/plugins/git-release-manager/package.json +++ b/plugins/git-release-manager/package.json @@ -22,7 +22,7 @@ "dependencies": { "@backstage/core": "^0.7.11", "@backstage/integration": "^0.5.3", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "recharts": "^1.8.5", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -36,9 +36,9 @@ "react": "^16.13.1" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@types/recharts": "^1.8.15", "@testing-library/react-hooks": "^3.4.2", diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index f787533504..51678940bf 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -36,7 +36,7 @@ "@backstage/plugin-catalog-react": "^0.2.0", "@backstage/core": "^0.7.11", "@backstage/integration": "^0.5.4", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -50,9 +50,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json index 821d9f440e..aba58f4410 100644 --- a/plugins/github-deployments/package.json +++ b/plugins/github-deployments/package.json @@ -26,7 +26,7 @@ "@backstage/integration": "^0.5.4", "@backstage/integration-react": "^0.1.2", "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -37,9 +37,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index e5113e1696..5116c797b8 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@backstage/core": "^0.7.11", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -42,9 +42,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index 44ac82c3f4..c6929fe208 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -33,7 +33,7 @@ "dependencies": { "@backstage/core": "^0.7.11", "@backstage/core-plugin-api": "^0.1.0", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -44,9 +44,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json index 195696fca3..cc3a212c42 100644 --- a/plugins/ilert/package.json +++ b/plugins/ilert/package.json @@ -24,7 +24,7 @@ "@backstage/core": "^0.7.11", "@backstage/errors": "^0.1.1", "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@date-io/luxon": "1.x", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -37,9 +37,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index abec18f34d..ada9815baf 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -34,7 +34,7 @@ "@backstage/catalog-model": "^0.8.0", "@backstage/core": "^0.7.11", "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -47,9 +47,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index 99564fa95c..ce8bc35bd9 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -23,7 +23,7 @@ "@backstage/catalog-model": "^0.8.0", "@backstage/core": "^0.7.11", "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -33,9 +33,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.4.2", diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index f45d431c2e..5f3ca01e37 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -35,7 +35,7 @@ "@backstage/core": "^0.7.11", "@backstage/plugin-catalog-react": "^0.2.0", "@backstage/plugin-kubernetes-common": "^0.1.1", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@kubernetes/client-node": "^0.14.0", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -49,9 +49,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.4.2", diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 672e90e210..ead00d9d7d 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -35,7 +35,7 @@ "@backstage/config": "^0.1.4", "@backstage/core": "^0.7.11", "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -46,9 +46,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index b343964b81..e5fa8d2e88 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@backstage/core": "^0.7.11", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -41,9 +41,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/org/package.json b/plugins/org/package.json index 4b04fff827..c9195d5709 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -24,7 +24,7 @@ "@backstage/core": "^0.7.11", "@backstage/core-api": "^0.2.20", "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -35,9 +35,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index 32bddc57ac..81ac6e23fc 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -33,7 +33,7 @@ "@backstage/catalog-model": "^0.8.0", "@backstage/core": "^0.7.11", "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -46,9 +46,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index beb49db85a..80b0e96a50 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -33,7 +33,7 @@ "@backstage/catalog-model": "^0.8.0", "@backstage/core": "^0.7.11", "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -45,9 +45,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 000621e73d..06ce51eada 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -34,7 +34,7 @@ "@backstage/catalog-model": "^0.8.0", "@backstage/core": "^0.7.11", "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -47,9 +47,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index a82a278993..4d48ea5713 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-backend +## 0.11.5 + +### Patch Changes + +- 6fe1567a7: This adds a configuration option to the scaffolder plugin router, so we can allow for multiple `TaskWorkers`. Currently with only one `TaskWorker` you are limited to scaffolding one thing at a time. Set the `taskWorkers?: number` option in your scaffolder router to get more than 1 `TaskWorker` +- Updated dependencies [ebe802bc4] +- Updated dependencies [49d7ec169] + - @backstage/catalog-model@0.8.1 + - @backstage/integration@0.5.5 + ## 0.11.4 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index f20c43b4fa..10dfaea709 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "0.11.4", + "version": "0.11.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "dependencies": { "@backstage/backend-common": "^0.8.1", "@backstage/catalog-client": "^0.3.12", - "@backstage/catalog-model": "^0.8.0", + "@backstage/catalog-model": "^0.8.1", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.4", + "@backstage/integration": "^0.5.5", "@gitbeaker/core": "^29.2.0", "@gitbeaker/node": "^29.2.0", "@octokit/rest": "^18.5.3", @@ -64,8 +64,8 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/test-utils": "^0.1.13", "@types/fs-extra": "^9.0.1", "@types/mock-fs": "^4.13.0", "@types/supertest": "^2.0.8", diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 0d0ff58754..25f27aac81 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,54 @@ # @backstage/plugin-scaffolder +## 0.9.7 + +### Patch Changes + +- 497f4ce18: Scaffolder Field Extensions are here! This means you'll now the ability to create custom field extensions and have the Scaffolder use the components when collecting information from the user in the wizard. By default we supply the `RepoUrlPicker` and the `OwnerPicker`, but if you want to provide some more extensions or override the built on ones you will have to change how the `ScaffolderPage` is wired up in your `app/src/App.tsx` to pass in the custom fields to the Scaffolder. + + You'll need to move this: + + ```tsx + } /> + ``` + + To this: + + ```tsx + import { + ScaffolderFieldExtensions, + RepoUrlPickerFieldExtension, + OwnerPickerFieldExtension, + } from '@backstage/plugin-scaffolder'; + + }> + + + + + {/*Any other extensions you want to provide*/} + + ; + ``` + + More documentation on how to write your own `FieldExtensions` to follow. + +- 3772de8ba: Remove the trailing space from a the aria-label of the Template "CHOOSE" button. +- f430b6c6f: Don't merge with previous from state on form changes. +- 76f99a1a0: Export `createScaffolderFieldExtension` to enable the creation of new field extensions. +- 1157fa307: Add a `` field to the scaffolder to pick arbitrary entity kinds, like systems. +- Updated dependencies [e7c5e4b30] +- Updated dependencies [ebe802bc4] +- Updated dependencies [49d7ec169] +- Updated dependencies [1cf1d351f] +- Updated dependencies [deaba2e13] +- Updated dependencies [8e919a6f8] + - @backstage/theme@0.2.8 + - @backstage/catalog-model@0.8.1 + - @backstage/integration@0.5.5 + - @backstage/core@0.7.12 + - @backstage/plugin-catalog-react@0.2.1 + ## 0.9.6 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index f1ad6c50bc..dceb207ed1 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "0.9.6", + "version": "0.9.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,14 +31,14 @@ }, "dependencies": { "@backstage/catalog-client": "^0.3.12", - "@backstage/catalog-model": "^0.8.0", + "@backstage/catalog-model": "^0.8.1", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", - "@backstage/core": "^0.7.11", - "@backstage/integration": "^0.5.4", + "@backstage/core": "^0.7.12", + "@backstage/integration": "^0.5.5", "@backstage/integration-react": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/theme": "^0.2.7", + "@backstage/plugin-catalog-react": "^0.2.1", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -61,9 +61,9 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/search/package.json b/plugins/search/package.json index aca7ba3868..5483eda4c2 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -33,7 +33,7 @@ "@backstage/catalog-model": "^0.8.0", "@backstage/plugin-catalog-react": "^0.2.0", "@backstage/search-common": "^0.1.1", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -44,9 +44,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index aa55f7db84..b028963bc3 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -34,7 +34,7 @@ "@backstage/catalog-model": "^0.8.0", "@backstage/core": "^0.7.11", "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -46,9 +46,9 @@ "timeago.js": "^4.0.2" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json index 3ff01b1fcc..c778cad93a 100644 --- a/plugins/shortcuts/package.json +++ b/plugins/shortcuts/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@backstage/core": "^0.7.11", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -35,9 +35,9 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index ac8f5a190f..0f4f800d57 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -35,7 +35,7 @@ "@backstage/catalog-model": "^0.8.0", "@backstage/plugin-catalog-react": "^0.2.0", "@backstage/core": "^0.7.11", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -47,9 +47,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index f873f75c1e..22e4811f8b 100644 --- a/plugins/splunk-on-call/package.json +++ b/plugins/splunk-on-call/package.json @@ -33,7 +33,7 @@ "@backstage/catalog-model": "^0.8.0", "@backstage/core": "^0.7.11", "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -45,9 +45,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/tech-radar/CHANGELOG.md b/plugins/tech-radar/CHANGELOG.md index 64f255adb1..5a0592b078 100644 --- a/plugins/tech-radar/CHANGELOG.md +++ b/plugins/tech-radar/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/plugin-tech-radar +## 0.4.0 + +### Minor Changes + +- 90a505a77: Migrating the Tech Radar to support using `ApiRefs` to load custom data. + + If you had a `getData` function, you'll now need to encapsulate that logic in a class that can override the `techRadarApiRef`. + + ```ts + // app/src/lib/MyClient.ts + import { + TechRadarApi, + TechRadarLoaderResponse, + } from '@backstage/plugin-tech-radar'; + + class MyOwnClient implements TechRadarApi { + async load(): Promise { + // here's where you would put you logic to load the response that was previously passed into getData + } + } + + // app/src/apis.ts + import { MyOwnClient } from './lib/MyClient'; + import { techRadarApiRef } from '@backstage/plugin-tech-radar'; + + export const apis: AnyApiFactory[] = [ + /* + ... + */ + createApiFactory(techRadarApiRef, new MyOwnClient()), + ]; + ``` + +### Patch Changes + +- Updated dependencies [e7c5e4b30] +- Updated dependencies [1cf1d351f] + - @backstage/theme@0.2.8 + - @backstage/core@0.7.12 + ## 0.3.11 ### Patch Changes diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 423d516b6e..73134fb45d 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.11", + "version": "0.4.0", "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.7.11", - "@backstage/theme": "^0.2.7", + "@backstage/core": "^0.7.12", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -43,9 +43,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 056dfe55a0..103ec1c5d7 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-techdocs +## 0.9.5 + +### Patch Changes + +- aad98c544: Fixes multiple XSS and sanitization bypass vulnerabilities in TechDocs. +- Updated dependencies [e7c5e4b30] +- Updated dependencies [ebe802bc4] +- Updated dependencies [49d7ec169] +- Updated dependencies [1cf1d351f] +- Updated dependencies [deaba2e13] +- Updated dependencies [8e919a6f8] + - @backstage/theme@0.2.8 + - @backstage/catalog-model@0.8.1 + - @backstage/integration@0.5.5 + - @backstage/core@0.7.12 + - @backstage/plugin-catalog-react@0.2.1 + ## 0.9.4 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 06b4f551de..747c44488d 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "0.9.4", + "version": "0.9.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,12 +32,12 @@ }, "dependencies": { "@backstage/config": "^0.1.5", - "@backstage/catalog-model": "^0.8.0", - "@backstage/core": "^0.7.11", - "@backstage/integration": "^0.5.4", + "@backstage/catalog-model": "^0.8.1", + "@backstage/core": "^0.7.12", + "@backstage/integration": "^0.5.5", "@backstage/integration-react": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/theme": "^0.2.7", + "@backstage/plugin-catalog-react": "^0.2.1", + "@backstage/theme": "^0.2.8", "@backstage/errors": "^0.1.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -51,9 +51,9 @@ "sanitize-html": "^2.3.2" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/todo/package.json b/plugins/todo/package.json index 3dd07a9cc7..88eb60b3ce 100644 --- a/plugins/todo/package.json +++ b/plugins/todo/package.json @@ -30,7 +30,7 @@ "@backstage/core": "^0.7.11", "@backstage/errors": "^0.1.1", "@backstage/plugin-catalog-react": "^0.2.0", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -39,9 +39,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index b425584c78..f9e1bfe5f3 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/core": "^0.7.11", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -41,9 +41,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index 8d17b57bcc..e803b37059 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/core": "^0.7.11", - "@backstage/theme": "^0.2.7", + "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -41,9 +41,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.13", - "@backstage/dev-utils": "^0.1.16", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.6.14", + "@backstage/dev-utils": "^0.1.17", + "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", From d9cd644a1fe584e50154da1acf593925c6a67cdd Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Tue, 18 May 2021 19:38:52 +0200 Subject: [PATCH 031/102] search context Signed-off-by: Emma Indal Signed-off-by: Eric Peterson --- .../SearchContext/SearchContext.tsx | 68 +++++++++++++++++++ .../src/components/SearchContext/index.tsx | 17 +++++ 2 files changed, 85 insertions(+) create mode 100644 plugins/search/src/components/SearchContext/SearchContext.tsx create mode 100644 plugins/search/src/components/SearchContext/index.tsx diff --git a/plugins/search/src/components/SearchContext/SearchContext.tsx b/plugins/search/src/components/SearchContext/SearchContext.tsx new file mode 100644 index 0000000000..ca1a82cde0 --- /dev/null +++ b/plugins/search/src/components/SearchContext/SearchContext.tsx @@ -0,0 +1,68 @@ +/* + * 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 React, { + PropsWithChildren, + createContext, + useContext, + useState, +} from 'react'; +import { useAsync } from 'react-use'; +import { useApi } from '@backstage/core'; +import { SearchQuery, SearchResultSet } from '@backstage/search-common'; +import { searchApiRef } from '../../apis'; +import { AsyncState } from 'react-use/lib/useAsync'; + +type SearchContextValue = { + resultState: AsyncState; + queryState: SearchQuery; + setQueryState: React.Dispatch>; +}; + +const SearchContext = createContext({} as SearchContextValue); + +export const SearchContextProvider = ({ + initialState = { + term: '', + pageCursor: '', + types: ['*'], + }, + children, +}: PropsWithChildren<{ initialState?: any }>) => { + const searchApi = useApi(searchApiRef); + const [queryState, setQueryState] = useState(initialState); + + const resultState = useAsync( + () => + searchApi._alphaPerformSearch({ + term: queryState.term, + pageCursor: queryState.pageCursor, + }), + [queryState.term], + ); + + const value: SearchContextValue = { resultState, queryState, setQueryState }; + + return ; +}; + +export const useSearch = () => { + const context = useContext(SearchContext); + if (context === undefined) { + throw new Error('useSearch must be used within a SearchContextProvider'); + } + return context; +}; diff --git a/plugins/search/src/components/SearchContext/index.tsx b/plugins/search/src/components/SearchContext/index.tsx new file mode 100644 index 0000000000..b45c169879 --- /dev/null +++ b/plugins/search/src/components/SearchContext/index.tsx @@ -0,0 +1,17 @@ +/* + * 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 { SearchContextProvider, useSearch } from './SearchContext'; From 0f7799795348f3e3c1bdfa8120de5a02ae461a5f Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Tue, 18 May 2021 19:41:10 +0200 Subject: [PATCH 032/102] wip new search page components Signed-off-by: Emma Indal Signed-off-by: Eric Peterson --- .../SearchBarNext/SearchBarNext.tsx | 89 +++++++++++++++++++ .../src/components/SearchBarNext/index.tsx | 17 ++++ .../SearchResultNext/SearchResultNext.tsx | 67 ++++++++++++++ .../src/components/SearchResultNext/index.tsx | 17 ++++ 4 files changed, 190 insertions(+) create mode 100644 plugins/search/src/components/SearchBarNext/SearchBarNext.tsx create mode 100644 plugins/search/src/components/SearchBarNext/index.tsx create mode 100644 plugins/search/src/components/SearchResultNext/SearchResultNext.tsx create mode 100644 plugins/search/src/components/SearchResultNext/index.tsx diff --git a/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx b/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx new file mode 100644 index 0000000000..1e4a59ac8b --- /dev/null +++ b/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx @@ -0,0 +1,89 @@ +/* + * 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 React, { useEffect } from 'react'; +import { useDebounce } from 'react-use'; +import { useQueryParamState } from '@backstage/core'; +import { Paper, InputBase, IconButton, makeStyles } from '@material-ui/core'; +import SearchIcon from '@material-ui/icons/Search'; +import ClearButton from '@material-ui/icons/Clear'; +import { useSearch } from '../SearchContext'; + +const useStyles = makeStyles(() => ({ + root: { + display: 'flex', + alignItems: 'center', + }, + input: { + flex: 1, + }, +})); + +export const SearchBarNext = () => { + const classes = useStyles(); + const { + queryState: { term }, + setQueryState, + } = useSearch(); + + const [queryString, setQueryString] = useQueryParamState('query'); + + useEffect(() => { + setQueryState({ term: queryString ?? '', pageCursor: '' }); + }, [queryString, setQueryState]); + + useDebounce( + () => { + setQueryString(term); + }, + 200, + [term], + ); + + const handleSearch = (event: React.ChangeEvent | React.FormEvent) => { + event.preventDefault(); + setQueryState({ + term: (event.target as HTMLInputElement).value, + pageCursor: '', + }); + }; + + const handleClearSearchBar = () => { + setQueryState({ term: '', pageCursor: '' }); + }; + + return ( + handleSearch(e)} + className={classes.root} + > + + + + handleSearch(e)} + inputProps={{ 'aria-label': 'search backstage' }} + /> + handleClearSearchBar()}> + + + + ); +}; diff --git a/plugins/search/src/components/SearchBarNext/index.tsx b/plugins/search/src/components/SearchBarNext/index.tsx new file mode 100644 index 0000000000..20ba7d9c55 --- /dev/null +++ b/plugins/search/src/components/SearchBarNext/index.tsx @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { SearchBarNext } from './SearchBarNext'; diff --git a/plugins/search/src/components/SearchResultNext/SearchResultNext.tsx b/plugins/search/src/components/SearchResultNext/SearchResultNext.tsx new file mode 100644 index 0000000000..5b2133e573 --- /dev/null +++ b/plugins/search/src/components/SearchResultNext/SearchResultNext.tsx @@ -0,0 +1,67 @@ +/* + * 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 { EmptyState, Link, Progress } from '@backstage/core'; +import { Divider, List, ListItem, ListItemText } from '@material-ui/core'; +import { Alert } from '@material-ui/lab'; +import React from 'react'; + +import { useSearch } from '../SearchContext'; + +const DefaultResultListItem = ({ result }: any) => { + return ( + + + + + + + ); +}; + +export const SearchResultNext = () => { + const { + resultState: { loading, error, value }, + } = useSearch(); + + if (loading) { + return ; + } + if (error) { + return ( + + Error encountered while fetching search results. {error.toString()} + + ); + } + + if (!value) { + return ; + } + + return ( + + {value.results.map(result => ( + <> + + + ))} + + ); +}; diff --git a/plugins/search/src/components/SearchResultNext/index.tsx b/plugins/search/src/components/SearchResultNext/index.tsx new file mode 100644 index 0000000000..1a21491289 --- /dev/null +++ b/plugins/search/src/components/SearchResultNext/index.tsx @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { SearchResultNext } from './SearchResultNext'; From f0ffd95326393de2bd450dfdbbf95320c8003cfb Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Tue, 18 May 2021 19:41:49 +0200 Subject: [PATCH 033/102] refactor search page using new components Signed-off-by: Emma Indal Signed-off-by: Eric Peterson --- .../SearchPageNext/SearchPageNext.tsx | 73 ++++++------------- 1 file changed, 24 insertions(+), 49 deletions(-) diff --git a/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx b/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx index a6655e37ac..ce9e203cf1 100644 --- a/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx +++ b/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx @@ -13,59 +13,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - Content, - Header, - Lifecycle, - Page, - useQueryParamState, -} from '@backstage/core'; + +import React from 'react'; +import { Content, Header, Lifecycle, Page } from '@backstage/core'; import { Grid } from '@material-ui/core'; -import React, { useEffect, useState } from 'react'; -import { useDebounce } from 'react-use'; -import { SearchBar } from '../SearchBar'; -import { SearchResult } from '../SearchResult'; +import { SearchBarNext } from '../SearchBarNext'; +import { SearchResultNext } from '../SearchResultNext'; +import { SearchContextProvider } from '../SearchContext'; export const SearchPageNext = () => { - const [queryString, setQueryString] = useQueryParamState('query'); - const [searchQuery, setSearchQuery] = useState(queryString ?? ''); - - const handleSearch = (event: React.ChangeEvent) => { - event.preventDefault(); - setSearchQuery(event.target.value); - }; - - useEffect(() => setSearchQuery(queryString ?? ''), [queryString]); - - useDebounce( - () => { - setQueryString(searchQuery); - }, - 200, - [searchQuery], - ); - - const handleClearSearchBar = () => { - setSearchQuery(''); - }; - return ( - -

} /> - - - - + + +
} /> + + + + + + + {/* filter component should be rendered here */} +

filter

+
+ + +
- - - - -
- + + + ); }; From 83ef71d3f70b87e77f726c74073fd27180a1da5f Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Tue, 18 May 2021 19:42:05 +0200 Subject: [PATCH 034/102] export components Signed-off-by: Emma Indal Signed-off-by: Eric Peterson --- plugins/search/src/components/index.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/search/src/components/index.tsx b/plugins/search/src/components/index.tsx index f571c61373..ae0bc2cdca 100644 --- a/plugins/search/src/components/index.tsx +++ b/plugins/search/src/components/index.tsx @@ -16,7 +16,10 @@ export * from './Filters'; export * from './SearchBar'; +export * from './SearchBarNext'; export * from './SearchPage'; export * from './SearchPageNext'; export * from './SearchResult'; +export * from './SearchResultNext'; export * from './SidebarSearch'; +export * from './SearchContext'; From b26eaef405f9d4297935b6d6c20588c3b23e4916 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Wed, 19 May 2021 12:12:11 +0200 Subject: [PATCH 035/102] new filters component Signed-off-by: Emma Indal Signed-off-by: Eric Peterson --- .../FiltersNext/FiltersButtonNext.tsx | 56 +++++++ .../components/FiltersNext/FiltersNext.tsx | 139 ++++++++++++++++++ .../src/components/FiltersNext/index.tsx | 18 +++ .../SearchPageNext/SearchPageNext.tsx | 12 +- plugins/search/src/components/index.tsx | 1 + 5 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 plugins/search/src/components/FiltersNext/FiltersButtonNext.tsx create mode 100644 plugins/search/src/components/FiltersNext/FiltersNext.tsx create mode 100644 plugins/search/src/components/FiltersNext/index.tsx diff --git a/plugins/search/src/components/FiltersNext/FiltersButtonNext.tsx b/plugins/search/src/components/FiltersNext/FiltersButtonNext.tsx new file mode 100644 index 0000000000..8da6e26028 --- /dev/null +++ b/plugins/search/src/components/FiltersNext/FiltersButtonNext.tsx @@ -0,0 +1,56 @@ +/* + * 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 React from 'react'; +import FilterListIcon from '@material-ui/icons/FilterList'; +import { makeStyles, IconButton, Typography } from '@material-ui/core'; + +const useStyles = makeStyles(theme => ({ + filters: { + width: '250px', + display: 'flex', + }, + icon: { + margin: theme.spacing(-1, 0, 0, 0), + }, +})); + +type FiltersButtonProps = { + numberOfSelectedFilters: number; + handleToggleFilters: () => void; +}; + +export const FiltersButtonNext = ({ + numberOfSelectedFilters, + handleToggleFilters, +}: FiltersButtonProps) => { + const classes = useStyles(); + + return ( +
+ + + + + Filters ({numberOfSelectedFilters ? numberOfSelectedFilters : 0}) + +
+ ); +}; diff --git a/plugins/search/src/components/FiltersNext/FiltersNext.tsx b/plugins/search/src/components/FiltersNext/FiltersNext.tsx new file mode 100644 index 0000000000..770fe8078d --- /dev/null +++ b/plugins/search/src/components/FiltersNext/FiltersNext.tsx @@ -0,0 +1,139 @@ +/* + * 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 React from 'react'; +import { + makeStyles, + Typography, + Card, + CardContent, + Select, + Checkbox, + List, + ListItem, + ListItemText, + MenuItem, +} from '@material-ui/core'; + +const useStyles = makeStyles(theme => ({ + filters: { + background: 'transparent', + boxShadow: '0px 0px 0px 0px', + }, + // checkbox: { + // padding: theme.spacing(0, 1, 0, 1), + // }, + // dropdown: { + // width: '100%', + // }, +})); + +export type FilterOptions = { + kind: Array; + lifecycle: Array; +}; + +type FiltersProps = { + definitions: FilterDefinition[]; +}; + +type ValuedFilterProps = { + fieldName: string; + values: string[]; +}; + +export enum FilterType { + CHECKBOX = 'checkbox', + SELECT = 'select', +} + +export type NewFilterDefinition = { + component: any; + props: any; +}; + +export type FilterDefinition = { + field: string; + type: FilterType; + values: string[]; +}; + +const CheckBoxFilter = ({ fieldName, values }: ValuedFilterProps) => { + return ( + + {fieldName} + + {values.map((value: string) => ( + {}}> + + + + ))} + + + ); +}; + +const SelectFilter = ({ fieldName, values }: ValuedFilterProps) => { + return ( + + {fieldName} + + + ); +}; + +export const FiltersNext = ({ definitions }: FiltersProps) => { + const classes = useStyles(); + + return ( + + {definitions.map(definition => { + switch (definition.type) { + case 'checkbox': + return ( + + ); + case 'select': + return ( + + ); + default: + return null; + } + })} + + ); +}; diff --git a/plugins/search/src/components/FiltersNext/index.tsx b/plugins/search/src/components/FiltersNext/index.tsx new file mode 100644 index 0000000000..574a31e93f --- /dev/null +++ b/plugins/search/src/components/FiltersNext/index.tsx @@ -0,0 +1,18 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { FiltersButtonNext } from './FiltersButtonNext'; +export { FiltersNext, FilterType } from './FiltersNext'; diff --git a/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx b/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx index ce9e203cf1..ca942aabb1 100644 --- a/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx +++ b/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx @@ -20,6 +20,15 @@ import { Grid } from '@material-ui/core'; import { SearchBarNext } from '../SearchBarNext'; import { SearchResultNext } from '../SearchResultNext'; import { SearchContextProvider } from '../SearchContext'; +import { FiltersNext, FilterType } from '../FiltersNext'; + +const exampleFilterDefinition = [ + { + field: 'lifecycle', + type: FilterType.CHECKBOX, + values: ['exerpimental', 'production'], + }, +]; export const SearchPageNext = () => { return ( @@ -32,8 +41,7 @@ export const SearchPageNext = () => { - {/* filter component should be rendered here */} -

filter

+
diff --git a/plugins/search/src/components/index.tsx b/plugins/search/src/components/index.tsx index ae0bc2cdca..27491d6c93 100644 --- a/plugins/search/src/components/index.tsx +++ b/plugins/search/src/components/index.tsx @@ -15,6 +15,7 @@ */ export * from './Filters'; +export * from './FiltersNext'; export * from './SearchBar'; export * from './SearchBarNext'; export * from './SearchPage'; From fa753e4edd6a147caf87d3701c91e72ddfa24e67 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Wed, 19 May 2021 21:48:25 +0200 Subject: [PATCH 036/102] split out state in search context Signed-off-by: Emma Indal Signed-off-by: Eric Peterson --- .../SearchContext/SearchContext.tsx | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/plugins/search/src/components/SearchContext/SearchContext.tsx b/plugins/search/src/components/SearchContext/SearchContext.tsx index ca1a82cde0..b0bcff1f25 100644 --- a/plugins/search/src/components/SearchContext/SearchContext.tsx +++ b/plugins/search/src/components/SearchContext/SearchContext.tsx @@ -22,14 +22,21 @@ import React, { } from 'react'; import { useAsync } from 'react-use'; import { useApi } from '@backstage/core'; -import { SearchQuery, SearchResultSet } from '@backstage/search-common'; +import { SearchResultSet } from '@backstage/search-common'; import { searchApiRef } from '../../apis'; import { AsyncState } from 'react-use/lib/useAsync'; +import { JsonObject } from '@backstage/config'; type SearchContextValue = { - resultState: AsyncState; - queryState: SearchQuery; - setQueryState: React.Dispatch>; + result: AsyncState; + term: string; + setTerm: React.Dispatch>; + types: string[]; + setTypes: React.Dispatch>; + filters: JsonObject; + setFilters: React.Dispatch>; + pageCursor: string; + setPageCursor: React.Dispatch>; }; const SearchContext = createContext({} as SearchContextValue); @@ -38,23 +45,39 @@ export const SearchContextProvider = ({ initialState = { term: '', pageCursor: '', + filters: {}, types: ['*'], }, children, }: PropsWithChildren<{ initialState?: any }>) => { const searchApi = useApi(searchApiRef); - const [queryState, setQueryState] = useState(initialState); + const [pageCursor, setPageCursor] = useState(initialState.pageCursor); + const [filters, setFilters] = useState(initialState.filters); + const [term, setTerm] = useState(initialState.term); + const [types, setTypes] = useState(initialState.types); - const resultState = useAsync( + const result = useAsync( () => searchApi._alphaPerformSearch({ - term: queryState.term, - pageCursor: queryState.pageCursor, + term, + filters, + pageCursor, + types, }), - [queryState.term], + [term, filters, types, pageCursor], ); - const value: SearchContextValue = { resultState, queryState, setQueryState }; + const value: SearchContextValue = { + result, + filters, + setFilters, + term, + setTerm, + types, + setTypes, + pageCursor, + setPageCursor, + }; return ; }; From 982849c5b7b9aa926afac05d1ffd421ffdc31cc0 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Wed, 19 May 2021 21:49:23 +0200 Subject: [PATCH 037/102] wip new filter component Signed-off-by: Emma Indal Signed-off-by: Eric Peterson --- .../components/FiltersNext/FiltersNext.tsx | 108 ++++++++++++++---- 1 file changed, 85 insertions(+), 23 deletions(-) diff --git a/plugins/search/src/components/FiltersNext/FiltersNext.tsx b/plugins/search/src/components/FiltersNext/FiltersNext.tsx index 770fe8078d..45de27b032 100644 --- a/plugins/search/src/components/FiltersNext/FiltersNext.tsx +++ b/plugins/search/src/components/FiltersNext/FiltersNext.tsx @@ -14,33 +14,41 @@ * limitations under the License. */ -import React from 'react'; import { - makeStyles, - Typography, Card, CardContent, - Select, Checkbox, + FormControl, + InputLabel, List, + MenuItem, ListItem, ListItemText, - MenuItem, + makeStyles, + Select, } from '@material-ui/core'; +import React from 'react'; +import { useSearch } from '../SearchContext'; -const useStyles = makeStyles(theme => ({ +const useFilterStyles = makeStyles({ filters: { background: 'transparent', boxShadow: '0px 0px 0px 0px', }, - // checkbox: { - // padding: theme.spacing(0, 1, 0, 1), - // }, - // dropdown: { - // width: '100%', - // }, +}); + +const useCheckBoxStyles = makeStyles(theme => ({ + checkbox: { + padding: theme.spacing(0, 1, 0, 1), + }, })); +const useSelectStyles = makeStyles({ + select: { + width: '100%', + }, +}); + export type FilterOptions = { kind: Array; lifecycle: Array; @@ -72,13 +80,45 @@ export type FilterDefinition = { }; const CheckBoxFilter = ({ fieldName, values }: ValuedFilterProps) => { + const { filters, setFilters } = useSearch(); + const classes = useCheckBoxStyles(); + + const setCheckboxFilter = (filter: string) => { + const newFilters = filters; + const currentValues = newFilters[fieldName] as string[]; + + if (!filter) return; + + if (!currentValues) { + setFilters({ ...filters, [fieldName]: [filter] }); + } else if (!currentValues?.includes(filter)) { + setFilters({ + ...filters, + [fieldName]: [...currentValues, filter], + }); + } else { + const filterToDelete = currentValues.find(value => value === filter); + if (filterToDelete) { + currentValues.splice(currentValues.indexOf(filterToDelete), 1); + + setFilters({ ...filters, [fieldName]: currentValues }); + } + } + }; return ( - {fieldName} - + {fieldName} + {values.map((value: string) => ( - {}}> + setCheckboxFilter(value)} + > { }; const SelectFilter = ({ fieldName, values }: ValuedFilterProps) => { + const { filters, setFilters } = useSearch(); + const classes = useSelectStyles(); + + const setSelectFilter = (filter: string) => { + const newFilters = filters; + if (newFilters[fieldName] && filter === '') { + delete newFilters[fieldName]; + setFilters({ newFilters }); + } else { + setFilters({ ...filters, [fieldName]: filter as string }); + } + }; + return ( - {fieldName} - ) => + setSelectFilter(e?.target?.value) + } + > + + All - ))} - + {values.map((value: string) => ( + {value} + ))} + + ); }; export const FiltersNext = ({ definitions }: FiltersProps) => { - const classes = useStyles(); + const classes = useFilterStyles(); return ( From af6a718e825cda8b6ed706d69702499089bb4706 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Wed, 19 May 2021 21:50:59 +0200 Subject: [PATCH 038/102] change term presence in lunr query Signed-off-by: Emma Indal Signed-off-by: Eric Peterson --- plugins/search-backend-node/src/engines/LunrSearchEngine.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts index cc8c5bb982..bbe7eb7905 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts @@ -44,6 +44,7 @@ export class LunrSearchEngine implements SearchEngine { types, }: SearchQuery): ConcreteLunrQuery => { let lunrQueryFilters; + const lunrTerm = term ? `+${term}` : ''; if (filters) { lunrQueryFilters = Object.entries(filters) .map(([key, value]) => ` +${key}:${value}`) @@ -51,7 +52,7 @@ export class LunrSearchEngine implements SearchEngine { } return { - lunrQueryString: `${term}${lunrQueryFilters || ''}`, + lunrQueryString: `${lunrTerm}${lunrQueryFilters || ''}`, documentTypes: types || ['*'], }; }; From d57dc2dce9e4b74236d0699e11eea2454c437d79 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Wed, 19 May 2021 21:51:25 +0200 Subject: [PATCH 039/102] fixups Signed-off-by: Emma Indal Signed-off-by: Eric Peterson --- .../src/components/FiltersNext/FiltersNext.tsx | 2 ++ .../components/SearchBarNext/SearchBarNext.tsx | 17 ++++++----------- .../SearchPageNext/SearchPageNext.tsx | 11 ++++++++--- .../SearchResultNext/SearchResultNext.tsx | 9 +++++---- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/plugins/search/src/components/FiltersNext/FiltersNext.tsx b/plugins/search/src/components/FiltersNext/FiltersNext.tsx index 45de27b032..6b833dbf5a 100644 --- a/plugins/search/src/components/FiltersNext/FiltersNext.tsx +++ b/plugins/search/src/components/FiltersNext/FiltersNext.tsx @@ -181,6 +181,7 @@ export const FiltersNext = ({ definitions }: FiltersProps) => { case 'checkbox': return ( @@ -188,6 +189,7 @@ export const FiltersNext = ({ definitions }: FiltersProps) => { case 'select': return ( diff --git a/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx b/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx index 1e4a59ac8b..bec296c42a 100644 --- a/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx +++ b/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx @@ -34,16 +34,13 @@ const useStyles = makeStyles(() => ({ export const SearchBarNext = () => { const classes = useStyles(); - const { - queryState: { term }, - setQueryState, - } = useSearch(); + const { term, setTerm, setPageCursor } = useSearch(); const [queryString, setQueryString] = useQueryParamState('query'); useEffect(() => { - setQueryState({ term: queryString ?? '', pageCursor: '' }); - }, [queryString, setQueryState]); + setTerm(queryString ?? ''); + }, [queryString, setTerm]); useDebounce( () => { @@ -55,14 +52,12 @@ export const SearchBarNext = () => { const handleSearch = (event: React.ChangeEvent | React.FormEvent) => { event.preventDefault(); - setQueryState({ - term: (event.target as HTMLInputElement).value, - pageCursor: '', - }); + setTerm((event.target as HTMLInputElement).value as string); }; const handleClearSearchBar = () => { - setQueryState({ term: '', pageCursor: '' }); + setTerm(''); + setPageCursor(''); }; return ( diff --git a/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx b/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx index ca942aabb1..441ca2b6a2 100644 --- a/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx +++ b/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx @@ -22,11 +22,16 @@ import { SearchResultNext } from '../SearchResultNext'; import { SearchContextProvider } from '../SearchContext'; import { FiltersNext, FilterType } from '../FiltersNext'; -const exampleFilterDefinition = [ +const defaultFilterDefinitions = [ + { + field: 'kind', + type: FilterType.SELECT, + values: ['Component', 'Template'], + }, { field: 'lifecycle', type: FilterType.CHECKBOX, - values: ['exerpimental', 'production'], + values: ['experimental', 'production'], }, ]; @@ -41,7 +46,7 @@ export const SearchPageNext = () => { - + diff --git a/plugins/search/src/components/SearchResultNext/SearchResultNext.tsx b/plugins/search/src/components/SearchResultNext/SearchResultNext.tsx index 5b2133e573..af772c4967 100644 --- a/plugins/search/src/components/SearchResultNext/SearchResultNext.tsx +++ b/plugins/search/src/components/SearchResultNext/SearchResultNext.tsx @@ -37,7 +37,7 @@ const DefaultResultListItem = ({ result }: any) => { export const SearchResultNext = () => { const { - resultState: { loading, error, value }, + result: { loading, error, value }, } = useSearch(); if (loading) { @@ -58,9 +58,10 @@ export const SearchResultNext = () => { return ( {value.results.map(result => ( - <> - - + ))} ); From 5bd3611406d96af0c6df2ac747c14483a1284403 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 3 Jun 2021 16:25:24 +0200 Subject: [PATCH 040/102] add backtage config package to deps Signed-off-by: Emma Indal Signed-off-by: Eric Peterson --- plugins/search/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/search/package.json b/plugins/search/package.json index 5483eda4c2..cc42bb05ed 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -33,6 +33,7 @@ "@backstage/catalog-model": "^0.8.0", "@backstage/plugin-catalog-react": "^0.2.0", "@backstage/search-common": "^0.1.1", + "@backstage/config": "^0.1.5", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", From 94d1d852e1b23a32fda665ac23ed15582c81ff64 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Fri, 21 May 2021 09:41:55 +0200 Subject: [PATCH 041/102] update lunr search query filters Signed-off-by: Emma Indal Signed-off-by: Eric Peterson --- .../src/engines/LunrSearchEngine.ts | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts index bbe7eb7905..77bba3ff6c 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts @@ -47,7 +47,27 @@ export class LunrSearchEngine implements SearchEngine { const lunrTerm = term ? `+${term}` : ''; if (filters) { lunrQueryFilters = Object.entries(filters) - .map(([key, value]) => ` +${key}:${value}`) + .map(([field, value]) => { + // Require that the given field has the given value (with +). + if (['string', 'number', 'boolean'].includes(typeof value)) { + return ` +${field}:${value}`; + } + + // Illustrate how multi-value filters could work. + if (Array.isArray(value)) { + // But warn that Lurn supports this poorly. + this.logger.warn( + `Non-scalar filter value used for field ${field}. Consider using a different Search Engine for better results.`, + ); + return ` ${value.map(v => { + return `${field}:${v}`; + })}`; + } + + // Log a warning or something about unknown filter value + this.logger.warn(`Unknown filter type used on field ${field}`); + return ''; + }) .join(''); } From b614549b3ed054aa225bf8a6ede6506665986cd9 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Fri, 21 May 2021 09:44:08 +0200 Subject: [PATCH 042/102] export component as extensions Signed-off-by: Emma Indal Signed-off-by: Eric Peterson --- .../FiltersButtonNext.tsx | 0 .../SearchFiltersNext.tsx} | 2 +- .../index.tsx | 2 +- .../SearchPageNext/SearchPageNext.tsx | 4 +-- plugins/search/src/components/index.tsx | 2 +- plugins/search/src/index.ts | 3 ++ plugins/search/src/plugin.ts | 28 +++++++++++++++++++ 7 files changed, 36 insertions(+), 5 deletions(-) rename plugins/search/src/components/{FiltersNext => SearchFiltersNext}/FiltersButtonNext.tsx (100%) rename plugins/search/src/components/{FiltersNext/FiltersNext.tsx => SearchFiltersNext/SearchFiltersNext.tsx} (98%) rename plugins/search/src/components/{FiltersNext => SearchFiltersNext}/index.tsx (90%) diff --git a/plugins/search/src/components/FiltersNext/FiltersButtonNext.tsx b/plugins/search/src/components/SearchFiltersNext/FiltersButtonNext.tsx similarity index 100% rename from plugins/search/src/components/FiltersNext/FiltersButtonNext.tsx rename to plugins/search/src/components/SearchFiltersNext/FiltersButtonNext.tsx diff --git a/plugins/search/src/components/FiltersNext/FiltersNext.tsx b/plugins/search/src/components/SearchFiltersNext/SearchFiltersNext.tsx similarity index 98% rename from plugins/search/src/components/FiltersNext/FiltersNext.tsx rename to plugins/search/src/components/SearchFiltersNext/SearchFiltersNext.tsx index 6b833dbf5a..92c65f4a57 100644 --- a/plugins/search/src/components/FiltersNext/FiltersNext.tsx +++ b/plugins/search/src/components/SearchFiltersNext/SearchFiltersNext.tsx @@ -171,7 +171,7 @@ const SelectFilter = ({ fieldName, values }: ValuedFilterProps) => { ); }; -export const FiltersNext = ({ definitions }: FiltersProps) => { +export const SearchFiltersNext = ({ definitions }: FiltersProps) => { const classes = useFilterStyles(); return ( diff --git a/plugins/search/src/components/FiltersNext/index.tsx b/plugins/search/src/components/SearchFiltersNext/index.tsx similarity index 90% rename from plugins/search/src/components/FiltersNext/index.tsx rename to plugins/search/src/components/SearchFiltersNext/index.tsx index 574a31e93f..d45eadc79a 100644 --- a/plugins/search/src/components/FiltersNext/index.tsx +++ b/plugins/search/src/components/SearchFiltersNext/index.tsx @@ -15,4 +15,4 @@ */ export { FiltersButtonNext } from './FiltersButtonNext'; -export { FiltersNext, FilterType } from './FiltersNext'; +export { SearchFiltersNext, FilterType } from './SearchFiltersNext'; diff --git a/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx b/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx index 441ca2b6a2..691f01b2cb 100644 --- a/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx +++ b/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx @@ -20,7 +20,7 @@ import { Grid } from '@material-ui/core'; import { SearchBarNext } from '../SearchBarNext'; import { SearchResultNext } from '../SearchResultNext'; import { SearchContextProvider } from '../SearchContext'; -import { FiltersNext, FilterType } from '../FiltersNext'; +import { SearchFiltersNext, FilterType } from '../SearchFiltersNext'; const defaultFilterDefinitions = [ { @@ -46,7 +46,7 @@ export const SearchPageNext = () => { - + diff --git a/plugins/search/src/components/index.tsx b/plugins/search/src/components/index.tsx index 27491d6c93..aa9a443f21 100644 --- a/plugins/search/src/components/index.tsx +++ b/plugins/search/src/components/index.tsx @@ -15,7 +15,7 @@ */ export * from './Filters'; -export * from './FiltersNext'; +export * from './SearchFiltersNext'; export * from './SearchBar'; export * from './SearchBarNext'; export * from './SearchPage'; diff --git a/plugins/search/src/index.ts b/plugins/search/src/index.ts index 519d7fcf66..367b195f9d 100644 --- a/plugins/search/src/index.ts +++ b/plugins/search/src/index.ts @@ -20,6 +20,9 @@ export { searchPlugin as plugin, SearchPage, SearchPageNext, + SearchBarNext, + SearchResultNext, + SearchFiltersNext, } from './plugin'; export { Filters, diff --git a/plugins/search/src/plugin.ts b/plugins/search/src/plugin.ts index e3e953e07d..03322b33aa 100644 --- a/plugins/search/src/plugin.ts +++ b/plugins/search/src/plugin.ts @@ -19,6 +19,7 @@ import { createRouteRef, createRoutableExtension, discoveryApiRef, + createComponentExtension, } from '@backstage/core'; import { SearchClient, searchApiRef } from './apis'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; @@ -70,3 +71,30 @@ export const SearchPageNext = searchPlugin.provide( mountPoint: rootNextRouteRef, }), ); + +export const SearchBarNext = searchPlugin.provide( + createComponentExtension({ + component: { + lazy: () => + import('./components/SearchBarNext').then(m => m.SearchBarNext), + }, + }), +); + +export const SearchResultNext = searchPlugin.provide( + createComponentExtension({ + component: { + lazy: () => + import('./components/SearchResultNext').then(m => m.SearchResultNext), + }, + }), +); + +export const SearchFiltersNext = searchPlugin.provide( + createComponentExtension({ + component: { + lazy: () => + import('./components/SearchFiltersNext').then(m => m.SearchFiltersNext), + }, + }), +); From 8829e8228e9e8a0d693e349aad125d142f8cc657 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Fri, 21 May 2021 09:45:54 +0200 Subject: [PATCH 043/102] wip composable result list Signed-off-by: Emma Indal Signed-off-by: Eric Peterson --- packages/search-common/src/types.ts | 1 + .../src/engines/LunrSearchEngine.ts | 2 +- .../SearchResultNext/SearchResultNext.tsx | 47 ++++++++++++++++--- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/packages/search-common/src/types.ts b/packages/search-common/src/types.ts index daa5823424..c26ecdcb4c 100644 --- a/packages/search-common/src/types.ts +++ b/packages/search-common/src/types.ts @@ -23,6 +23,7 @@ export interface SearchQuery { } export interface SearchResult { + type: string; document: IndexableDocument; } diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts index 77bba3ff6c..3829693d4e 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts @@ -146,7 +146,7 @@ export class LunrSearchEngine implements SearchEngine { // Translate results into SearchResultSet const resultSet: SearchResultSet = { results: results.map(d => { - return { document: this.docStore[d.ref] }; + return { type: 'techdocs', document: this.docStore[d.ref] }; }), }; diff --git a/plugins/search/src/components/SearchResultNext/SearchResultNext.tsx b/plugins/search/src/components/SearchResultNext/SearchResultNext.tsx index af772c4967..53fc4ff17b 100644 --- a/plugins/search/src/components/SearchResultNext/SearchResultNext.tsx +++ b/plugins/search/src/components/SearchResultNext/SearchResultNext.tsx @@ -35,6 +35,21 @@ const DefaultResultListItem = ({ result }: any) => { ); }; +const TechDocsResultListItem = ({ result }: any) => { + return ( + + + + + + + ); +}; + export const SearchResultNext = () => { const { result: { loading, error, value }, @@ -57,12 +72,32 @@ export const SearchResultNext = () => { return ( - {value.results.map(result => ( - - ))} + {value.results.map(result => { + // Render different result items based on document type + switch (result.type) { + case 'software-catalog': + return ( + + ); + case 'techdocs': + return ( + + ); + default: + return ( + + ); + } + })} ); }; From 1f3e6dff881be05a3b66f8d0329a3c138550abe7 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 21 May 2021 14:17:35 +0200 Subject: [PATCH 044/102] Pass result type along from search engine. Signed-off-by: Eric Peterson --- packages/search-common/src/types.ts | 2 +- .../src/IndexBuilder.test.ts | 2 +- .../search-backend-node/src/IndexBuilder.ts | 2 +- .../src/engines/LunrSearchEngine.ts | 43 ++++++++++++++----- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/packages/search-common/src/types.ts b/packages/search-common/src/types.ts index c26ecdcb4c..07a4296b85 100644 --- a/packages/search-common/src/types.ts +++ b/packages/search-common/src/types.ts @@ -66,5 +66,5 @@ export interface DocumentCollator { * additional metadata. */ export interface DocumentDecorator { - execute(documents: IndexableDocument[]): Promise; + execute(type: string, documents: IndexableDocument[]): Promise; } diff --git a/plugins/search-backend-node/src/IndexBuilder.test.ts b/plugins/search-backend-node/src/IndexBuilder.test.ts index e1dc511b97..c7a2a7817a 100644 --- a/plugins/search-backend-node/src/IndexBuilder.test.ts +++ b/plugins/search-backend-node/src/IndexBuilder.test.ts @@ -30,7 +30,7 @@ class TestDocumentCollator implements DocumentCollator { } class TestDocumentDecorator implements DocumentDecorator { - async execute(documents: IndexableDocument[]) { + async execute(_type: string, documents: IndexableDocument[]) { return documents; } } diff --git a/plugins/search-backend-node/src/IndexBuilder.ts b/plugins/search-backend-node/src/IndexBuilder.ts index 71d374e704..acaa18fc03 100644 --- a/plugins/search-backend-node/src/IndexBuilder.ts +++ b/plugins/search-backend-node/src/IndexBuilder.ts @@ -113,7 +113,7 @@ export class IndexBuilder { this.logger.debug( `Decorating ${type} documents via ${decorators[i].constructor.name}`, ); - documents = await decorators[i].execute(documents); + documents = await decorators[i].execute(type, documents); } if (!documents || documents.length === 0) { diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts index 3829693d4e..8bff26a784 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts @@ -28,6 +28,11 @@ type ConcreteLunrQuery = { documentTypes: string[]; }; +type LunrResultEnvelope = { + result: lunr.Index.Result; + type: string; +}; + export class LunrSearchEngine implements SearchEngine { protected lunrIndices: Record = {}; protected docStore: Record; @@ -104,13 +109,22 @@ export class LunrSearchEngine implements SearchEngine { query, ) as ConcreteLunrQuery; - const results: lunr.Index.Result[] = []; + const results: LunrResultEnvelope[] = []; if (documentTypes.length === 1 && documentTypes[0] === '*') { // Iterate over all this.lunrIndex values. - Object.values(this.lunrIndices).forEach(i => { + Object.keys(this.lunrIndices).forEach(type => { try { - results.push(...i.search(lunrQueryString)); + results.push( + ...this.lunrIndices[type] + .search(lunrQueryString) + .map(result => { + return { + result: result, + type: type, + }; + }) + ); } catch (err) { // if a field does not exist on a index, we can see that as a no-match if ( @@ -123,10 +137,19 @@ export class LunrSearchEngine implements SearchEngine { } else { // Iterate over the filtered list of this.lunrIndex keys. Object.keys(this.lunrIndices) - .filter(d => documentTypes.includes(d)) - .forEach(d => { + .filter(type => documentTypes.includes(type)) + .forEach(type => { try { - results.push(...this.lunrIndices[d].search(lunrQueryString)); + results.push( + ...this.lunrIndices[type] + .search(lunrQueryString) + .map(result => { + return { + result: result, + type: type, + }; + }) + ); } catch (err) { // if a field does not exist on a index, we can see that as a no-match if ( @@ -140,16 +163,16 @@ export class LunrSearchEngine implements SearchEngine { // Sort results. results.sort((doc1, doc2) => { - return doc2.score - doc1.score; + return doc2.result.score - doc1.result.score; }); // Translate results into SearchResultSet - const resultSet: SearchResultSet = { + const realResultSet: SearchResultSet = { results: results.map(d => { - return { type: 'techdocs', document: this.docStore[d.ref] }; + return { type: d.type, document: this.docStore[d.result.ref] }; }), }; - return Promise.resolve(resultSet); + return Promise.resolve(realResultSet); } } From 94e13c8892dcd1016f4c476c5c501e58db4d4d8e Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 21 May 2021 14:18:33 +0200 Subject: [PATCH 045/102] Making Search page composable. Signed-off-by: Eric Peterson --- packages/app/src/App.tsx | 7 +- .../app/src/components/search/SearchPage.tsx | 78 +++++++++++++++++++ .../CatalogResultListItem.tsx | 51 ++++++++++++ .../components/CatalogResultListItem/index.ts | 17 ++++ plugins/catalog/src/index.ts | 1 + plugins/search/package.json | 1 + .../DefaultResultListItem.tsx | 35 +++++++++ .../components/DefaultResultListItem/index.ts | 18 +++++ .../SearchPageNext/SearchPageNext.tsx | 36 +-------- .../SearchResultNext/SearchResultNext.tsx | 71 ++--------------- plugins/search/src/components/index.tsx | 1 + plugins/search/src/index.ts | 4 + plugins/search/src/plugin.ts | 9 +++ 13 files changed, 230 insertions(+), 99 deletions(-) create mode 100644 packages/app/src/components/search/SearchPage.tsx create mode 100644 plugins/catalog/src/components/CatalogResultListItem/CatalogResultListItem.tsx create mode 100644 plugins/catalog/src/components/CatalogResultListItem/index.ts create mode 100644 plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.tsx create mode 100644 plugins/search/src/components/DefaultResultListItem/index.ts diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index c5e5033973..04f618c583 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -54,6 +54,7 @@ import { Navigate, Route } from 'react-router'; import { apis } from './apis'; import { Root } from './components/Root'; import { entityPage } from './components/catalog/EntityPage'; +import { searchPage } from './components/search/SearchPage'; import { providers } from './identityProviders'; import * as plugins from './plugins'; @@ -121,8 +122,10 @@ const routes = ( } /> } - /> + element={} + > + {searchPage} + } /> +
} /> + + + + + + + + + + + {({results}) => ( + + {results.map(result => { + switch (result.type) { + case 'software-catalog': + return ( + + ); + default: + return ( + + ); + } + })} + + )} + + + + + +); diff --git a/plugins/catalog/src/components/CatalogResultListItem/CatalogResultListItem.tsx b/plugins/catalog/src/components/CatalogResultListItem/CatalogResultListItem.tsx new file mode 100644 index 0000000000..88a1f9f1d6 --- /dev/null +++ b/plugins/catalog/src/components/CatalogResultListItem/CatalogResultListItem.tsx @@ -0,0 +1,51 @@ +/* + * 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 + * l +imitations under the License. + */ + +import React from 'react'; +import { Link } from '@backstage/core'; +import { Box, Chip, Divider, ListItem, ListItemText, makeStyles } from '@material-ui/core'; + +const useStyles = makeStyles({ + flexContainer: { + flexWrap: 'wrap', + }, + itemText: { + width: '100%', + marginBottom: '1rem', + }, +}); + +export const CatalogResultListItem = ({ result }: any) => { + const classes = useStyles(); + return ( + + + + + {result.kind && ()} + {result.lifecycle && ()} + + + + + ); +}; diff --git a/plugins/catalog/src/components/CatalogResultListItem/index.ts b/plugins/catalog/src/components/CatalogResultListItem/index.ts new file mode 100644 index 0000000000..8f418c1dc7 --- /dev/null +++ b/plugins/catalog/src/components/CatalogResultListItem/index.ts @@ -0,0 +1,17 @@ +/* + * 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 { CatalogResultListItem } from './CatalogResultListItem'; diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index f7506b534a..052f055dc0 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -15,6 +15,7 @@ */ export { AboutCard } from './components/AboutCard'; +export { CatalogResultListItem } from './components/CatalogResultListItem'; export { EntityLayout } from './components/EntityLayout'; export { EntityPageLayout } from './components/EntityPageLayout'; export { CatalogTable } from './components/CatalogTable'; diff --git a/plugins/search/package.json b/plugins/search/package.json index cc42bb05ed..c767c923db 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -41,6 +41,7 @@ "qs": "^6.9.4", "react": "^16.13.1", "react-dom": "^16.13.1", + "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", "react-use": "^17.2.4" }, diff --git a/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.tsx b/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.tsx new file mode 100644 index 0000000000..09ef325f85 --- /dev/null +++ b/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.tsx @@ -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 + * l +imitations under the License. + */ + +import React from 'react'; +import { Link } from '@backstage/core'; +import { ListItem, ListItemText, Divider } from '@material-ui/core'; + +export const DefaultResultListItem = ({ result }: any) => { + return ( + + + + + + + ); +}; diff --git a/plugins/search/src/components/DefaultResultListItem/index.ts b/plugins/search/src/components/DefaultResultListItem/index.ts new file mode 100644 index 0000000000..f195f62929 --- /dev/null +++ b/plugins/search/src/components/DefaultResultListItem/index.ts @@ -0,0 +1,18 @@ +/* + * 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 + * l +imitations under the License. + */ + +export { DefaultResultListItem } from './DefaultResultListItem'; diff --git a/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx b/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx index 691f01b2cb..e86c887f59 100644 --- a/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx +++ b/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx @@ -15,45 +15,13 @@ */ import React from 'react'; -import { Content, Header, Lifecycle, Page } from '@backstage/core'; -import { Grid } from '@material-ui/core'; -import { SearchBarNext } from '../SearchBarNext'; -import { SearchResultNext } from '../SearchResultNext'; +import { Outlet } from 'react-router'; import { SearchContextProvider } from '../SearchContext'; -import { SearchFiltersNext, FilterType } from '../SearchFiltersNext'; - -const defaultFilterDefinitions = [ - { - field: 'kind', - type: FilterType.SELECT, - values: ['Component', 'Template'], - }, - { - field: 'lifecycle', - type: FilterType.CHECKBOX, - values: ['experimental', 'production'], - }, -]; export const SearchPageNext = () => { return ( - -
} /> - - - - - - - - - - - - - - + ); }; diff --git a/plugins/search/src/components/SearchResultNext/SearchResultNext.tsx b/plugins/search/src/components/SearchResultNext/SearchResultNext.tsx index 53fc4ff17b..ec738e839c 100644 --- a/plugins/search/src/components/SearchResultNext/SearchResultNext.tsx +++ b/plugins/search/src/components/SearchResultNext/SearchResultNext.tsx @@ -13,44 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { EmptyState, Link, Progress } from '@backstage/core'; -import { Divider, List, ListItem, ListItemText } from '@material-ui/core'; +import { EmptyState, Progress } from '@backstage/core'; +import { SearchResult } from '@backstage/search-common'; import { Alert } from '@material-ui/lab'; import React from 'react'; import { useSearch } from '../SearchContext'; -const DefaultResultListItem = ({ result }: any) => { - return ( - - - - - - - ); -}; +type ChildrenArguments = { + results: SearchResult[]; +} -const TechDocsResultListItem = ({ result }: any) => { - return ( - - - - - - - ); -}; - -export const SearchResultNext = () => { +export const SearchResultNext = ({ children }: { children: (results: ChildrenArguments) => JSX.Element}) => { const { result: { loading, error, value }, } = useSearch(); @@ -67,37 +41,8 @@ export const SearchResultNext = () => { } if (!value) { - return ; + return ; } - return ( - - {value.results.map(result => { - // Render different result items based on document type - switch (result.type) { - case 'software-catalog': - return ( - - ); - case 'techdocs': - return ( - - ); - default: - return ( - - ); - } - })} - - ); + return children({ results: value.results }); }; diff --git a/plugins/search/src/components/index.tsx b/plugins/search/src/components/index.tsx index aa9a443f21..597bfabfb1 100644 --- a/plugins/search/src/components/index.tsx +++ b/plugins/search/src/components/index.tsx @@ -22,5 +22,6 @@ export * from './SearchPage'; export * from './SearchPageNext'; export * from './SearchResult'; export * from './SearchResultNext'; +export * from './DefaultResultListItem'; export * from './SidebarSearch'; export * from './SearchContext'; diff --git a/plugins/search/src/index.ts b/plugins/search/src/index.ts index 367b195f9d..e5894aced4 100644 --- a/plugins/search/src/index.ts +++ b/plugins/search/src/index.ts @@ -23,11 +23,15 @@ export { SearchBarNext, SearchResultNext, SearchFiltersNext, + DefaultResultListItem, } from './plugin'; export { Filters, FiltersButton, + FilterType, SearchBar, + SearchContextProvider, + useSearch, SearchPage as Router, SearchResult, SidebarSearch, diff --git a/plugins/search/src/plugin.ts b/plugins/search/src/plugin.ts index 03322b33aa..3de59fe1d0 100644 --- a/plugins/search/src/plugin.ts +++ b/plugins/search/src/plugin.ts @@ -90,6 +90,15 @@ export const SearchResultNext = searchPlugin.provide( }), ); +export const DefaultResultListItem= searchPlugin.provide( + createComponentExtension({ + component: { + lazy: () => + import('./components/DefaultResultListItem').then(m => m.DefaultResultListItem), + }, + }), +); + export const SearchFiltersNext = searchPlugin.provide( createComponentExtension({ component: { From 578028c9a6cbe21e147a6c3252e0b8db0610d474 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 24 May 2021 09:07:36 +0200 Subject: [PATCH 046/102] consistent composability between result list items and filters Signed-off-by: Emma Indal Signed-off-by: Eric Peterson --- .../app/src/components/search/SearchPage.tsx | 41 ++++++++++++++++--- .../SearchFiltersNext/SearchFiltersNext.tsx | 35 +++------------- .../components/SearchFiltersNext/index.tsx | 7 +++- plugins/search/src/index.ts | 2 + 4 files changed, 49 insertions(+), 36 deletions(-) diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx index 3aff554b42..59b4168a18 100644 --- a/packages/app/src/components/search/SearchPage.tsx +++ b/packages/app/src/components/search/SearchPage.tsx @@ -17,9 +17,15 @@ import React from 'react'; import { Content, Header, Lifecycle, Page } from '@backstage/core'; import { Grid, List } from '@material-ui/core'; -import { SearchBarNext } from '@backstage/plugin-search'; -import { SearchResultNext, DefaultResultListItem } from '@backstage/plugin-search'; -import { SearchFiltersNext, FilterType } from '@backstage/plugin-search'; +import { + SearchBarNext, + SearchResultNext, + DefaultResultListItem, + SearchFiltersNext, + FilterType, + CheckBoxFilter, + SelectFilter, +} from '@backstage/plugin-search'; import { CatalogResultListItem } from '@backstage/plugin-catalog'; const filterDefinitions = [ @@ -44,11 +50,36 @@ export const searchPage = ( - + + <> + {filterDefinitions.map(definition => { + switch (definition.type) { + case 'checkbox': + return ( + + ); + case 'select': + return ( + + ); + default: + return null; + } + })} + + - {({results}) => ( + {({ results }) => ( {results.map(result => { switch (result.type) { diff --git a/plugins/search/src/components/SearchFiltersNext/SearchFiltersNext.tsx b/plugins/search/src/components/SearchFiltersNext/SearchFiltersNext.tsx index 92c65f4a57..b6536276c3 100644 --- a/plugins/search/src/components/SearchFiltersNext/SearchFiltersNext.tsx +++ b/plugins/search/src/components/SearchFiltersNext/SearchFiltersNext.tsx @@ -55,7 +55,7 @@ export type FilterOptions = { }; type FiltersProps = { - definitions: FilterDefinition[]; + children: React.ReactChild; }; type ValuedFilterProps = { @@ -79,7 +79,7 @@ export type FilterDefinition = { values: string[]; }; -const CheckBoxFilter = ({ fieldName, values }: ValuedFilterProps) => { +export const CheckBoxFilter = ({ fieldName, values }: ValuedFilterProps) => { const { filters, setFilters } = useSearch(); const classes = useCheckBoxStyles(); @@ -134,7 +134,7 @@ const CheckBoxFilter = ({ fieldName, values }: ValuedFilterProps) => { ); }; -const SelectFilter = ({ fieldName, values }: ValuedFilterProps) => { +export const SelectFilter = ({ fieldName, values }: ValuedFilterProps) => { const { filters, setFilters } = useSearch(); const classes = useSelectStyles(); @@ -171,33 +171,8 @@ const SelectFilter = ({ fieldName, values }: ValuedFilterProps) => { ); }; -export const SearchFiltersNext = ({ definitions }: FiltersProps) => { +export const SearchFiltersNext = ({ children }: FiltersProps) => { const classes = useFilterStyles(); - return ( - - {definitions.map(definition => { - switch (definition.type) { - case 'checkbox': - return ( - - ); - case 'select': - return ( - - ); - default: - return null; - } - })} - - ); + return {children}; }; diff --git a/plugins/search/src/components/SearchFiltersNext/index.tsx b/plugins/search/src/components/SearchFiltersNext/index.tsx index d45eadc79a..999db8deb5 100644 --- a/plugins/search/src/components/SearchFiltersNext/index.tsx +++ b/plugins/search/src/components/SearchFiltersNext/index.tsx @@ -15,4 +15,9 @@ */ export { FiltersButtonNext } from './FiltersButtonNext'; -export { SearchFiltersNext, FilterType } from './SearchFiltersNext'; +export { + SearchFiltersNext, + CheckBoxFilter, + SelectFilter, + FilterType, +} from './SearchFiltersNext'; diff --git a/plugins/search/src/index.ts b/plugins/search/src/index.ts index e5894aced4..4d8d9c08a2 100644 --- a/plugins/search/src/index.ts +++ b/plugins/search/src/index.ts @@ -29,6 +29,8 @@ export { Filters, FiltersButton, FilterType, + CheckBoxFilter, + SelectFilter, SearchBar, SearchContextProvider, useSearch, From cdfa7d69df13221d9501335b97024b0d5a43b3d0 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 24 May 2021 09:21:29 +0200 Subject: [PATCH 047/102] fix margin Signed-off-by: Emma Indal Signed-off-by: Eric Peterson --- .../src/components/SearchFiltersNext/SearchFiltersNext.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/search/src/components/SearchFiltersNext/SearchFiltersNext.tsx b/plugins/search/src/components/SearchFiltersNext/SearchFiltersNext.tsx index b6536276c3..91c193f54e 100644 --- a/plugins/search/src/components/SearchFiltersNext/SearchFiltersNext.tsx +++ b/plugins/search/src/components/SearchFiltersNext/SearchFiltersNext.tsx @@ -151,7 +151,7 @@ export const SelectFilter = ({ fieldName, values }: ValuedFilterProps) => { return ( - {fieldName} + {fieldName} ) => + setSelectFilter(e?.target?.value) + } + > + + All + + {values && + values.map((value: string) => ( + {value} + ))} + + + ); +}; + +const SearchFilterNext = ({ component: Element, ...props }: Props) => { + return ; +}; + +SearchFilterNext.Checkbox = (props: Omit) => ( + +); +SearchFilterNext.Select = (props: Omit) => ( + +); + +export { SearchFilterNext }; diff --git a/plugins/search/src/components/SearchFilterNext/index.ts b/plugins/search/src/components/SearchFilterNext/index.ts new file mode 100644 index 0000000000..322abe64d7 --- /dev/null +++ b/plugins/search/src/components/SearchFilterNext/index.ts @@ -0,0 +1,17 @@ +/* + * 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 { SearchFilterNext } from './SearchFilterNext'; diff --git a/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx b/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx index e86c887f59..f3a4efc32c 100644 --- a/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx +++ b/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx @@ -15,12 +15,26 @@ */ import React from 'react'; -import { Outlet } from 'react-router'; +import qs from 'qs'; +import { Outlet, useLocation } from 'react-router'; +import { useQueryParamState } from '@backstage/core'; import { SearchContextProvider } from '../SearchContext'; +import { JsonObject } from '@backstage/config'; export const SearchPageNext = () => { + const location = useLocation(); + const [queryString] = useQueryParamState('query'); + const filters = (qs.parse(location.search.substring(1), { arrayLimit: 0 }) + .filters || {}) as JsonObject; + const initialState = { + term: queryString || '', + types: [], + pageCursor: '', + filters, + }; + return ( - + ); diff --git a/plugins/search/src/components/index.tsx b/plugins/search/src/components/index.tsx index 597bfabfb1..b02561c710 100644 --- a/plugins/search/src/components/index.tsx +++ b/plugins/search/src/components/index.tsx @@ -15,6 +15,7 @@ */ export * from './Filters'; +export * from './SearchFilterNext'; export * from './SearchFiltersNext'; export * from './SearchBar'; export * from './SearchBarNext'; diff --git a/plugins/search/src/index.ts b/plugins/search/src/index.ts index 4d8d9c08a2..5ff047e986 100644 --- a/plugins/search/src/index.ts +++ b/plugins/search/src/index.ts @@ -22,7 +22,6 @@ export { SearchPageNext, SearchBarNext, SearchResultNext, - SearchFiltersNext, DefaultResultListItem, } from './plugin'; export { @@ -35,6 +34,7 @@ export { SearchContextProvider, useSearch, SearchPage as Router, + SearchFilterNext, SearchResult, SidebarSearch, } from './components'; diff --git a/plugins/search/src/plugin.ts b/plugins/search/src/plugin.ts index cff594b299..6ff40ebaf7 100644 --- a/plugins/search/src/plugin.ts +++ b/plugins/search/src/plugin.ts @@ -100,12 +100,3 @@ export const DefaultResultListItem = searchPlugin.provide( }, }), ); - -export const SearchFiltersNext = searchPlugin.provide( - createComponentExtension({ - component: { - lazy: () => - import('./components/SearchFiltersNext').then(m => m.SearchFiltersNext), - }, - }), -); From 54cdabdd3533172ae9ab4f0c18678595603e4252 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 24 May 2021 12:47:05 +0200 Subject: [PATCH 056/102] delete search filters component Signed-off-by: Emma Indal Signed-off-by: Eric Peterson --- .../SearchFiltersNext/FiltersButtonNext.tsx | 56 ------ .../SearchFiltersNext/SearchFiltersNext.tsx | 183 ------------------ .../components/SearchFiltersNext/index.tsx | 23 --- plugins/search/src/index.ts | 3 - 4 files changed, 265 deletions(-) delete mode 100644 plugins/search/src/components/SearchFiltersNext/FiltersButtonNext.tsx delete mode 100644 plugins/search/src/components/SearchFiltersNext/SearchFiltersNext.tsx delete mode 100644 plugins/search/src/components/SearchFiltersNext/index.tsx diff --git a/plugins/search/src/components/SearchFiltersNext/FiltersButtonNext.tsx b/plugins/search/src/components/SearchFiltersNext/FiltersButtonNext.tsx deleted file mode 100644 index f7628714ca..0000000000 --- a/plugins/search/src/components/SearchFiltersNext/FiltersButtonNext.tsx +++ /dev/null @@ -1,56 +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 React from 'react'; -import FilterListIcon from '@material-ui/icons/FilterList'; -import { makeStyles, IconButton, Typography } from '@material-ui/core'; - -const useStyles = makeStyles(theme => ({ - filters: { - width: '250px', - display: 'flex', - }, - icon: { - margin: theme.spacing(-1, 0, 0, 0), - }, -})); - -type FiltersButtonProps = { - numberOfSelectedFilters: number; - handleToggleFilters: () => void; -}; - -export const FiltersButtonNext = ({ - numberOfSelectedFilters, - handleToggleFilters, -}: FiltersButtonProps) => { - const classes = useStyles(); - - return ( -
- - - - - Filters ({numberOfSelectedFilters ? numberOfSelectedFilters : 0}) - -
- ); -}; diff --git a/plugins/search/src/components/SearchFiltersNext/SearchFiltersNext.tsx b/plugins/search/src/components/SearchFiltersNext/SearchFiltersNext.tsx deleted file mode 100644 index d94b8021ef..0000000000 --- a/plugins/search/src/components/SearchFiltersNext/SearchFiltersNext.tsx +++ /dev/null @@ -1,183 +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 { - Card, - CardContent, - Checkbox, - FormControl, - InputLabel, - List, - MenuItem, - ListItem, - ListItemText, - makeStyles, - Select, -} from '@material-ui/core'; -import React from 'react'; -import { useSearch } from '../SearchContext'; - -const useFilterStyles = makeStyles({ - filters: { - background: 'transparent', - boxShadow: '0px 0px 0px 0px', - }, -}); - -const useCheckBoxStyles = makeStyles(theme => ({ - checkbox: { - padding: theme.spacing(0, 1, 0, 1), - }, -})); - -const useSelectStyles = makeStyles({ - select: { - width: '100%', - }, -}); - -export type FilterOptions = { - kind: Array; - lifecycle: Array; -}; - -type FiltersProps = { - children: React.ReactChild; -}; - -type ValuedFilterProps = { - fieldName: string; - values: string[]; -}; - -export enum FilterType { - CHECKBOX = 'checkbox', - SELECT = 'select', -} - -export type NewFilterDefinition = { - component: any; - props: any; -}; - -export type FilterDefinition = { - field: string; - type: FilterType; - values: string[]; -}; - -export const CheckBoxFilter = ({ fieldName, values }: ValuedFilterProps) => { - const { filters, setFilters } = useSearch(); - const classes = useCheckBoxStyles(); - - const setCheckboxFilter = (filter: string) => { - const newFilters = filters; - const currentValues = newFilters[fieldName] as string[]; - - if (!filter) return; - - if (!currentValues) { - setFilters({ ...filters, [fieldName]: [filter] }); - } else if (!currentValues?.includes(filter)) { - setFilters({ - ...filters, - [fieldName]: [...currentValues, filter], - }); - } else { - const filterToDelete = currentValues.find(value => value === filter); - if (filterToDelete) { - currentValues.splice(currentValues.indexOf(filterToDelete), 1); - - setFilters({ ...filters, [fieldName]: currentValues }); - } - } - }; - return ( - - {fieldName} - - {values.map((value: string) => ( - setCheckboxFilter(value)} - > - - - - ))} - - - ); -}; - -export const SelectFilter = ({ fieldName, values }: ValuedFilterProps) => { - const { filters, setFilters } = useSearch(); - const classes = useSelectStyles(); - - const setSelectFilter = (filter: string) => { - const newFilters = filters; - if (newFilters[fieldName] && filter === '') { - delete newFilters[fieldName]; - setFilters({ newFilters }); - } else { - setFilters({ ...filters, [fieldName]: filter as string }); - } - }; - - return ( - - - {fieldName} - - - - ); -}; - -export const SearchFiltersNext = ({ children }: FiltersProps) => { - const classes = useFilterStyles(); - - return {children}; -}; diff --git a/plugins/search/src/components/SearchFiltersNext/index.tsx b/plugins/search/src/components/SearchFiltersNext/index.tsx deleted file mode 100644 index dd35bd3624..0000000000 --- a/plugins/search/src/components/SearchFiltersNext/index.tsx +++ /dev/null @@ -1,23 +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. - */ - -export { FiltersButtonNext } from './FiltersButtonNext'; -export { - SearchFiltersNext, - CheckBoxFilter, - SelectFilter, - FilterType, -} from './SearchFiltersNext'; diff --git a/plugins/search/src/index.ts b/plugins/search/src/index.ts index 5ff047e986..27fd1706fc 100644 --- a/plugins/search/src/index.ts +++ b/plugins/search/src/index.ts @@ -27,9 +27,6 @@ export { export { Filters, FiltersButton, - FilterType, - CheckBoxFilter, - SelectFilter, SearchBar, SearchContextProvider, useSearch, From d92b8ec5eaf481a8c3cc183fd7a9dcfdd48d3032 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 24 May 2021 15:40:57 +0200 Subject: [PATCH 057/102] fixups Signed-off-by: Emma Indal Signed-off-by: Eric Peterson --- plugins/search/src/components/SearchBarNext/SearchBarNext.tsx | 2 +- plugins/search/src/components/index.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx b/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx index a36393a376..70868f5ce5 100644 --- a/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx +++ b/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { useEffect } from 'react'; +import React from 'react'; import { useDebounce } from 'react-use'; import { useQueryParamState } from '@backstage/core'; import { Paper, InputBase, IconButton, makeStyles } from '@material-ui/core'; diff --git a/plugins/search/src/components/index.tsx b/plugins/search/src/components/index.tsx index b02561c710..92e24b60df 100644 --- a/plugins/search/src/components/index.tsx +++ b/plugins/search/src/components/index.tsx @@ -16,7 +16,6 @@ export * from './Filters'; export * from './SearchFilterNext'; -export * from './SearchFiltersNext'; export * from './SearchBar'; export * from './SearchBarNext'; export * from './SearchPage'; From b264028394c27f4e2a8b459e4572b36456f2a04b Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 24 May 2021 18:27:00 +0200 Subject: [PATCH 058/102] initial tests Signed-off-by: Emma Indal Signed-off-by: Eric Peterson --- .../SearchBarNext/SearchBarNext.test.tsx | 29 +++++++++ .../SearchContext/SearchContext.test.tsx | 61 +++++++++++++++++++ .../SearchFilterNext.test.tsx | 37 +++++++++++ 3 files changed, 127 insertions(+) create mode 100644 plugins/search/src/components/SearchBarNext/SearchBarNext.test.tsx create mode 100644 plugins/search/src/components/SearchContext/SearchContext.test.tsx create mode 100644 plugins/search/src/components/SearchFilterNext/SearchFilterNext.test.tsx diff --git a/plugins/search/src/components/SearchBarNext/SearchBarNext.test.tsx b/plugins/search/src/components/SearchBarNext/SearchBarNext.test.tsx new file mode 100644 index 0000000000..3a5cb3e97a --- /dev/null +++ b/plugins/search/src/components/SearchBarNext/SearchBarNext.test.tsx @@ -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 React from 'react'; +import { renderInTestApp } from '@backstage/test-utils'; +import { SearchBarNext } from './SearchBarNext'; + +describe('', () => { + it('renders without exploding', async () => { + const { getByRole } = await renderInTestApp(); + + expect( + getByRole('textbox', { name: 'search backstage' }), + ).toBeInTheDocument(); + }); +}); diff --git a/plugins/search/src/components/SearchContext/SearchContext.test.tsx b/plugins/search/src/components/SearchContext/SearchContext.test.tsx new file mode 100644 index 0000000000..cce315f62b --- /dev/null +++ b/plugins/search/src/components/SearchContext/SearchContext.test.tsx @@ -0,0 +1,61 @@ +/* + * 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 React from 'react'; +import { renderInTestApp } from '@backstage/test-utils'; +import * as SearchContext from './SearchContext'; + +const mockContextState = ({ term }: { term: string }) => { + return { + term, + pageCursor: '', + filters: {}, + types: ['*'], + result: { results: [], loading: false, error: undefined }, + setTerm: jest.fn(), + setFilters: jest.fn(), + setTypes: jest.fn(), + setPageCursor: jest.fn(), + }; +}; + +const MockSearchContextConsumer = () => { + const { term } = SearchContext.useSearch(); + + return
{term}
; +}; + +describe('useSearch', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + it('context should use initial term', async () => { + jest.spyOn(SearchContext, 'useSearch'); + const { getByRole } = await renderInTestApp(); + expect(getByRole('heading')).toBeInTheDocument(); + }); + + it('context should use mocked term', async () => { + jest + .spyOn(SearchContext, 'useSearch') + .mockImplementation(() => mockContextState({ term: 'new-term' })); + + const { getByRole } = await renderInTestApp(); + + expect(getByRole('heading', { name: 'new-term' })).toBeInTheDocument(); + }); +}); diff --git a/plugins/search/src/components/SearchFilterNext/SearchFilterNext.test.tsx b/plugins/search/src/components/SearchFilterNext/SearchFilterNext.test.tsx new file mode 100644 index 0000000000..63822b9e00 --- /dev/null +++ b/plugins/search/src/components/SearchFilterNext/SearchFilterNext.test.tsx @@ -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. + */ + +import React from 'react'; +import { renderInTestApp } from '@backstage/test-utils'; +import { SearchFilterNext } from './SearchFilterNext'; + +const MockFilterComponent = ({ name }: { name: string }) => { + return
{name}
; +}; + +describe('', () => { + it('renders without exploding', async () => { + const props = { + name: 'filter name', + }; + + const { getByRole } = await renderInTestApp( + , + ); + + expect(getByRole('heading', { name: 'filter name' })).toBeInTheDocument(); + }); +}); From 4e6d888340d0b625ccd5360f934623e543a57c51 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Tue, 25 May 2021 10:21:11 +0200 Subject: [PATCH 059/102] Move react type from devDeps to deps Signed-off-by: Eric Peterson --- plugins/search/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/search/package.json b/plugins/search/package.json index 0c99e9b81c..a02beefb4d 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -38,6 +38,7 @@ "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", + "@types/react": "^16.9", "qs": "^6.9.4", "react": "^16.13.1", "react-dom": "^16.13.1", From c70139e732190be37155cff424153203f57f8a88 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 25 May 2021 11:20:24 +0200 Subject: [PATCH 060/102] Update CheckBox filter defaultValue to be string array. Signed-off-by: Camila Belo Signed-off-by: Eric Peterson --- .../SearchFilterNext/SearchFilterNext.tsx | 30 +++++++------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx b/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx index 02b625bd42..dd805c3af1 100644 --- a/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx +++ b/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx @@ -43,12 +43,9 @@ export type Props = { debug?: boolean; name: string; values?: string[]; - defaultValue?: string | null; + defaultValue?: string[] | string | null; }; -/** - * @param defaultValue - The default value. If more than one value should be defaulted to "checked," pass a comma-separated string. - */ const CheckboxFilter = ({ name, defaultValue, values }: Component) => { const { filters, setFilters } = useSearch(); @@ -76,13 +73,12 @@ const CheckboxFilter = ({ name, defaultValue, values }: Component) => { }; useEffect(() => { - if (defaultValue) { + if (defaultValue && Array.isArray(defaultValue)) { setFilters(prevFilters => ({ ...prevFilters, - [name]: defaultValue.split(','), + [name]: defaultValue, })); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [defaultValue, setFilters]); return ( @@ -127,18 +123,14 @@ const SelectFilter = ({ name, defaultValue, values }: Component) => { } }; - useEffect( - () => { - if (defaultValue) { - setFilters(prevFilters => ({ - ...prevFilters, - [name]: defaultValue, - })); - } - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [setFilters, defaultValue], - ); + useEffect(() => { + if (defaultValue && typeof defaultValue === 'string') { + setFilters(prevFilters => ({ + ...prevFilters, + [name]: defaultValue, + })); + } + }, [setFilters, defaultValue]); return ( From db6fd04d732c68e742bdcdc980926990caf6ee69 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 25 May 2021 14:36:58 +0200 Subject: [PATCH 061/102] Move query param logic from SearchBar to SearchPage Signed-off-by: Camila Belo Signed-off-by: Eric Peterson --- .../SearchBarNext/SearchBarNext.tsx | 32 ++++++++--------- .../SearchPageNext/SearchPageNext.tsx | 34 ++++++++++++++++--- 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx b/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx index 70868f5ce5..0e8c626b6f 100644 --- a/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx +++ b/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx @@ -14,9 +14,8 @@ * limitations under the License. */ -import React from 'react'; +import React, { useState } from 'react'; import { useDebounce } from 'react-use'; -import { useQueryParamState } from '@backstage/core'; import { Paper, InputBase, IconButton, makeStyles } from '@material-ui/core'; import SearchIcon from '@material-ui/icons/Search'; import ClearButton from '@material-ui/icons/Clear'; @@ -32,23 +31,26 @@ const useStyles = makeStyles(() => ({ }, })); -export const SearchBarNext = () => { +type Props = { + debounceTime?: number; +}; + +export const SearchBarNext = ({ debounceTime = 200 }: Props) => { const classes = useStyles(); const { term, setTerm, setPageCursor } = useSearch(); - - const [, setQueryString] = useQueryParamState('query'); + const [value, setValue] = useState(term); useDebounce( () => { - setQueryString(term); + setTerm(value); }, - 200, - [term], + debounceTime, + [value], ); const handleSearch = (event: React.ChangeEvent | React.FormEvent) => { event.preventDefault(); - setTerm((event.target as HTMLInputElement).value as string); + setValue((event.target as HTMLInputElement).value as string); }; const handleClearSearchBar = () => { @@ -57,22 +59,18 @@ export const SearchBarNext = () => { }; return ( - handleSearch(e)} - className={classes.root} - > + handleSearch(e)} + value={value} + onChange={handleSearch} inputProps={{ 'aria-label': 'search backstage' }} /> - handleClearSearchBar()}> + diff --git a/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx b/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx index f3a4efc32c..bae5be6042 100644 --- a/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx +++ b/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx @@ -17,15 +17,38 @@ import React from 'react'; import qs from 'qs'; import { Outlet, useLocation } from 'react-router'; -import { useQueryParamState } from '@backstage/core'; -import { SearchContextProvider } from '../SearchContext'; +import { SearchContextProvider, useSearch } from '../SearchContext'; import { JsonObject } from '@backstage/config'; +export const UrlUpdater = () => { + const { term, types, pageCursor, filters } = useSearch(); + + const newParams = qs.stringify( + { + query: term, + types, + pageCursor, + filters, + }, + { arrayFormat: 'brackets' }, + ); + const newUrl = `${window.location.pathname}?${newParams}`; + + // We directly manipulate window history here in order to not re-render + // infinitely (state => location => state => etc). The intention of this + // code is just to ensure the right query/filters are loaded when a user + // clicks the "back" button after clicking a result. + window.history.replaceState(null, document.title, newUrl); + + return null; +}; + export const SearchPageNext = () => { const location = useLocation(); - const [queryString] = useQueryParamState('query'); - const filters = (qs.parse(location.search.substring(1), { arrayLimit: 0 }) - .filters || {}) as JsonObject; + const query = qs.parse(location.search.substring(1), { arrayLimit: 0 }) || {}; + const filters = (query.filters as JsonObject) || {}; + const queryString = (query.query as string) || ''; + const initialState = { term: queryString || '', types: [], @@ -35,6 +58,7 @@ export const SearchPageNext = () => { return ( + ); From 83a293b5f9d90287cd54b9ef900139dac34eac92 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 25 May 2021 14:50:45 +0200 Subject: [PATCH 062/102] Clear cursorPage on the Context level Signed-off-by: Camila Belo Signed-off-by: Eric Peterson --- .../src/components/SearchBarNext/SearchBarNext.tsx | 3 +-- .../src/components/SearchContext/SearchContext.tsx | 11 ++++++++++- .../src/components/SearchPageNext/SearchPageNext.tsx | 3 ++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx b/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx index 0e8c626b6f..0a42bfd296 100644 --- a/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx +++ b/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx @@ -37,7 +37,7 @@ type Props = { export const SearchBarNext = ({ debounceTime = 200 }: Props) => { const classes = useStyles(); - const { term, setTerm, setPageCursor } = useSearch(); + const { term, setTerm } = useSearch(); const [value, setValue] = useState(term); useDebounce( @@ -55,7 +55,6 @@ export const SearchBarNext = ({ debounceTime = 200 }: Props) => { const handleClearSearchBar = () => { setTerm(''); - setPageCursor(''); }; return ( diff --git a/plugins/search/src/components/SearchContext/SearchContext.tsx b/plugins/search/src/components/SearchContext/SearchContext.tsx index ac264ccf32..40a9c78cf6 100644 --- a/plugins/search/src/components/SearchContext/SearchContext.tsx +++ b/plugins/search/src/components/SearchContext/SearchContext.tsx @@ -19,8 +19,9 @@ import React, { createContext, useContext, useState, + useEffect, } from 'react'; -import { useAsync } from 'react-use'; +import { useAsync, usePrevious } from 'react-use'; import { useApi } from '@backstage/core'; import { SearchResultSet } from '@backstage/search-common'; import { searchApiRef } from '../../apis'; @@ -60,6 +61,7 @@ export const SearchContextProvider = ({ const [filters, setFilters] = useState(initialState.filters); const [term, setTerm] = useState(initialState.term); const [types, setTypes] = useState(initialState.types); + const prevTerm = usePrevious(term); const result = useAsync( () => @@ -72,6 +74,13 @@ export const SearchContextProvider = ({ [term, filters, types, pageCursor], ); + useEffect(() => { + // Any time a term is reset, we want to start from page 0. + if (term && prevTerm && term !== prevTerm) { + setPageCursor(''); + } + }, [term, prevTerm]); + const value: SearchContextValue = { result, filters, diff --git a/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx b/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx index bae5be6042..cd069b8d2e 100644 --- a/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx +++ b/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx @@ -48,11 +48,12 @@ export const SearchPageNext = () => { const query = qs.parse(location.search.substring(1), { arrayLimit: 0 }) || {}; const filters = (query.filters as JsonObject) || {}; const queryString = (query.query as string) || ''; + const pageCursor = (query.pageCursor as string) || ''; const initialState = { term: queryString || '', types: [], - pageCursor: '', + pageCursor, filters, }; From 8f3994141b2926ee5555928fc40494c0e5d694a3 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 25 May 2021 14:57:52 +0200 Subject: [PATCH 063/102] Set default value for SearchBar as zero Signed-off-by: Camila Belo Signed-off-by: Eric Peterson --- packages/app/src/components/search/SearchPage.tsx | 2 +- plugins/search/src/components/SearchBarNext/SearchBarNext.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx index 8480edf514..3955ec1a2a 100644 --- a/packages/app/src/components/search/SearchPage.tsx +++ b/packages/app/src/components/search/SearchPage.tsx @@ -31,7 +31,7 @@ export const searchPage = ( - + diff --git a/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx b/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx index 0a42bfd296..014e3d6ee4 100644 --- a/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx +++ b/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx @@ -35,7 +35,7 @@ type Props = { debounceTime?: number; }; -export const SearchBarNext = ({ debounceTime = 200 }: Props) => { +export const SearchBarNext = ({ debounceTime = 0 }: Props) => { const classes = useStyles(); const { term, setTerm } = useSearch(); const [value, setValue] = useState(term); From a4c2bb02db7349143a5c2225314c2685b4f9d337 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 25 May 2021 15:24:10 +0200 Subject: [PATCH 064/102] Add key to Checkbox and Select filters Signed-off-by: Camila Belo Signed-off-by: Eric Peterson --- .../SearchFilterNext/SearchFilterNext.tsx | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx b/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx index dd805c3af1..d745b5c2d4 100644 --- a/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx +++ b/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx @@ -79,30 +79,31 @@ const CheckboxFilter = ({ name, defaultValue, values }: Component) => { [name]: defaultValue, })); } - }, [defaultValue, setFilters]); + }, [name, defaultValue, setFilters]); return ( {name} {values && - values.map((v: string) => ( + values.map((value: string) => ( setCheckboxFilter(v)} + inputProps={{ 'aria-labelledby': value }} + value={value} + name={value} + onChange={() => setCheckboxFilter(value)} checked={ filters[name] - ? (filters[name] as string[]).includes(v) + ? (filters[name] as string[]).includes(value) : false } /> } - label={v} + label={value} /> ))} @@ -130,7 +131,7 @@ const SelectFilter = ({ name, defaultValue, values }: Component) => { [name]: defaultValue, })); } - }, [setFilters, defaultValue]); + }, [name, defaultValue, setFilters]); return ( @@ -147,7 +148,9 @@ const SelectFilter = ({ name, defaultValue, values }: Component) => { {values && values.map((value: string) => ( - {value} + + {value} + ))} From f644678bd84508e30ba31b8ed56194fd3b39e0d6 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 25 May 2021 19:49:02 +0200 Subject: [PATCH 065/102] :recycle: Reduce setFilters logic of Checkbox and Select Signed-off-by: Camila Belo Signed-off-by: Eric Peterson --- .../SearchFilterNext/SearchFilterNext.tsx | 158 ++++++++---------- 1 file changed, 72 insertions(+), 86 deletions(-) diff --git a/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx b/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx index d745b5c2d4..0c13fbc008 100644 --- a/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx +++ b/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { ReactElement, useEffect } from 'react'; + +import React, { ReactElement, ChangeEvent, useEffect } from 'react'; import { makeStyles, FormControl, @@ -28,52 +29,28 @@ import { import { useSearch } from '../SearchContext'; const useStyles = makeStyles({ - select: { - width: '100%', - }, - subtitle: { + label: { textTransform: 'capitalize', }, }); -export type Component = Omit; - -export type Props = { - component: (props: Component) => ReactElement; - debug?: boolean; +export type Component = { name: string; values?: string[]; defaultValue?: string[] | string | null; }; -const CheckboxFilter = ({ name, defaultValue, values }: Component) => { +export type Props = Component & { + component: (props: Component) => ReactElement; + debug?: boolean; +}; + +const CheckboxFilter = ({ name, defaultValue, values = [] }: Component) => { + const classes = useStyles(); const { filters, setFilters } = useSearch(); - const setCheckboxFilter = (filter: string) => { - const newFilters = filters; - const currentValues = newFilters[name] as string[]; - - if (!filter) return; - - if (!currentValues) { - setFilters({ ...filters, [name]: [filter] }); - } else if (!currentValues?.includes(filter)) { - setFilters({ - ...filters, - [name]: [...currentValues, filter], - }); - } else { - const filterToDelete = currentValues.find(value => value === filter); - if (filterToDelete) { - currentValues.splice(currentValues.indexOf(filterToDelete), 1); - - setFilters({ ...filters, [name]: currentValues }); - } - } - }; - useEffect(() => { - if (defaultValue && Array.isArray(defaultValue)) { + if (Array.isArray(defaultValue)) { setFilters(prevFilters => ({ ...prevFilters, [name]: defaultValue, @@ -81,51 +58,49 @@ const CheckboxFilter = ({ name, defaultValue, values }: Component) => { } }, [name, defaultValue, setFilters]); + const handleChange = (e: ChangeEvent) => { + const { + target: { value, checked }, + } = e; + + setFilters(prevFilters => { + const { [name]: filter, ...others } = prevFilters; + const rest = ((filter as string[]) || []).filter(i => i !== value); + const items = checked ? [...rest, value] : rest; + return items.length ? { ...others, [name]: items } : others; + }); + }; + return ( - {name} - {values && - values.map((value: string) => ( - setCheckboxFilter(value)} - checked={ - filters[name] - ? (filters[name] as string[]).includes(value) - : false - } - /> - } - label={value} - /> - ))} + {name} + {values.map((value: string) => ( + + } + label={value} + /> + ))} ); }; -const SelectFilter = ({ name, defaultValue, values }: Component) => { +const SelectFilter = ({ name, defaultValue, values = [] }: Component) => { const classes = useStyles(); const { filters, setFilters } = useSearch(); - const setSelectFilter = (filter: string) => { - const newFilters = filters; - if (newFilters[name] && filter === '') { - delete newFilters[name]; - setFilters({ newFilters }); - } else { - setFilters({ ...filters, [name]: filter as string }); - } - }; - useEffect(() => { - if (defaultValue && typeof defaultValue === 'string') { + if (typeof defaultValue === 'string') { setFilters(prevFilters => ({ ...prevFilters, [name]: defaultValue, @@ -133,38 +108,49 @@ const SelectFilter = ({ name, defaultValue, values }: Component) => { } }, [name, defaultValue, setFilters]); + const handleChange = (e: ChangeEvent<{ value: unknown }>) => { + const { + target: { value }, + } = e; + + setFilters(prevFilters => { + const { [name]: filter, ...others } = prevFilters; + return value ? { ...others, [name]: value as string } : others; + }); + }; + return ( - - {name} + + + {name} + ); }; -const SearchFilterNext = ({ component: Element, ...props }: Props) => { - return ; -}; +const SearchFilterNext = ({ component: Element, ...props }: Props) => ( + +); -SearchFilterNext.Checkbox = (props: Omit) => ( +SearchFilterNext.Checkbox = (props: Omit & Component) => ( ); -SearchFilterNext.Select = (props: Omit) => ( + +SearchFilterNext.Select = (props: Omit & Component) => ( ); From 56d84c51a949ec5ba15fe60b20a15fd654e62e5b Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Wed, 26 May 2021 11:01:21 +0200 Subject: [PATCH 066/102] =?UTF-8?q?chore(plugins/search):=20=E2=9E=95=20@t?= =?UTF-8?q?esting-library/react-hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Camila Belo Signed-off-by: Eric Peterson --- plugins/search/package.json | 1 + yarn.lock | 57 +++++++++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/plugins/search/package.json b/plugins/search/package.json index a02beefb4d..f2c10b89c4 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -52,6 +52,7 @@ "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", + "@testing-library/react-hooks": "^7.0.0", "@testing-library/user-event": "^13.1.8", "@types/react": "^16.9", "@types/jest": "^26.0.7", diff --git a/yarn.lock b/yarn.lock index 25202523c2..a5832e82da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5700,6 +5700,17 @@ "@babel/runtime" "^7.5.4" "@types/testing-library__react-hooks" "^3.4.0" +"@testing-library/react-hooks@^7.0.0": + version "7.0.0" + resolved "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-7.0.0.tgz#dd6d37a7e018f147a3b9153137f10e013be8472b" + integrity sha512-WFBGH8DWdIGGBHt6PBtQPe2v4Kbj9vQ1sQ9qLBTmwn1PNggngint4MTE/IiWCYhPbyTW3oc/7X62DObMn/AjQQ== + dependencies: + "@babel/runtime" "^7.12.5" + "@types/react" ">=16.9.0" + "@types/react-dom" ">=16.9.0" + "@types/react-test-renderer" ">=16.9.0" + react-error-boundary "^3.1.0" + "@testing-library/react@^11.2.5": version "11.2.6" resolved "https://registry.npmjs.org/@testing-library/react/-/react-11.2.6.tgz#586a23adc63615985d85be0c903f374dab19200b" @@ -6667,6 +6678,13 @@ "@types/webpack" "^4" "@types/webpack-dev-server" "*" +"@types/react-dom@>=16.9.0": + version "17.0.5" + resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.5.tgz#df44eed5b8d9e0b13bb0cd38e0ea6572a1231227" + integrity sha512-ikqukEhH4H9gr4iJCmQVNzTB307kROe3XFfHAOTxOXPOw7lAoEXnM5KWTkzeANGL5Ce6ABfiMl/zJBYNi7ObmQ== + dependencies: + "@types/react" "*" + "@types/react-dom@^16.9.8": version "16.9.8" resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423" @@ -6710,6 +6728,13 @@ dependencies: "@types/react" "*" +"@types/react-test-renderer@>=16.9.0": + version "17.0.1" + resolved "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-17.0.1.tgz#3120f7d1c157fba9df0118dae20cb0297ee0e06b" + integrity sha512-3Fi2O6Zzq/f3QR9dRnlnHso9bMl7weKCviFmfF6B4LS1Uat6Hkm15k0ZAQuDz+UBq6B3+g+NM6IT2nr5QgPzCw== + dependencies: + "@types/react" "*" + "@types/react-text-truncate@^0.14.0": version "0.14.0" resolved "https://registry.npmjs.org/@types/react-text-truncate/-/react-text-truncate-0.14.0.tgz#588bbabbc7f2a13815e805f3a48942db73fe65fe" @@ -6746,6 +6771,15 @@ dependencies: csstype "^2.2.0" +"@types/react@>=16.9.0": + version "17.0.8" + resolved "https://registry.npmjs.org/@types/react/-/react-17.0.8.tgz#fe76e3ba0fbb5602704110fd1e3035cf394778e3" + integrity sha512-3sx4c0PbXujrYAKwXxNONXUtRp9C+hE2di0IuxFyf5BELD+B+AXL8G7QrmSKhVwKZDbv0igiAjQAMhXj8Yg3aw== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/reactcss@*": version "1.2.3" resolved "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.3.tgz#af28ae11bbb277978b99d04d1eedfd068ca71834" @@ -6827,6 +6861,11 @@ "@types/node" "*" rollup "^0.63.4" +"@types/scheduler@*": + version "0.16.1" + resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275" + integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA== + "@types/semver@^6.0.0": version "6.2.1" resolved "https://registry.npmjs.org/@types/semver/-/semver-6.2.1.tgz#a236185670a7860f1597cf73bea2e16d001461ba" @@ -14575,7 +14614,7 @@ graphql-extensions@^0.12.8: apollo-server-env "^3.0.0" apollo-server-types "^0.6.3" -graphql-language-service-interface@2.8.2, graphql-language-service-interface@^2.8.2: +graphql-language-service-interface@^2.8.2: version "2.8.2" resolved "https://registry.npmjs.org/graphql-language-service-interface/-/graphql-language-service-interface-2.8.2.tgz#b3bb2aef7eaf0dff0b4ea419fa412c5f66fa268b" integrity sha512-otbOQmhgkAJU1QJgQkMztNku6SbJLu/uodoFOYOOtJsizTjrMs93vkYaHCcYnLA3oi1Goj27XcHjMnRCYQOZXQ== @@ -14585,7 +14624,7 @@ graphql-language-service-interface@2.8.2, graphql-language-service-interface@^2. graphql-language-service-utils "^2.5.1" vscode-languageserver-types "^3.15.1" -graphql-language-service-parser@1.9.0, graphql-language-service-parser@^1.9.0: +graphql-language-service-parser@^1.9.0: version "1.9.0" resolved "https://registry.npmjs.org/graphql-language-service-parser/-/graphql-language-service-parser-1.9.0.tgz#79af21294119a0a7e81b6b994a1af36833bab724" integrity sha512-B5xPZLbBmIp0kHvpY1Z35I5DtPoDK9wGxQVRDIzcBaiIvAmlTrDvjo3bu7vKREdjFbYKvWNgrEWENuprMbF17Q== @@ -22182,6 +22221,13 @@ react-draggable@^4.0.3: classnames "^2.2.5" prop-types "^15.6.0" +react-error-boundary@^3.1.0: + version "3.1.3" + resolved "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.3.tgz#276bfa05de8ac17b863587c9e0647522c25e2a0b" + integrity sha512-A+F9HHy9fvt9t8SNDlonq01prnU8AmkjvGKV4kk8seB9kU3xMEO8J/PQlLVmoOIDODl5U2kufSBs4vrWIqhsAA== + dependencies: + "@babel/runtime" "^7.12.5" + react-error-overlay@^6.0.7, react-error-overlay@^6.0.9: version "6.0.9" resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a" @@ -25955,11 +26001,16 @@ typescript-json-schema@^0.49.0: typescript "^4.1.3" yargs "^16.2.0" -typescript@^4.0.3, typescript@^4.1.3, typescript@~4.1.3: +typescript@^4.0.3, typescript@^4.1.3: version "4.2.3" resolved "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3" integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw== +typescript@~4.1.3: + version "4.1.5" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz#123a3b214aaff3be32926f0d8f1f6e704eb89a72" + integrity sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA== + ua-parser-js@^0.7.18: version "0.7.28" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" From 4e97c47c9390654e6a9d6232a368674e74ae7a1c Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Wed, 26 May 2021 11:04:47 +0200 Subject: [PATCH 067/102] test(plugins/search): complete coverage of SearchContext Signed-off-by: Camila Belo Signed-off-by: Eric Peterson --- .../SearchBarNext/SearchBarNext.test.tsx | 32 ++- .../SearchContext/SearchContext.test.tsx | 214 +++++++++++++++--- .../SearchContext/SearchContext.tsx | 2 +- 3 files changed, 218 insertions(+), 30 deletions(-) diff --git a/plugins/search/src/components/SearchBarNext/SearchBarNext.test.tsx b/plugins/search/src/components/SearchBarNext/SearchBarNext.test.tsx index 3a5cb3e97a..f29cc53b71 100644 --- a/plugins/search/src/components/SearchBarNext/SearchBarNext.test.tsx +++ b/plugins/search/src/components/SearchBarNext/SearchBarNext.test.tsx @@ -16,11 +16,41 @@ import React from 'react'; import { renderInTestApp } from '@backstage/test-utils'; +import { useApi } from '@backstage/core'; + +import { SearchContextProvider } from '../SearchContext'; import { SearchBarNext } from './SearchBarNext'; +jest.mock('@backstage/core', () => ({ + ...jest.requireActual('@backstage/core'), + useApi: jest.fn(), +})); + describe('', () => { + const _alphaPerformSearch = jest.fn(); + + const initialState = { + term: '', + pageCursor: '', + filters: {}, + types: ['*'], + }; + + beforeEach(() => { + _alphaPerformSearch.mockResolvedValue([]); + (useApi as jest.Mock).mockReturnValue({ _alphaPerformSearch }); + }); + + afterAll(() => { + jest.resetAllMocks(); + }); + it('renders without exploding', async () => { - const { getByRole } = await renderInTestApp(); + const { getByRole } = await renderInTestApp( + + + , + ); expect( getByRole('textbox', { name: 'search backstage' }), diff --git a/plugins/search/src/components/SearchContext/SearchContext.test.tsx b/plugins/search/src/components/SearchContext/SearchContext.test.tsx index cce315f62b..c45d74eade 100644 --- a/plugins/search/src/components/SearchContext/SearchContext.test.tsx +++ b/plugins/search/src/components/SearchContext/SearchContext.test.tsx @@ -15,47 +15,205 @@ */ import React from 'react'; -import { renderInTestApp } from '@backstage/test-utils'; -import * as SearchContext from './SearchContext'; +import { render, screen, waitFor } from '@testing-library/react'; +import { renderHook, act } from '@testing-library/react-hooks'; -const mockContextState = ({ term }: { term: string }) => { - return { - term, +import { useApi } from '@backstage/core'; + +import { useSearch, SearchContextProvider } from './SearchContext'; + +jest.mock('@backstage/core', () => ({ + ...jest.requireActual('@backstage/core'), + useApi: jest.fn(), +})); + +describe('SearchContext', () => { + const _alphaPerformSearch = jest.fn(); + + const wrapper = ({ children, initialState }: any) => ( + + {children} + + ); + + const initialState = { + term: '', pageCursor: '', filters: {}, types: ['*'], - result: { results: [], loading: false, error: undefined }, - setTerm: jest.fn(), - setFilters: jest.fn(), - setTypes: jest.fn(), - setPageCursor: jest.fn(), }; -}; -const MockSearchContextConsumer = () => { - const { term } = SearchContext.useSearch(); + beforeEach(() => { + _alphaPerformSearch.mockResolvedValue([]); + (useApi as jest.Mock).mockReturnValue({ _alphaPerformSearch }); + }); - return
{term}
; -}; - -describe('useSearch', () => { - afterEach(() => { + afterAll(() => { jest.resetAllMocks(); }); - it('context should use initial term', async () => { - jest.spyOn(SearchContext, 'useSearch'); - const { getByRole } = await renderInTestApp(); - expect(getByRole('heading')).toBeInTheDocument(); + it('Passes children', async () => { + const text = 'text'; + + render( + + {text} + , + ); + + await waitFor(() => { + expect(screen.getByText(text)).toBeInTheDocument(); + }); }); - it('context should use mocked term', async () => { - jest - .spyOn(SearchContext, 'useSearch') - .mockImplementation(() => mockContextState({ term: 'new-term' })); + it('Throws error when no context is set', () => { + const { result } = renderHook(() => useSearch()); - const { getByRole } = await renderInTestApp(); + expect(result.error).toEqual( + Error('useSearch must be used within a SearchContextProvider'), + ); + }); - expect(getByRole('heading', { name: 'new-term' })).toBeInTheDocument(); + it('Uses initial state values', async () => { + const { result, waitForNextUpdate } = renderHook(() => useSearch(), { + wrapper, + initialProps: { + initialState, + }, + }); + + await waitForNextUpdate(); + + expect(result.current).toEqual(expect.objectContaining(initialState)); + }); + + it('Resets cursor when term is set (and different from previous)', async () => { + const { result, waitForNextUpdate } = renderHook(() => useSearch(), { + wrapper, + initialProps: { + initialState: { + ...initialState, + pageCursor: '1', + }, + }, + }); + + await waitForNextUpdate(); + + expect(result.current.pageCursor).toBe('1'); + + act(() => { + result.current.setTerm('first term'); + }); + + await waitForNextUpdate(); + + expect(result.current.pageCursor).toBe('1'); + + act(() => { + result.current.setTerm('second term'); + }); + + await waitForNextUpdate(); + + expect(result.current.pageCursor).toBe(''); + }); + + describe('Performs search (and sets results)', () => { + it('When term is set', async () => { + const { result, waitForNextUpdate } = renderHook(() => useSearch(), { + wrapper, + initialProps: { + initialState, + }, + }); + + await waitForNextUpdate(); + + const term = 'term'; + + act(() => { + result.current.setTerm(term); + }); + + await waitForNextUpdate(); + + expect(_alphaPerformSearch).toHaveBeenLastCalledWith({ + ...initialState, + term, + }); + }); + + it('When filters are set', async () => { + const { result, waitForNextUpdate } = renderHook(() => useSearch(), { + wrapper, + initialProps: { + initialState, + }, + }); + + await waitForNextUpdate(); + + const filters = { filter: 'filter' }; + + act(() => { + result.current.setFilters(filters); + }); + + await waitForNextUpdate(); + + expect(_alphaPerformSearch).toHaveBeenLastCalledWith({ + ...initialState, + filters, + }); + }); + + it('When pageCursor is set', async () => { + const { result, waitForNextUpdate } = renderHook(() => useSearch(), { + wrapper, + initialProps: { + initialState, + }, + }); + + await waitForNextUpdate(); + + const pageCursor = 'pageCursor'; + + act(() => { + result.current.setPageCursor(pageCursor); + }); + + await waitForNextUpdate(); + + expect(_alphaPerformSearch).toHaveBeenLastCalledWith({ + ...initialState, + pageCursor, + }); + }); + + it('When types is set', async () => { + const { result, waitForNextUpdate } = renderHook(() => useSearch(), { + wrapper, + initialProps: { + initialState, + }, + }); + + await waitForNextUpdate(); + + const types = ['type']; + + act(() => { + result.current.setTypes(types); + }); + + await waitForNextUpdate(); + + expect(_alphaPerformSearch).toHaveBeenLastCalledWith({ + ...initialState, + types, + }); + }); }); }); diff --git a/plugins/search/src/components/SearchContext/SearchContext.tsx b/plugins/search/src/components/SearchContext/SearchContext.tsx index 40a9c78cf6..55ccd096a1 100644 --- a/plugins/search/src/components/SearchContext/SearchContext.tsx +++ b/plugins/search/src/components/SearchContext/SearchContext.tsx @@ -45,7 +45,7 @@ type SettableSearchContext = Omit< 'result' | 'setTerm' | 'setTypes' | 'setFilters' | 'setPageCursor' >; -const SearchContext = createContext({} as SearchContextValue); +const SearchContext = createContext(undefined); export const SearchContextProvider = ({ initialState = { From 6ac8da4d49ef4ab6643d0dfa56799a08e8214fba Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 26 May 2021 12:18:42 +0200 Subject: [PATCH 068/102] Adding test coverage for SearchPageNext. Signed-off-by: Eric Peterson --- .../SearchPageNext/SearchPageNext.test.tsx | 105 ++++++++++++++++++ .../SearchPageNext/SearchPageNext.tsx | 3 +- 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 plugins/search/src/components/SearchPageNext/SearchPageNext.test.tsx diff --git a/plugins/search/src/components/SearchPageNext/SearchPageNext.test.tsx b/plugins/search/src/components/SearchPageNext/SearchPageNext.test.tsx new file mode 100644 index 0000000000..f00772f1c5 --- /dev/null +++ b/plugins/search/src/components/SearchPageNext/SearchPageNext.test.tsx @@ -0,0 +1,105 @@ +/* + * 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 React from 'react'; +import { renderInTestApp } from '@backstage/test-utils'; +import { render, screen, waitFor } from '@testing-library/react'; +import { useLocation, Outlet } from 'react-router'; + +import { useSearch, SearchContextProvider } from '../SearchContext'; +import { SearchPageNext } from './'; + +jest.mock('react-router', () => ({ + ...jest.requireActual('react-router'), + useLocation: jest.fn().mockReturnValue({ + search: '', + }), + Outlet: jest.fn().mockReturnValue(null), +})); + +jest.mock('../SearchContext', () => ({ + ...jest.requireActual('../SearchContext'), + SearchContextProvider: jest + .fn() + .mockImplementation(({ children }) => children), + useSearch: jest.fn().mockReturnValue({ + term: '', + types: [], + filters: {}, + pageCursor: '', + }), +})); + +describe('SearchPage', () => { + const origReplaceState = window.history.replaceState; + + beforeEach(() => { + window.history.replaceState = jest.fn(); + }); + + afterEach(() => { + window.history.replaceState = origReplaceState; + }); + + it('uses initial term state from location', async () => { + // Given this initial location.search value... + const expectedFilterField = 'anyKey'; + const expectedFilterValue = 'anyValue'; + const expectedTerm = 'justin bieber'; + const expectedTypes = ['software-catalog']; + const expectedFilters = { [expectedFilterField]: expectedFilterValue }; + const expectedPageCursor = 'page2-or-something'; + + // e.g. ?query=petstore&pageCursor=1&filters[lifecycle][]=experimental&filters[kind]=Component + (useLocation as jest.Mock).mockReturnValueOnce({ + search: `?query=${expectedTerm}&types[]=${expectedTypes[0]}&filters[${expectedFilterField}]=${expectedFilterValue}&pageCursor=${expectedPageCursor}`, + }); + + // When we render the page... + await renderInTestApp(); + + // Then search context should be initialized with these values... + const calls = (SearchContextProvider as jest.Mock).mock.calls[0]; + const actualInitialState = calls[0].initialState; + expect(actualInitialState.term).toEqual(expectedTerm); + expect(actualInitialState.types).toEqual(expectedTypes); + expect(actualInitialState.pageCursor).toEqual(expectedPageCursor); + expect(actualInitialState.filters).toStrictEqual(expectedFilters); + }); + + it('renders provided router element', async () => { + await renderInTestApp(); + + expect(Outlet).toHaveBeenCalled(); + }); + + it('replaces window history with expected query parameters', async () => { + (useSearch as jest.Mock).mockReturnValueOnce({ + term: 'bieber', + types: ['software-catalog'], + pageCursor: 'page2-or-something', + filters: { anyKey: 'anyValue' }, + }); + const expectedLocation = encodeURI( + '?query=bieber&types[]=software-catalog&pageCursor=page2-or-something&filters[anyKey]=anyValue', + ); + + await renderInTestApp(); + + const calls = (window.history.replaceState as jest.Mock).mock.calls[0]; + expect(calls[2]).toContain(expectedLocation); + }); +}); diff --git a/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx b/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx index cd069b8d2e..f851778ddf 100644 --- a/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx +++ b/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx @@ -49,10 +49,11 @@ export const SearchPageNext = () => { const filters = (query.filters as JsonObject) || {}; const queryString = (query.query as string) || ''; const pageCursor = (query.pageCursor as string) || ''; + const types = (query.types as string[]) || []; const initialState = { term: queryString || '', - types: [], + types, pageCursor, filters, }; From f131c3de21e52b6d0ddeb0985a579db99179ef67 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 26 May 2021 12:22:51 +0200 Subject: [PATCH 069/102] Fix Lunr tests to match new translation logic. Signed-off-by: Eric Peterson --- .../src/engines/LunrSearchEngine.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts index 6c348235e0..f597327dbf 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts @@ -53,7 +53,7 @@ describe('LunrSearchEngine', () => { expect(mockedTranslatedQuery).toMatchObject({ documentTypes: ['*'], - lunrQueryString: 'testTerm', + lunrQueryString: '+testTerm', }); }); @@ -66,7 +66,7 @@ describe('LunrSearchEngine', () => { expect(mockedTranslatedQuery).toMatchObject({ documentTypes: ['*'], - lunrQueryString: 'testTerm +kind:testKind', + lunrQueryString: '+testTerm +kind:testKind', }); }); @@ -79,7 +79,7 @@ describe('LunrSearchEngine', () => { expect(mockedTranslatedQuery).toMatchObject({ documentTypes: ['*'], - lunrQueryString: 'testTerm +kind:testKind +namespace:testNameSpace', + lunrQueryString: '+testTerm +kind:testKind +namespace:testNameSpace', }); }); }); From 970082195c0fe1b5e706648790e9c503a2a53ab1 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Wed, 26 May 2021 13:56:41 +0200 Subject: [PATCH 070/102] test(plugins/search): complete SearchResultNext coverage Signed-off-by: Camila Belo Signed-off-by: Eric Peterson --- .../SearchContext/SearchContext.test.tsx | 2 +- .../SearchPageNext/SearchPageNext.test.tsx | 1 - .../SearchResultNext.test.tsx | 92 +++++++++++++++++++ 3 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 plugins/search/src/components/SearchResultNext/SearchResultNext.test.tsx diff --git a/plugins/search/src/components/SearchContext/SearchContext.test.tsx b/plugins/search/src/components/SearchContext/SearchContext.test.tsx index c45d74eade..1b79e62698 100644 --- a/plugins/search/src/components/SearchContext/SearchContext.test.tsx +++ b/plugins/search/src/components/SearchContext/SearchContext.test.tsx @@ -44,7 +44,7 @@ describe('SearchContext', () => { }; beforeEach(() => { - _alphaPerformSearch.mockResolvedValue([]); + _alphaPerformSearch.mockResolvedValue({}); (useApi as jest.Mock).mockReturnValue({ _alphaPerformSearch }); }); diff --git a/plugins/search/src/components/SearchPageNext/SearchPageNext.test.tsx b/plugins/search/src/components/SearchPageNext/SearchPageNext.test.tsx index f00772f1c5..5890f8cf41 100644 --- a/plugins/search/src/components/SearchPageNext/SearchPageNext.test.tsx +++ b/plugins/search/src/components/SearchPageNext/SearchPageNext.test.tsx @@ -16,7 +16,6 @@ import React from 'react'; import { renderInTestApp } from '@backstage/test-utils'; -import { render, screen, waitFor } from '@testing-library/react'; import { useLocation, Outlet } from 'react-router'; import { useSearch, SearchContextProvider } from '../SearchContext'; diff --git a/plugins/search/src/components/SearchResultNext/SearchResultNext.test.tsx b/plugins/search/src/components/SearchResultNext/SearchResultNext.test.tsx new file mode 100644 index 0000000000..98fc285265 --- /dev/null +++ b/plugins/search/src/components/SearchResultNext/SearchResultNext.test.tsx @@ -0,0 +1,92 @@ +/* + * 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 React from 'react'; +import { render, waitFor } from '@testing-library/react'; + +import { SearchResultNext } from './SearchResultNext'; +import { useSearch } from '../SearchContext'; + +jest.mock('../SearchContext', () => ({ + ...jest.requireActual('../SearchContext'), + useSearch: jest.fn().mockReturnValue({ + result: {}, + }), +})); + +describe('SearchResultNext', () => { + it('Progress rendered on Loading state', async () => { + (useSearch as jest.Mock).mockReturnValueOnce({ + result: { loading: true }, + }); + + const { getByRole } = render( + {() => <>}, + ); + + await waitFor(() => { + expect(getByRole('progressbar')).toBeInTheDocument(); + }); + }); + + it('Alert rendered on Error state', async () => { + const error = 'error'; + (useSearch as jest.Mock).mockReturnValueOnce({ + result: { loading: false, error }, + }); + + const { getByRole } = render( + {() => <>}, + ); + + await waitFor(() => { + expect(getByRole('alert')).toHaveTextContent( + `Error encountered while fetching search results. ${error}`, + ); + }); + }); + + it('On empty result value state', async () => { + (useSearch as jest.Mock).mockReturnValueOnce({ + result: { loading: false, error: '', value: undefined }, + }); + + const { getByRole } = render( + {() => <>}, + ); + + await waitFor(() => { + expect( + getByRole('heading', { name: 'Sorry, no results were found' }), + ).toBeInTheDocument(); + }); + }); + + it('Calls children with results set to result.value', () => { + (useSearch as jest.Mock).mockReturnValueOnce({ + result: { loading: false, error: '', value: { results: [] } }, + }); + + render( + + {({ results }) => { + expect(results).toEqual([]); + return <>; + }} + , + ); + }); +}); From 51e2b40cbb354569afeddd8e665705f0fb8cd84d Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Wed, 26 May 2021 17:29:36 +0200 Subject: [PATCH 071/102] test(plugins/search): complete SearchFilterNext coverage Signed-off-by: Camila Belo Signed-off-by: Eric Peterson --- .../SearchFilterNext.test.tsx | 362 +++++++++++++++++- 1 file changed, 349 insertions(+), 13 deletions(-) diff --git a/plugins/search/src/components/SearchFilterNext/SearchFilterNext.test.tsx b/plugins/search/src/components/SearchFilterNext/SearchFilterNext.test.tsx index 63822b9e00..ed9c0ecd74 100644 --- a/plugins/search/src/components/SearchFilterNext/SearchFilterNext.test.tsx +++ b/plugins/search/src/components/SearchFilterNext/SearchFilterNext.test.tsx @@ -15,23 +15,359 @@ */ import React from 'react'; -import { renderInTestApp } from '@backstage/test-utils'; +import { screen, render, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { useApi } from '@backstage/core'; + import { SearchFilterNext } from './SearchFilterNext'; +import { SearchContextProvider } from '../SearchContext'; -const MockFilterComponent = ({ name }: { name: string }) => { - return
{name}
; -}; +jest.mock('@backstage/core', () => ({ + ...jest.requireActual('@backstage/core'), + useApi: jest.fn().mockReturnValue({ + _alphaPerformSearch: jest.fn().mockResolvedValue({}), + }), +})); -describe('', () => { - it('renders without exploding', async () => { - const props = { - name: 'filter name', - }; +describe('SearchFilterNext', () => { + const initialState = { + term: '', + filters: {}, + types: [], + pageCursor: '', + }; - const { getByRole } = await renderInTestApp( - , - ); + const name = 'field'; + const values = ['value1', 'value2']; + const filters = { unrelated: 'unrelated' }; - expect(getByRole('heading', { name: 'filter name' })).toBeInTheDocument(); + const _alphaPerformSearch = jest.fn().mockResolvedValue({}); + (useApi as jest.Mock).mockReturnValue({ _alphaPerformSearch }); + + it('Check that element was rendered and received props', async () => { + const CustomFilter = (props: { name: string }) =>
{props.name}
; + + render(); + + expect(screen.getByRole('heading', { name })).toBeInTheDocument(); + }); + + describe('Checkbox', () => { + it('Renders field name and values when provided as props', async () => { + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByText(name)).toBeInTheDocument(); + }); + + expect( + screen.getByRole('checkbox', { name: values[0] }), + ).toBeInTheDocument(); + expect( + screen.getByRole('checkbox', { name: values[1] }), + ).toBeInTheDocument(); + }); + + it('Renders correctly based on filter state', async () => { + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByText(name)).toBeInTheDocument(); + }); + + expect( + screen.getByRole('checkbox', { name: values[0] }), + ).not.toBeChecked(); + expect(screen.getByRole('checkbox', { name: values[1] })).toBeChecked(); + }); + + it('Renders correctly based on defaultValue', async () => { + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByText(name)).toBeInTheDocument(); + }); + + expect(screen.getByRole('checkbox', { name: values[0] })).toBeChecked(); + expect( + screen.getByRole('checkbox', { name: values[1] }), + ).not.toBeChecked(); + }); + + it('Checking / unchecking a value sets filter state', async () => { + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByText(name)).toBeInTheDocument(); + }); + + const checkBox = screen.getByRole('checkbox', { name: values[0] }); + + // Check the box. + userEvent.click(checkBox); + await waitFor(() => { + expect(_alphaPerformSearch).toHaveBeenLastCalledWith( + expect.objectContaining({ filters: { field: [values[0]] } }), + ); + }); + + // Uncheck the box. + userEvent.click(checkBox); + await waitFor(() => { + expect(_alphaPerformSearch).toHaveBeenLastCalledWith( + expect.objectContaining({ filters: {} }), + ); + }); + }); + + it('Checking / unchecking a value maintains unrelated filter state', async () => { + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByText(name)).toBeInTheDocument(); + }); + + const checkBox = screen.getByRole('checkbox', { name: values[0] }); + + // Check the box. + userEvent.click(checkBox); + await waitFor(() => { + expect(_alphaPerformSearch).toHaveBeenLastCalledWith( + expect.objectContaining({ + filters: { ...filters, field: [values[0]] }, + }), + ); + }); + + // Uncheck the box. + userEvent.click(checkBox); + await waitFor(() => { + expect(_alphaPerformSearch).toHaveBeenLastCalledWith( + expect.objectContaining({ filters }), + ); + }); + }); + }); + + describe('Select', () => { + it('Renders field name and values when provided as props', async () => { + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByText(name)).toBeInTheDocument(); + }); + + userEvent.click(screen.getByRole('button')); + + await waitFor(() => { + expect(screen.getByRole('listbox')).toBeInTheDocument(); + }); + + expect( + screen.getByRole('option', { name: values[0] }), + ).toBeInTheDocument(); + expect( + screen.getByRole('option', { name: values[1] }), + ).toBeInTheDocument(); + }); + + it('Renders correctly based on filter state', async () => { + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByText(name)).toBeInTheDocument(); + }); + + userEvent.click(screen.getByRole('button')); + + await waitFor(() => { + expect(screen.getByRole('listbox')).toBeInTheDocument(); + }); + + expect(screen.getByRole('option', { name: values[0] })).toHaveAttribute( + 'aria-selected', + 'true', + ); + expect( + screen.getByRole('option', { name: values[1] }), + ).not.toHaveAttribute('aria-selected'); + expect(screen.getByRole('option', { name: 'All' })).not.toHaveAttribute( + 'aria-selected', + ); + }); + + it('Renders correctly based on defaultValue', async () => { + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByText(name)).toBeInTheDocument(); + }); + + userEvent.click(screen.getByRole('button')); + + await waitFor(() => { + expect(screen.getByRole('listbox')).toBeInTheDocument(); + }); + + expect(screen.getByRole('option', { name: values[0] })).toHaveAttribute( + 'aria-selected', + 'true', + ); + expect( + screen.getByRole('option', { name: values[1] }), + ).not.toHaveAttribute('aria-selected'); + expect(screen.getByRole('option', { name: 'All' })).not.toHaveAttribute( + 'aria-selected', + ); + }); + + it('Selecting a value sets filter state', async () => { + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByText(name)).toBeInTheDocument(); + }); + + const button = screen.getByRole('button'); + + userEvent.click(button); + + await waitFor(() => { + expect(screen.getByRole('listbox')).toBeInTheDocument(); + }); + + userEvent.click(screen.getByRole('option', { name: values[0] })); + + await waitFor(() => { + expect(_alphaPerformSearch).toHaveBeenLastCalledWith( + expect.objectContaining({ + filters: { [name]: values[0] }, + }), + ); + }); + + userEvent.click(button); + + await waitFor(() => { + expect(screen.getByRole('listbox')).toBeInTheDocument(); + }); + + userEvent.click(screen.getByRole('option', { name: 'All' })); + + await waitFor(() => { + expect(_alphaPerformSearch).toHaveBeenLastCalledWith( + expect.objectContaining({ + filters: {}, + }), + ); + }); + }); + + it('Selecting a value maintains unrelated filter state', async () => { + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByText(name)).toBeInTheDocument(); + }); + + const button = screen.getByRole('button'); + + userEvent.click(button); + + await waitFor(() => { + expect(screen.getByRole('listbox')).toBeInTheDocument(); + }); + + userEvent.click(screen.getByRole('option', { name: values[0] })); + + await waitFor(() => { + expect(_alphaPerformSearch).toHaveBeenLastCalledWith( + expect.objectContaining({ + filters: { ...filters, [name]: values[0] }, + }), + ); + }); + + userEvent.click(button); + + await waitFor(() => { + expect(screen.getByRole('listbox')).toBeInTheDocument(); + }); + + userEvent.click(screen.getByRole('option', { name: 'All' })); + + await waitFor(() => { + expect(_alphaPerformSearch).toHaveBeenLastCalledWith( + expect.objectContaining({ filters }), + ); + }); + }); }); }); From c568e721f4d898c9abcd9a940341f27240ed43f6 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Wed, 26 May 2021 21:11:35 +0200 Subject: [PATCH 072/102] test(plugins/search): complete SearchBarNext coverage Signed-off-by: Camila Belo Signed-off-by: Eric Peterson --- .../SearchBarNext/SearchBarNext.test.tsx | 122 ++++++++++++++++-- .../SearchBarNext/SearchBarNext.tsx | 46 +++---- .../SearchFilterNext.test.tsx | 8 +- 3 files changed, 137 insertions(+), 39 deletions(-) diff --git a/plugins/search/src/components/SearchBarNext/SearchBarNext.test.tsx b/plugins/search/src/components/SearchBarNext/SearchBarNext.test.tsx index f29cc53b71..a5075c5593 100644 --- a/plugins/search/src/components/SearchBarNext/SearchBarNext.test.tsx +++ b/plugins/search/src/components/SearchBarNext/SearchBarNext.test.tsx @@ -15,7 +15,8 @@ */ import React from 'react'; -import { renderInTestApp } from '@backstage/test-utils'; +import { screen, render, waitFor, act } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { useApi } from '@backstage/core'; import { SearchContextProvider } from '../SearchContext'; @@ -23,12 +24,10 @@ import { SearchBarNext } from './SearchBarNext'; jest.mock('@backstage/core', () => ({ ...jest.requireActual('@backstage/core'), - useApi: jest.fn(), + useApi: jest.fn().mockReturnValue({}), })); -describe('', () => { - const _alphaPerformSearch = jest.fn(); - +describe('SearchBarNext', () => { const initialState = { term: '', pageCursor: '', @@ -36,24 +35,119 @@ describe('', () => { types: ['*'], }; - beforeEach(() => { - _alphaPerformSearch.mockResolvedValue([]); - (useApi as jest.Mock).mockReturnValue({ _alphaPerformSearch }); - }); + const name = 'Search term'; + const term = 'term'; + + const _alphaPerformSearch = jest.fn().mockResolvedValue({}); + (useApi as jest.Mock).mockReturnValue({ _alphaPerformSearch }); afterAll(() => { jest.resetAllMocks(); }); - it('renders without exploding', async () => { - const { getByRole } = await renderInTestApp( + it('Renders without exploding', async () => { + render( , ); - expect( - getByRole('textbox', { name: 'search backstage' }), - ).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByRole('textbox', { name })).toBeInTheDocument(); + }); + }); + + it('Renders based on initial search', async () => { + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByRole('textbox', { name })).toHaveValue(term); + }); + }); + + it('Updates term state when text is entered', async () => { + render( + + + , + ); + + const textbox = screen.getByRole('textbox', { name }); + + const value = 'value'; + + userEvent.type(textbox, value); + + await waitFor(() => { + expect(textbox).toHaveValue(value); + }); + + expect(_alphaPerformSearch).toHaveBeenLastCalledWith( + expect.objectContaining({ term: value }), + ); + }); + + it('Clear button clears term state', async () => { + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByRole('textbox', { name })).toHaveValue(term); + }); + + userEvent.click(screen.getByRole('button', { name: 'Clear term' })); + + await waitFor(() => { + expect(screen.getByRole('textbox', { name })).toHaveValue(''); + }); + + expect(_alphaPerformSearch).toHaveBeenLastCalledWith( + expect.objectContaining({ term: '' }), + ); + }); + + it('Adheres to provided debounceTime', async () => { + jest.useFakeTimers(); + + const debounceTime = 600; + + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByRole('textbox', { name })).toBeInTheDocument(); + }); + + const textbox = screen.getByRole('textbox', { name }); + + const value = 'value'; + + userEvent.type(textbox, value); + + expect(_alphaPerformSearch).not.toHaveBeenLastCalledWith( + expect.objectContaining({ term: value }), + ); + + act(() => { + jest.advanceTimersByTime(debounceTime); + }); + + await waitFor(() => { + expect(textbox).toHaveValue(value); + }); + + expect(_alphaPerformSearch).toHaveBeenLastCalledWith( + expect.objectContaining({ term: value }), + ); }); }); diff --git a/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx b/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx index 014e3d6ee4..b223e6c2cd 100644 --- a/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx +++ b/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx @@ -14,17 +14,26 @@ * limitations under the License. */ -import React, { useState } from 'react'; +import React, { ChangeEvent, useState } from 'react'; import { useDebounce } from 'react-use'; -import { Paper, InputBase, IconButton, makeStyles } from '@material-ui/core'; +import { + Theme, + Paper, + InputBase, + InputAdornment, + IconButton, + makeStyles, +} from '@material-ui/core'; import SearchIcon from '@material-ui/icons/Search'; import ClearButton from '@material-ui/icons/Clear'; + import { useSearch } from '../SearchContext'; -const useStyles = makeStyles(() => ({ +const useStyles = makeStyles((theme: Theme) => ({ root: { display: 'flex', alignItems: 'center', + padding: theme.spacing(0, 0, 0, 1.5), }, input: { flex: 1, @@ -40,36 +49,29 @@ export const SearchBarNext = ({ debounceTime = 0 }: Props) => { const { term, setTerm } = useSearch(); const [value, setValue] = useState(term); - useDebounce( - () => { - setTerm(value); - }, - debounceTime, - [value], - ); + useDebounce(() => setTerm(value), debounceTime, [value]); - const handleSearch = (event: React.ChangeEvent | React.FormEvent) => { - event.preventDefault(); - setValue((event.target as HTMLInputElement).value as string); + const handleSearch = (e: ChangeEvent) => { + setValue(e.target.value); }; - const handleClearSearchBar = () => { - setTerm(''); - }; + const handleClear = () => setValue(''); return ( - - - - + + + + } /> - + diff --git a/plugins/search/src/components/SearchFilterNext/SearchFilterNext.test.tsx b/plugins/search/src/components/SearchFilterNext/SearchFilterNext.test.tsx index ed9c0ecd74..1a35bf54d4 100644 --- a/plugins/search/src/components/SearchFilterNext/SearchFilterNext.test.tsx +++ b/plugins/search/src/components/SearchFilterNext/SearchFilterNext.test.tsx @@ -24,9 +24,7 @@ import { SearchContextProvider } from '../SearchContext'; jest.mock('@backstage/core', () => ({ ...jest.requireActual('@backstage/core'), - useApi: jest.fn().mockReturnValue({ - _alphaPerformSearch: jest.fn().mockResolvedValue({}), - }), + useApi: jest.fn().mockReturnValue({}), })); describe('SearchFilterNext', () => { @@ -44,6 +42,10 @@ describe('SearchFilterNext', () => { const _alphaPerformSearch = jest.fn().mockResolvedValue({}); (useApi as jest.Mock).mockReturnValue({ _alphaPerformSearch }); + afterAll(() => { + jest.resetAllMocks(); + }); + it('Check that element was rendered and received props', async () => { const CustomFilter = (props: { name: string }) =>
{props.name}
; From 49df6413f37589b082bf624080e9780cbb5cb10f Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Wed, 26 May 2021 21:56:27 +0200 Subject: [PATCH 073/102] test(plugins/search): complete DefaultResultListItem coverage Signed-off-by: Camila Belo Signed-off-by: Eric Peterson --- .../DefaultResultListItem.test.jsx | 41 +++++++++++++++++++ .../DefaultResultListItem.tsx | 7 +++- .../SearchResultNext/SearchResultNext.tsx | 13 +++--- 3 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.test.jsx diff --git a/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.test.jsx b/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.test.jsx new file mode 100644 index 0000000000..60ad453597 --- /dev/null +++ b/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.test.jsx @@ -0,0 +1,41 @@ +/* + * 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 React from 'react'; +import { screen } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; + +import { DefaultResultListItem } from './DefaultResultListItem'; + +describe('DefaultResultListItem', () => { + const result = { + title: 'title', + text: 'text', + location: '/location', + }; + + it('Links to result.location', async () => { + await renderInTestApp(); + expect(screen.getByRole('link')).toHaveAttribute('href', result.location); + }); + + it('Includes primary/secondary text (title / text)', async () => { + await renderInTestApp(); + expect(screen.getByRole('listitem')).toHaveTextContent( + result.title + result.text, + ); + }); +}); diff --git a/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.tsx b/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.tsx index 8571caa49d..5f2d4bf87f 100644 --- a/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.tsx +++ b/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.tsx @@ -16,9 +16,14 @@ import React from 'react'; import { Link } from '@backstage/core'; +import { IndexableDocument } from '@backstage/search-common'; import { ListItem, ListItemText, Divider } from '@material-ui/core'; -export const DefaultResultListItem = ({ result }: any) => { +type Props = { + result: IndexableDocument; +}; + +export const DefaultResultListItem = ({ result }: Props) => { return ( diff --git a/plugins/search/src/components/SearchResultNext/SearchResultNext.tsx b/plugins/search/src/components/SearchResultNext/SearchResultNext.tsx index c4e3edaa4c..84d3759ad3 100644 --- a/plugins/search/src/components/SearchResultNext/SearchResultNext.tsx +++ b/plugins/search/src/components/SearchResultNext/SearchResultNext.tsx @@ -13,22 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +import React from 'react'; import { EmptyState, Progress } from '@backstage/core'; import { SearchResult } from '@backstage/search-common'; import { Alert } from '@material-ui/lab'; -import React from 'react'; import { useSearch } from '../SearchContext'; -type ChildrenArguments = { - results: SearchResult[]; +type Props = { + children: (results: { results: SearchResult[] }) => JSX.Element; }; -export const SearchResultNext = ({ - children, -}: { - children: (results: ChildrenArguments) => JSX.Element; -}) => { +export const SearchResultNext = ({ children }: Props) => { const { result: { loading, error, value }, } = useSearch(); From 109e186b5f993b85612e9e59cb5bb01b8fd1beba Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 27 May 2021 00:38:48 +0200 Subject: [PATCH 074/102] test(plugins/search): complete AlphaPerformSearchApi coverage Signed-off-by: Camila Belo Signed-off-by: Eric Peterson --- plugins/search/src/apis.test.ts | 55 +++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 plugins/search/src/apis.test.ts diff --git a/plugins/search/src/apis.test.ts b/plugins/search/src/apis.test.ts new file mode 100644 index 0000000000..33714bc688 --- /dev/null +++ b/plugins/search/src/apis.test.ts @@ -0,0 +1,55 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CatalogApi } from '@backstage/plugin-catalog-react'; + +import { SearchClient } from './apis'; + +describe('apis', () => { + const query = { + term: '', + filters: {}, + types: [], + pageCursor: '', + }; + + const baseUrl = 'https://base-url.com/'; + const getBaseUrl = jest.fn().mockResolvedValue(baseUrl); + const client = new SearchClient({ + catalogApi: {} as CatalogApi, + discoveryApi: { getBaseUrl }, + }); + + const json = jest.fn(); + const originalFetch = window.fetch; + window.fetch = jest.fn().mockResolvedValue({ json }); + + afterAll(() => { + window.fetch = originalFetch; + }); + + it('Fetch is called with expected URL (including stringified Q params)', async () => { + await client._alphaPerformSearch(query); + expect(getBaseUrl).toHaveBeenLastCalledWith('search/query'); + expect(fetch).toHaveBeenLastCalledWith(`${baseUrl}?term=&pageCursor=`); + }); + + it('Resolves JSON from fetch response', async () => { + const result = { loading: false, error: '', value: {} }; + json.mockReturnValueOnce(result); + expect(await client._alphaPerformSearch(query)).toStrictEqual(result); + }); +}); From 6b8d6ccd13ef9b973bad2567632a86ec0962610e Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 27 May 2021 11:47:57 +0200 Subject: [PATCH 075/102] refactor(plugins/search): make components more stylable Signed-off-by: Camila Belo Signed-off-by: Eric Peterson --- .../app/src/components/search/SearchPage.tsx | 133 ++++++++++-------- .../SearchBarNext/SearchBarNext.tsx | 64 ++++----- .../SearchFilterNext/SearchFilterNext.tsx | 28 +++- 3 files changed, 127 insertions(+), 98 deletions(-) diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx index 3955ec1a2a..221d4a44c4 100644 --- a/packages/app/src/components/search/SearchPage.tsx +++ b/packages/app/src/components/search/SearchPage.tsx @@ -15,67 +15,88 @@ */ import React from 'react'; -import { Content, Header, Lifecycle, Page } from '@backstage/core'; -import { Grid, List, Card, CardContent } from '@material-ui/core'; -import { - SearchBarNext, - SearchResultNext, - DefaultResultListItem, - SearchFilterNext, -} from '@backstage/plugin-search'; -import { CatalogResultListItem } from '@backstage/plugin-catalog'; +import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; -export const searchPage = ( - -
} /> - - - - - - - - - ({ + bar: { + padding: theme.spacing(1, 0), + }, + filters: { + padding: theme.spacing(2), + }, + filter: { + '& + &': { + marginTop: theme.spacing(2.5), + }, + }, +})); + +const SearchPage = () => { + const classes = useStyles(); + + return ( + +
} /> + + + + + + + + + + - - - - - + + + + + {({ results }) => ( + + {results.map(({ type, document }) => { + switch (type) { + case 'software-catalog': + return ( + + ); + default: + return ( + + ); + } + })} + + )} + + - - - {({ results }) => ( - - {results.map(result => { - switch (result.type) { - case 'software-catalog': - return ( - - ); - default: - return ( - - ); - } - })} - - )} - - - - - -); + + + ); +}; + +export const searchPage = ; diff --git a/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx b/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx index b223e6c2cd..76bc9bf4f9 100644 --- a/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx +++ b/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx @@ -16,64 +16,52 @@ import React, { ChangeEvent, useState } from 'react'; import { useDebounce } from 'react-use'; -import { - Theme, - Paper, - InputBase, - InputAdornment, - IconButton, - makeStyles, -} from '@material-ui/core'; +import { InputBase, InputAdornment, IconButton } from '@material-ui/core'; import SearchIcon from '@material-ui/icons/Search'; import ClearButton from '@material-ui/icons/Clear'; import { useSearch } from '../SearchContext'; -const useStyles = makeStyles((theme: Theme) => ({ - root: { - display: 'flex', - alignItems: 'center', - padding: theme.spacing(0, 0, 0, 1.5), - }, - input: { - flex: 1, - }, -})); - type Props = { + className?: string; debounceTime?: number; }; -export const SearchBarNext = ({ debounceTime = 0 }: Props) => { - const classes = useStyles(); +export const SearchBarNext = ({ className, debounceTime = 0 }: Props) => { const { term, setTerm } = useSearch(); const [value, setValue] = useState(term); useDebounce(() => setTerm(value), debounceTime, [value]); - const handleSearch = (e: ChangeEvent) => { + const handleQuery = (e: ChangeEvent) => { setValue(e.target.value); }; const handleClear = () => setValue(''); return ( - - + + - - } - /> - - - - + + + } + endAdornment={ + + + + + + } + /> ); }; diff --git a/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx b/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx index 0c13fbc008..239707e5ab 100644 --- a/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx +++ b/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx @@ -35,6 +35,7 @@ const useStyles = makeStyles({ }); export type Component = { + className?: string; name: string; values?: string[]; defaultValue?: string[] | string | null; @@ -45,7 +46,12 @@ export type Props = Component & { debug?: boolean; }; -const CheckboxFilter = ({ name, defaultValue, values = [] }: Component) => { +const CheckboxFilter = ({ + className, + name, + defaultValue, + values = [], +}: Component) => { const classes = useStyles(); const { filters, setFilters } = useSearch(); @@ -72,7 +78,11 @@ const CheckboxFilter = ({ name, defaultValue, values = [] }: Component) => { }; return ( - + {name} {values.map((value: string) => ( { ); }; -const SelectFilter = ({ name, defaultValue, values = [] }: Component) => { +const SelectFilter = ({ + className, + name, + defaultValue, + values = [], +}: Component) => { const classes = useStyles(); const { filters, setFilters } = useSearch(); @@ -120,7 +135,12 @@ const SelectFilter = ({ name, defaultValue, values = [] }: Component) => { }; return ( - + {name} From 5a1c284b2c1fc04a24998bcf823a8d41c39cf121 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 27 May 2021 17:55:06 +0200 Subject: [PATCH 076/102] test(app/components/search): complete Page coverage Signed-off-by: Camila Belo Signed-off-by: Eric Peterson --- .../components/search/SearchPage.js | 121 ++++++++++++++++++ packages/app/cypress/support/commands.js | 19 +++ packages/app/cypress/support/index.js | 1 + 3 files changed, 141 insertions(+) create mode 100644 packages/app/cypress/integration/components/search/SearchPage.js create mode 100644 packages/app/cypress/support/commands.js diff --git a/packages/app/cypress/integration/components/search/SearchPage.js b/packages/app/cypress/integration/components/search/SearchPage.js new file mode 100644 index 0000000000..f97c26ee90 --- /dev/null +++ b/packages/app/cypress/integration/components/search/SearchPage.js @@ -0,0 +1,121 @@ +/* + * 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. + */ + +const API_ENDPOINT = 'http://localhost:7000/api/search/query'; + +describe('SearchPage', () => { + describe('Given a search context with a term, results, and filter values', () => { + it('The results are rendered as expected', () => { + const results = [ + { + type: 'software-catalog', + document: { + title: 'backstage', + text: 'Backstage system documentation', + location: '/result/location/path', + }, + }, + ]; + + cy.enterAsGuest(); + cy.visit('/search-next', { + onBeforeLoad(win) { + cy.stub(win, 'fetch') + .withArgs(`${API_ENDPOINT}?term=&pageCursor=`) + .resolves({ + ok: true, + json: () => ({ results }), + }); + }, + }); + cy.contains('Search'); + + cy.contains(results[0].document.title); + cy.contains(results[0].document.text); + cy.get(`a[href="${results[0].document.location}"]`).should('be.visible'); + }); + + it('The filters are rendered as expected', () => { + cy.enterAsGuest(); + cy.visit( + '/search-next?filters%5Bkind%5D=Component&filters%5Blifecycle%5D%5B%5D=experimental', + { + onBeforeLoad(win) { + cy.stub(win, 'fetch') + .withArgs( + `${API_ENDPOINT}?term=&filters%5Bkind%5D=Component&filters%5Blifecycle%5D%5B0%5D=experimental&pageCursor=`, + ) + .resolves({ + ok: true, + json: () => ({ results: [] }), + }); + }, + }, + ); + cy.contains('Search'); + + // lifecycle + cy.contains('lifecycle'); + + cy.contains('experimental'); + cy.get( + '[data-testid="search-checkboxfilter-next"] input[value="experimental"]', + ).should('have.attr', 'checked'); + + cy.contains('production'); + cy.get( + '[data-testid="search-checkboxfilter-next"] input[value="production"]', + ).should('not.have.attr', 'checked'); + + // kind + cy.contains('kind'); + cy.get( + '[data-testid="search-selectfilter-next"] [role="button"][aria-haspopup="listbox"]', + ).click(); + + cy.contains('All'); + cy.contains('Template'); + cy.contains('Component'); + + cy.get('[role="option"][data-value="Component"]').should( + 'have.attr', + 'aria-selected', + 'true', + ); + }); + + it('The search bar is rendered as expected', () => { + cy.enterAsGuest(); + cy.visit('/search-next?query=backstage', { + onBeforeLoad(win) { + cy.stub(win, 'fetch') + .withArgs(`${API_ENDPOINT}?term=backstage&pageCursor=`) + .resolves({ + ok: true, + json: () => ({ results: [] }), + }); + }, + }); + cy.contains('Search'); + + cy.get('[data-testid="search-bar-next"] input').should( + 'have.attr', + 'value', + 'backstage', + ); + }); + }); +}); diff --git a/packages/app/cypress/support/commands.js b/packages/app/cypress/support/commands.js new file mode 100644 index 0000000000..dd2b26634d --- /dev/null +++ b/packages/app/cypress/support/commands.js @@ -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. + */ +Cypress.Commands.add('enterAsGuest', () => { + cy.visit('/'); + cy.get('button').contains('Enter').click(); +}); diff --git a/packages/app/cypress/support/index.js b/packages/app/cypress/support/index.js index 8fc8ca91f1..c1f930027a 100644 --- a/packages/app/cypress/support/index.js +++ b/packages/app/cypress/support/index.js @@ -14,3 +14,4 @@ * limitations under the License. */ import '@testing-library/cypress/add-commands'; +import './commands'; From aeb224eb523eeec193e39709e0c2f925587584ef Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 31 May 2021 17:08:52 +0200 Subject: [PATCH 077/102] Resolve yarn build react version conflict something something Signed-off-by: Eric Peterson --- package.json | 1 + yarn.lock | 36 ++++++++---------------------------- 2 files changed, 9 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 1ffeb490a3..7169f0d8e8 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ ] }, "resolutions": { + "**/@types/react": "^16.9.0", "**/@roadiehq/**/@backstage/core": "*", "**/@roadiehq/**/@backstage/plugin-catalog": "*", "**/@roadiehq/**/@backstage/catalog-model": "*", diff --git a/yarn.lock b/yarn.lock index a5832e82da..fa077b9d5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6756,25 +6756,10 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^16.9": - version "16.9.37" - resolved "https://registry.npmjs.org/@types/react/-/react-16.9.37.tgz#8fb93e7dbd5b1d3796f69aa979a7fe0439bc7bea" - integrity sha512-ZqnAXallQiZ08LTSqMfWMNvAfJEzRLOxdlbbbCIJlYGjU98BEU6bE2uBpKPGeWn+v3hIgCraHKtqUcKZBzMP/Q== - dependencies: - "@types/prop-types" "*" - csstype "^2.2.0" - -"@types/react@16.4.6": - version "16.4.6" - resolved "https://registry.npmjs.org/@types/react/-/react-16.4.6.tgz#5024957c6bcef4f02823accf5974faba2e54fada" - integrity sha512-9LDZdhsuKSc+DjY65SjBkA958oBWcTWSVWAd2cD9XqKBjhGw1KzAkRhWRw2eIsXvaIE/TOTjjKMFVC+JA1iU4g== - dependencies: - csstype "^2.2.0" - -"@types/react@>=16.9.0": - version "17.0.8" - resolved "https://registry.npmjs.org/@types/react/-/react-17.0.8.tgz#fe76e3ba0fbb5602704110fd1e3035cf394778e3" - integrity sha512-3sx4c0PbXujrYAKwXxNONXUtRp9C+hE2di0IuxFyf5BELD+B+AXL8G7QrmSKhVwKZDbv0igiAjQAMhXj8Yg3aw== +"@types/react@*", "@types/react@16.4.6", "@types/react@>=16.9.0", "@types/react@^16.9", "@types/react@^16.9.0": + version "16.14.8" + resolved "https://registry.npmjs.org/@types/react/-/react-16.14.8.tgz#4aee3ab004cb98451917c9b7ada3c7d7e52db3fe" + integrity sha512-QN0/Qhmx+l4moe7WJuTxNiTsjBwlBGHqKGvInSQCBdo7Qio0VtOqwsC0Wq7q3PbJlB0cR4Y4CVo1OOe6BOsOmA== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -11082,7 +11067,7 @@ cssstyle@^2.2.0: dependencies: cssom "~0.3.6" -csstype@^2.2.0, csstype@^2.5.2, csstype@^2.5.7, csstype@^2.6.7: +csstype@^2.5.2, csstype@^2.5.7, csstype@^2.6.7: version "2.6.9" resolved "https://registry.npmjs.org/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098" integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q== @@ -14614,7 +14599,7 @@ graphql-extensions@^0.12.8: apollo-server-env "^3.0.0" apollo-server-types "^0.6.3" -graphql-language-service-interface@^2.8.2: +graphql-language-service-interface@2.8.2, graphql-language-service-interface@^2.8.2: version "2.8.2" resolved "https://registry.npmjs.org/graphql-language-service-interface/-/graphql-language-service-interface-2.8.2.tgz#b3bb2aef7eaf0dff0b4ea419fa412c5f66fa268b" integrity sha512-otbOQmhgkAJU1QJgQkMztNku6SbJLu/uodoFOYOOtJsizTjrMs93vkYaHCcYnLA3oi1Goj27XcHjMnRCYQOZXQ== @@ -14624,7 +14609,7 @@ graphql-language-service-interface@^2.8.2: graphql-language-service-utils "^2.5.1" vscode-languageserver-types "^3.15.1" -graphql-language-service-parser@^1.9.0: +graphql-language-service-parser@1.9.0, graphql-language-service-parser@^1.9.0: version "1.9.0" resolved "https://registry.npmjs.org/graphql-language-service-parser/-/graphql-language-service-parser-1.9.0.tgz#79af21294119a0a7e81b6b994a1af36833bab724" integrity sha512-B5xPZLbBmIp0kHvpY1Z35I5DtPoDK9wGxQVRDIzcBaiIvAmlTrDvjo3bu7vKREdjFbYKvWNgrEWENuprMbF17Q== @@ -26001,16 +25986,11 @@ typescript-json-schema@^0.49.0: typescript "^4.1.3" yargs "^16.2.0" -typescript@^4.0.3, typescript@^4.1.3: +typescript@^4.0.3, typescript@^4.1.3, typescript@~4.1.3: version "4.2.3" resolved "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3" integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw== -typescript@~4.1.3: - version "4.1.5" - resolved "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz#123a3b214aaff3be32926f0d8f1f6e704eb89a72" - integrity sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA== - ua-parser-js@^0.7.18: version "0.7.28" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" From b9b26ec57277048c896c8cb529957d7013cbd813 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Tue, 1 Jun 2021 10:42:42 +0200 Subject: [PATCH 078/102] Update decorator type-specific decorator test Signed-off-by: Eric Peterson --- plugins/search-backend-node/src/IndexBuilder.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/search-backend-node/src/IndexBuilder.test.ts b/plugins/search-backend-node/src/IndexBuilder.test.ts index c7a2a7817a..8d28698b18 100644 --- a/plugins/search-backend-node/src/IndexBuilder.test.ts +++ b/plugins/search-backend-node/src/IndexBuilder.test.ts @@ -131,7 +131,7 @@ describe('IndexBuilder', () => { // wait for async decorator execution await Promise.resolve(); expect(decoratorSpy).toHaveBeenCalled(); - expect(decoratorSpy).toHaveBeenCalledWith([docFixture]); + expect(decoratorSpy).toHaveBeenCalledWith(expectedType, [docFixture]); }); it('adds a type-specific decorator that should not be called', async () => { From 455b97b127dfc18befd2a6bda053b705ed6666ec Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Tue, 1 Jun 2021 11:15:52 +0200 Subject: [PATCH 079/102] Make type(s) readonly properties of Collator/Decorator classes Signed-off-by: Eric Peterson --- packages/backend/src/plugins/search.ts | 1 - packages/search-common/src/types.ts | 7 ++-- .../src/search/DefaultCatalogCollator.ts | 1 + .../src/IndexBuilder.test.ts | 38 +++++++++++-------- .../search-backend-node/src/IndexBuilder.ts | 19 ++++------ plugins/search-backend-node/src/types.ts | 11 ------ 6 files changed, 35 insertions(+), 42 deletions(-) diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts index 861723ad47..cfd3bb03eb 100644 --- a/packages/backend/src/plugins/search.ts +++ b/packages/backend/src/plugins/search.ts @@ -30,7 +30,6 @@ export default async function createPlugin({ const indexBuilder = new IndexBuilder({ logger, searchEngine }); indexBuilder.addCollator({ - type: 'software-catalog', defaultRefreshIntervalSeconds: 10, collator: new DefaultCatalogCollator({ discovery }), }); diff --git a/packages/search-common/src/types.ts b/packages/search-common/src/types.ts index 2aa45e0510..792b9f3a2a 100644 --- a/packages/search-common/src/types.ts +++ b/packages/search-common/src/types.ts @@ -58,6 +58,7 @@ export interface IndexableDocument { * search. */ export interface DocumentCollator { + readonly type: string; execute(): Promise; } @@ -66,8 +67,6 @@ export interface DocumentCollator { * additional metadata. */ export interface DocumentDecorator { - execute( - type: string, - documents: IndexableDocument[], - ): Promise; + readonly types?: string[]; + execute(documents: IndexableDocument[]): Promise; } diff --git a/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts b/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts index 068b9e36ae..70b8010d87 100644 --- a/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts +++ b/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts @@ -30,6 +30,7 @@ export interface CatalogEntityDocument extends IndexableDocument { export class DefaultCatalogCollator implements DocumentCollator { protected discovery: PluginEndpointDiscovery; protected locationTemplate: string; + public readonly type: string = 'software-catalog'; constructor({ discovery, diff --git a/plugins/search-backend-node/src/IndexBuilder.test.ts b/plugins/search-backend-node/src/IndexBuilder.test.ts index 8d28698b18..cac2975781 100644 --- a/plugins/search-backend-node/src/IndexBuilder.test.ts +++ b/plugins/search-backend-node/src/IndexBuilder.test.ts @@ -24,22 +24,33 @@ import { IndexBuilder } from './IndexBuilder'; import { LunrSearchEngine, SearchEngine } from './index'; class TestDocumentCollator implements DocumentCollator { - async execute() { + readonly type: string = 'anything'; + async execute(): Promise { return []; } } +class TypedDocumentCollator extends TestDocumentCollator { + readonly type = 'an-expected-type'; +} + class TestDocumentDecorator implements DocumentDecorator { - async execute(_type: string, documents: IndexableDocument[]) { + async execute(documents: IndexableDocument[]) { return documents; } } +class TypedDocumentDecorator extends TestDocumentDecorator { + readonly types = ['an-expected-type']; +} + +class DifferentlyTypedDocumentDecorator extends TestDocumentDecorator { + readonly types = ['not-the-expected-type']; +} + describe('IndexBuilder', () => { let testSearchEngine: SearchEngine; let testIndexBuilder: IndexBuilder; - let testCollator: DocumentCollator; - let testDecorator: DocumentDecorator; beforeEach(() => { const logger = getVoidLogger(); @@ -48,18 +59,16 @@ describe('IndexBuilder', () => { logger, searchEngine: testSearchEngine, }); - testCollator = new TestDocumentCollator(); - testDecorator = new TestDocumentDecorator(); }); describe('addCollator', () => { it('adds a collator', async () => { jest.useFakeTimers(); + const testCollator = new TestDocumentCollator(); const collatorSpy = jest.spyOn(testCollator, 'execute'); // Add a collator. testIndexBuilder.addCollator({ - type: 'anything', defaultRefreshIntervalSeconds: 6, collator: testCollator, }); @@ -75,11 +84,12 @@ describe('IndexBuilder', () => { describe('addDecorator', () => { it('adds a decorator', async () => { jest.useFakeTimers(); + const testCollator = new TestDocumentCollator(); + const testDecorator = new TestDocumentDecorator(); const decoratorSpy = jest.spyOn(testDecorator, 'execute'); // Add a collator. testIndexBuilder.addCollator({ - type: 'anything', defaultRefreshIntervalSeconds: 6, collator: testCollator, }); @@ -100,7 +110,8 @@ describe('IndexBuilder', () => { it('adds a type-specific decorator', async () => { jest.useFakeTimers(); - const expectedType = 'an-expected-type'; + const testCollator = new TypedDocumentCollator(); + const testDecorator = new TypedDocumentDecorator(); const docFixture = { title: 'Test', text: 'Test text.', @@ -113,14 +124,12 @@ describe('IndexBuilder', () => { // Add a collator. testIndexBuilder.addCollator({ - type: expectedType, defaultRefreshIntervalSeconds: 6, collator: testCollator, }); // Add a decorator for the same type. testIndexBuilder.addDecorator({ - types: [expectedType], decorator: testDecorator, }); @@ -131,16 +140,17 @@ describe('IndexBuilder', () => { // wait for async decorator execution await Promise.resolve(); expect(decoratorSpy).toHaveBeenCalled(); - expect(decoratorSpy).toHaveBeenCalledWith(expectedType, [docFixture]); + expect(decoratorSpy).toHaveBeenCalledWith([docFixture]); }); it('adds a type-specific decorator that should not be called', async () => { - const expectedType = 'an-expected-type'; const docFixture = { title: 'Test', text: 'Test text.', location: '/test/location', }; + const testCollator = new TestDocumentCollator(); + const testDecorator = new DifferentlyTypedDocumentDecorator(); const collatorSpy = jest .spyOn(testCollator, 'execute') .mockImplementation(async () => [docFixture]); @@ -148,14 +158,12 @@ describe('IndexBuilder', () => { // Add a collator. testIndexBuilder.addCollator({ - type: expectedType, defaultRefreshIntervalSeconds: 6, collator: testCollator, }); // Add a decorator for a different type. testIndexBuilder.addDecorator({ - types: ['not-the-expected-type'], decorator: testDecorator, }); diff --git a/plugins/search-backend-node/src/IndexBuilder.ts b/plugins/search-backend-node/src/IndexBuilder.ts index acaa18fc03..046eaf7f34 100644 --- a/plugins/search-backend-node/src/IndexBuilder.ts +++ b/plugins/search-backend-node/src/IndexBuilder.ts @@ -55,28 +55,25 @@ export class IndexBuilder { * given refresh interval. */ addCollator({ - type, collator, defaultRefreshIntervalSeconds, }: RegisterCollatorParameters): void { this.logger.info( - `Added ${collator.constructor.name} collator for type ${type}`, + `Added ${collator.constructor.name} collator for type ${collator.type}`, ); - this.collators[type] = { + this.collators[collator.type] = { refreshInterval: defaultRefreshIntervalSeconds, collate: collator, }; } /** - * Makes the index builder aware of a decorator. If no types are provided, it - * will be applied to documents from all known collators, otherwise it will - * only be applied to documents of the given types. + * Makes the index builder aware of a decorator. If no types are provided on + * the decorator, it will be applied to documents from all known collators, + * otherwise it will only be applied to documents of the given types. */ - addDecorator({ - types = ['*'], - decorator, - }: RegisterDecoratorParameters): void { + addDecorator({ decorator }: RegisterDecoratorParameters): void { + const types = decorator.types || ['*']; this.logger.info( `Added decorator ${decorator.constructor.name} to types ${types.join( ', ', @@ -113,7 +110,7 @@ export class IndexBuilder { this.logger.debug( `Decorating ${type} documents via ${decorators[i].constructor.name}`, ); - documents = await decorators[i].execute(type, documents); + documents = await decorators[i].execute(documents); } if (!documents || documents.length === 0) { diff --git a/plugins/search-backend-node/src/types.ts b/plugins/search-backend-node/src/types.ts index 4abfa77fb1..b47c191a7c 100644 --- a/plugins/search-backend-node/src/types.ts +++ b/plugins/search-backend-node/src/types.ts @@ -26,11 +26,6 @@ import { * Parameters required to register a collator. */ export interface RegisterCollatorParameters { - /** - * The type of document to be indexed (used to name indices, to configure refresh loop, etc). - */ - type: string; - /** * The default interval (in seconds) that the provided collator will be called (can be overridden in config). */ @@ -50,12 +45,6 @@ export interface RegisterDecoratorParameters { * The decorator class responsible for appending or modifying documents of the given type(s). */ decorator: DocumentDecorator; - - /** - * (Optional) An array of document types that the given decorator should apply to. If none are provided, - * the decorator will be applied to all types. - */ - types?: string[]; } /** From 8cb45d747a3e149217161f935ff4987d701154db Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Tue, 1 Jun 2021 11:48:04 +0200 Subject: [PATCH 080/102] Make the QueryTranslator a more integral part of the SearchEngine API Signed-off-by: Eric Peterson --- .../src/engines/LunrSearchEngine.test.ts | 49 +++++++++++++++---- .../src/engines/LunrSearchEngine.ts | 8 ++- plugins/search-backend-node/src/types.ts | 13 ++++- 3 files changed, 58 insertions(+), 12 deletions(-) diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts index f597327dbf..969e579a53 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts @@ -18,6 +18,15 @@ import { getVoidLogger } from '@backstage/backend-common'; import { LunrSearchEngine } from './LunrSearchEngine'; import { SearchEngine } from '../types'; +/** + * Just used to test the default translator shipped with LunrSearchEngine. + */ +class LunrSearchEngineForTranslatorTests extends LunrSearchEngine { + getTranslator() { + return this.translator; + } +} + describe('LunrSearchEngine', () => { let testLunrSearchEngine: SearchEngine; @@ -27,16 +36,21 @@ describe('LunrSearchEngine', () => { describe('translator', () => { it('query translator invoked', async () => { - const translatorSpy = jest.spyOn(testLunrSearchEngine, 'translator'); + // Given: Set a translator spy on the search engine. + const translatorSpy = jest.fn().mockReturnValue({ + lunrQueryString: '', + documentTypes: [], + }); + testLunrSearchEngine.setTranslator(translatorSpy); - // Translate query and ensure the translator was invoked. - await testLunrSearchEngine.translator({ + // When: querying the search engine + testLunrSearchEngine.query({ term: 'testTerm', filters: {}, pageCursor: '', }); - expect(translatorSpy).toHaveBeenCalled(); + // Then: the translator is invoked with expected args. expect(translatorSpy).toHaveBeenCalledWith({ term: 'testTerm', filters: {}, @@ -45,39 +59,54 @@ describe('LunrSearchEngine', () => { }); it('should return translated query', async () => { - const mockedTranslatedQuery = await testLunrSearchEngine.translator({ + const inspectableSearchEngine = new LunrSearchEngineForTranslatorTests({ + logger: getVoidLogger(), + }); + const translatorUnderTest = inspectableSearchEngine.getTranslator(); + + const actualTranslatedQuery = translatorUnderTest({ term: 'testTerm', filters: {}, pageCursor: '', }); - expect(mockedTranslatedQuery).toMatchObject({ + expect(actualTranslatedQuery).toMatchObject({ documentTypes: ['*'], lunrQueryString: '+testTerm', }); }); it('should return translated query with 1 filter', async () => { - const mockedTranslatedQuery = await testLunrSearchEngine.translator({ + const inspectableSearchEngine = new LunrSearchEngineForTranslatorTests({ + logger: getVoidLogger(), + }); + const translatorUnderTest = inspectableSearchEngine.getTranslator(); + + const actualTranslatedQuery = translatorUnderTest({ term: 'testTerm', filters: { kind: 'testKind' }, pageCursor: '', }); - expect(mockedTranslatedQuery).toMatchObject({ + expect(actualTranslatedQuery).toMatchObject({ documentTypes: ['*'], lunrQueryString: '+testTerm +kind:testKind', }); }); it('should return translated query with multiple filters', async () => { - const mockedTranslatedQuery = await testLunrSearchEngine.translator({ + const inspectableSearchEngine = new LunrSearchEngineForTranslatorTests({ + logger: getVoidLogger(), + }); + const translatorUnderTest = inspectableSearchEngine.getTranslator(); + + const actualTranslatedQuery = translatorUnderTest({ term: 'testTerm', filters: { kind: 'testKind', namespace: 'testNameSpace' }, pageCursor: '', }); - expect(mockedTranslatedQuery).toMatchObject({ + expect(actualTranslatedQuery).toMatchObject({ documentTypes: ['*'], lunrQueryString: '+testTerm +kind:testKind +namespace:testNameSpace', }); diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts index d6afdc21ae..be51738920 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts @@ -33,6 +33,8 @@ type LunrResultEnvelope = { type: string; }; +type LunrQueryTranslator = (query: SearchQuery) => ConcreteLunrQuery; + export class LunrSearchEngine implements SearchEngine { protected lunrIndices: Record = {}; protected docStore: Record; @@ -43,7 +45,7 @@ export class LunrSearchEngine implements SearchEngine { this.docStore = {}; } - translator: QueryTranslator = ({ + protected translator: QueryTranslator = ({ term, filters, types, @@ -82,6 +84,10 @@ export class LunrSearchEngine implements SearchEngine { }; }; + setTranslator(translator: LunrQueryTranslator) { + this.translator = translator; + } + index(type: string, documents: IndexableDocument[]): void { const lunrBuilder = new lunr.Builder(); // Make this lunr index aware of all relevant fields. diff --git a/plugins/search-backend-node/src/types.ts b/plugins/search-backend-node/src/types.ts index b47c191a7c..e7643ee5aa 100644 --- a/plugins/search-backend-node/src/types.ts +++ b/plugins/search-backend-node/src/types.ts @@ -59,7 +59,18 @@ export type QueryTranslator = (query: SearchQuery) => unknown; * concrete, search engine-specific queries. */ export interface SearchEngine { - translator: QueryTranslator; + /** + * Override the default translator provided by the SearchEngine. + */ + setTranslator(translator: QueryTranslator): void; + + /** + * Add the given documents to the SearchEngine index of the given type. + */ index(type: string, documents: IndexableDocument[]): void; + + /** + * Perform a search query against the SearchEngine. + */ query(query: SearchQuery): Promise; } From db1c8f93b31150334af7591a2994cdb4d2a13401 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Tue, 1 Jun 2021 12:02:49 +0200 Subject: [PATCH 081/102] Add changesets. Signed-off-by: Eric Peterson --- .changeset/catalog-search-item.md | 5 +++++ .changeset/search-cross-the-goal.md | 9 +++++++++ 2 files changed, 14 insertions(+) create mode 100644 .changeset/catalog-search-item.md create mode 100644 .changeset/search-cross-the-goal.md diff --git a/.changeset/catalog-search-item.md b/.changeset/catalog-search-item.md new file mode 100644 index 0000000000..c1a12ec161 --- /dev/null +++ b/.changeset/catalog-search-item.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +A `` component is now available for use in custom Search Experiences. diff --git a/.changeset/search-cross-the-goal.md b/.changeset/search-cross-the-goal.md new file mode 100644 index 0000000000..580b28ac23 --- /dev/null +++ b/.changeset/search-cross-the-goal.md @@ -0,0 +1,9 @@ +--- +'@backstage/search-common': patch +'@backstage/plugin-search-backend-node': patch +'@backstage/plugin-search': patch +--- + +The ` set of components exported by the Search Plugin are now updated to use the Search Backend API. These will be made available as the default non-"next" versions in a follow-up release. + +The interfaces for decorators and collators in the Search Backend have also seen minor, breaking revisions ahead of a general release. If you happen to be building on top of these interfaces, check and update your implementations accordingly. The APIs will be considered more stable in a follow-up release. From d0d2bf79c8253630d5fed8327cb208e084afb636 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Tue, 1 Jun 2021 12:13:36 +0200 Subject: [PATCH 082/102] API Report and document types. Signed-off-by: Eric Peterson --- packages/search-common/api-report.md | 4 +++- packages/search-common/src/types.ts | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/search-common/api-report.md b/packages/search-common/api-report.md index 35a868849d..a3de27b5a9 100644 --- a/packages/search-common/api-report.md +++ b/packages/search-common/api-report.md @@ -10,12 +10,14 @@ import { JsonObject } from '@backstage/config'; export interface DocumentCollator { // (undocumented) execute(): Promise; + readonly type: string; } // @public export interface DocumentDecorator { // (undocumented) - execute(type: string, documents: IndexableDocument[]): Promise; + execute(documents: IndexableDocument[]): Promise; + readonly types?: string[]; } // @public diff --git a/packages/search-common/src/types.ts b/packages/search-common/src/types.ts index 792b9f3a2a..2e7ca97edc 100644 --- a/packages/search-common/src/types.ts +++ b/packages/search-common/src/types.ts @@ -58,6 +58,10 @@ export interface IndexableDocument { * search. */ export interface DocumentCollator { + /** + * The type or name of the document set returned by this collator. Used as an + * index name by Search Engines. + */ readonly type: string; execute(): Promise; } @@ -67,6 +71,11 @@ export interface DocumentCollator { * additional metadata. */ export interface DocumentDecorator { + /** + * An optional array of document/index types on which this decorator should + * be applied. If no types are provided, this decorator will be applied to + * all document/index types. + */ readonly types?: string[]; execute(documents: IndexableDocument[]): Promise; } From 311ca9f517c795041afc8b18132dc86c61cb4a57 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Wed, 2 Jun 2021 08:27:48 +0200 Subject: [PATCH 083/102] fix: change copyright year to 2021 Signed-off-by: Camila Belo Signed-off-by: Eric Peterson --- .../app/cypress/integration/components/search/SearchPage.js | 2 +- plugins/search/src/apis.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app/cypress/integration/components/search/SearchPage.js b/packages/app/cypress/integration/components/search/SearchPage.js index f97c26ee90..4e13fc8a0f 100644 --- a/packages/app/cypress/integration/components/search/SearchPage.js +++ b/packages/app/cypress/integration/components/search/SearchPage.js @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * 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. diff --git a/plugins/search/src/apis.test.ts b/plugins/search/src/apis.test.ts index 33714bc688..32ec0f0d0e 100644 --- a/plugins/search/src/apis.test.ts +++ b/plugins/search/src/apis.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * 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. From 9f31301bc8c4e6dfb14469126271ea2c02f456fd Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Wed, 2 Jun 2021 08:35:47 +0200 Subject: [PATCH 084/102] fix: reset Lunr refresh interval Signed-off-by: Camila Belo Signed-off-by: Eric Peterson --- packages/backend/src/plugins/search.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts index cfd3bb03eb..6688b9c158 100644 --- a/packages/backend/src/plugins/search.ts +++ b/packages/backend/src/plugins/search.ts @@ -30,7 +30,7 @@ export default async function createPlugin({ const indexBuilder = new IndexBuilder({ logger, searchEngine }); indexBuilder.addCollator({ - defaultRefreshIntervalSeconds: 10, + defaultRefreshIntervalSeconds: 600, collator: new DefaultCatalogCollator({ discovery }), }); From fb6301e3c47b6ff1b7e4ef39b0cd4ea42a932812 Mon Sep 17 00:00:00 2001 From: Nikhil Unni Date: Thu, 3 Jun 2021 13:33:55 -0400 Subject: [PATCH 085/102] Update Cortex Marketplace Card Slight rename, and update the `authorUrl` to a reachable URL. Sorry for the small update -- last one I promise! Signed-off-by: Nikhil Unni --- microsite/data/plugins/cortex.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/microsite/data/plugins/cortex.yaml b/microsite/data/plugins/cortex.yaml index 1ebf8ba567..1f1c57adfd 100644 --- a/microsite/data/plugins/cortex.yaml +++ b/microsite/data/plugins/cortex.yaml @@ -1,7 +1,7 @@ --- -title: Service Quality Scorecards +title: Cortex Service Quality Scorecards author: Cortex -authorUrl: https://github.com/cortexapps +authorUrl: https://www.getcortexapp.com category: Monitoring description: Grade the quality of your Backstage services using Scorecards. Automate production readiness, migrations, security audits, and more with CQL (Cortex Query Language). documentation: https://github.com/cortexapps/backstage-plugin From 7e76dddadee73c24f277fd78a2cd567219639bb1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Jun 2021 04:27:04 +0000 Subject: [PATCH 086/102] chore(deps-dev): bump @spotify/prettier-config from 9.0.0 to 10.0.0 Bumps [@spotify/prettier-config](https://github.com/spotify/web-scripts) from 9.0.0 to 10.0.0. - [Release notes](https://github.com/spotify/web-scripts/releases) - [Changelog](https://github.com/spotify/web-scripts/blob/master/CHANGELOG.md) - [Commits](https://github.com/spotify/web-scripts/compare/v9.0.0...v10.0.0) --- updated-dependencies: - dependency-name: "@spotify/prettier-config" dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 1ffeb490a3..4998c43ebb 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "devDependencies": { "@changesets/cli": "^2.14.0", "@octokit/openapi-types": "^2.2.0", - "@spotify/prettier-config": "^9.0.0", + "@spotify/prettier-config": "^10.0.0", "command-exists": "^1.2.9", "concurrently": "^6.0.0", "eslint-plugin-notice": "^0.9.10", diff --git a/yarn.lock b/yarn.lock index 25202523c2..8c10a80e9a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1702,7 +1702,7 @@ to-fast-properties "^2.0.0" "@backstage/catalog-model@^0.7.4": - version "0.8.0" + version "0.8.1" dependencies: "@backstage/config" "^0.1.5" "@backstage/errors" "^0.1.1" @@ -1716,7 +1716,7 @@ yup "^0.29.3" "@backstage/catalog-model@^0.7.9": - version "0.8.0" + version "0.8.1" dependencies: "@backstage/config" "^0.1.5" "@backstage/errors" "^0.1.1" @@ -1746,16 +1746,16 @@ react-use "^17.2.4" "@backstage/plugin-catalog@^0.5.1": - version "0.6.0" + version "0.6.1" dependencies: "@backstage/catalog-client" "^0.3.12" - "@backstage/catalog-model" "^0.8.0" - "@backstage/core" "^0.7.11" + "@backstage/catalog-model" "^0.8.1" + "@backstage/core" "^0.7.12" "@backstage/errors" "^0.1.1" - "@backstage/integration" "^0.5.4" + "@backstage/integration" "^0.5.5" "@backstage/integration-react" "^0.1.2" - "@backstage/plugin-catalog-react" "^0.2.0" - "@backstage/theme" "^0.2.7" + "@backstage/plugin-catalog-react" "^0.2.1" + "@backstage/theme" "^0.2.8" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" "@material-ui/lab" "4.0.0-alpha.45" @@ -4599,10 +4599,10 @@ resolved "https://registry.npmjs.org/@spotify/eslint-config-typescript/-/eslint-config-typescript-9.0.0.tgz#be68cfaf212599f0bfeb6536c7c58ec05d2b6fba" integrity sha512-ZsXTwMA68ZCz943U4N8XwprdWcc7ErOO/IW8PewLK5lycCZtLnmRkOvAbae7O5qNJPD8b/l0iUMTLaZuwjXWwg== -"@spotify/prettier-config@^9.0.0": - version "9.0.0" - resolved "https://registry.npmjs.org/@spotify/prettier-config/-/prettier-config-9.0.0.tgz#7b562d56573c6fc0094446fbc92b22bc318945dc" - integrity sha512-In1q0tIiqTYKAGe3KOHDcFDdZRFISyQeSeipeTHGfki23ebHRZcjxvqj5SSdBkw65D4VpSREMi0s9i5iJiMcTw== +"@spotify/prettier-config@^10.0.0": + version "10.0.0" + resolved "https://registry.npmjs.org/@spotify/prettier-config/-/prettier-config-10.0.0.tgz#fa076d98d2e7e6c53dd3d86a696307a7010bd056" + integrity sha512-VYOdo8P7lIScAkl02nB9KpUAuOYMManryBIBuKJkAw5D3aVtLobfmdIKvdV6MqEmGMEQPbn7w/UpnjJYhUH+IA== "@storybook/addon-actions@^6.1.11": version "6.1.17" From eeedd2b0c68a6272c10039b7b4b220044d1e8553 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Jun 2021 04:31:07 +0000 Subject: [PATCH 087/102] chore(deps): bump @rollup/plugin-node-resolve from 11.2.0 to 11.2.1 Bumps [@rollup/plugin-node-resolve](https://github.com/rollup/plugins/tree/HEAD/packages/node-resolve) from 11.2.0 to 11.2.1. - [Release notes](https://github.com/rollup/plugins/releases) - [Changelog](https://github.com/rollup/plugins/blob/master/packages/node-resolve/CHANGELOG.md) - [Commits](https://github.com/rollup/plugins/commits/node-resolve-v11.2.1/packages/node-resolve) --- updated-dependencies: - dependency-name: "@rollup/plugin-node-resolve" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- yarn.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/yarn.lock b/yarn.lock index 25202523c2..fa6eee4c3f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1702,7 +1702,7 @@ to-fast-properties "^2.0.0" "@backstage/catalog-model@^0.7.4": - version "0.8.0" + version "0.8.1" dependencies: "@backstage/config" "^0.1.5" "@backstage/errors" "^0.1.1" @@ -1716,7 +1716,7 @@ yup "^0.29.3" "@backstage/catalog-model@^0.7.9": - version "0.8.0" + version "0.8.1" dependencies: "@backstage/config" "^0.1.5" "@backstage/errors" "^0.1.1" @@ -1746,16 +1746,16 @@ react-use "^17.2.4" "@backstage/plugin-catalog@^0.5.1": - version "0.6.0" + version "0.6.1" dependencies: "@backstage/catalog-client" "^0.3.12" - "@backstage/catalog-model" "^0.8.0" - "@backstage/core" "^0.7.11" + "@backstage/catalog-model" "^0.8.1" + "@backstage/core" "^0.7.12" "@backstage/errors" "^0.1.1" - "@backstage/integration" "^0.5.4" + "@backstage/integration" "^0.5.5" "@backstage/integration-react" "^0.1.2" - "@backstage/plugin-catalog-react" "^0.2.0" - "@backstage/theme" "^0.2.7" + "@backstage/plugin-catalog-react" "^0.2.1" + "@backstage/theme" "^0.2.8" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" "@material-ui/lab" "4.0.0-alpha.45" @@ -4466,9 +4466,9 @@ "@rollup/pluginutils" "^3.0.8" "@rollup/plugin-node-resolve@^11.2.0": - version "11.2.0" - resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.0.tgz#a5ab88c35bb7622d115f44984dee305112b6f714" - integrity sha512-qHjNIKYt5pCcn+5RUBQxK8krhRvf1HnyVgUCcFFcweDS7fhkOLZeYh0mhHK6Ery8/bb9tvN/ubPzmfF0qjDCTA== + version "11.2.1" + resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz#82aa59397a29cd4e13248b106e6a4a1880362a60" + integrity sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg== dependencies: "@rollup/pluginutils" "^3.1.0" "@types/resolve" "1.17.1" From 9d906c7a125c0503bc475278b242bbca9a97e68f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 4 Jun 2021 11:33:42 +0200 Subject: [PATCH 088/102] cost-insights: move canvas package to dev deps Signed-off-by: Patrik Oldsberg --- .changeset/cyan-suns-chew.md | 5 +++++ plugins/cost-insights/package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/cyan-suns-chew.md diff --git a/.changeset/cyan-suns-chew.md b/.changeset/cyan-suns-chew.md new file mode 100644 index 0000000000..95d51c23fe --- /dev/null +++ b/.changeset/cyan-suns-chew.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-cost-insights': patch +--- + +Move `canvas` package to `devDependencies`. diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index 8901ea96eb..bb7e9b6984 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -39,7 +39,6 @@ "@material-ui/styles": "^4.9.6", "@types/react": "^16.9", "@types/recharts": "^1.8.14", - "canvas": "^2.6.1", "classnames": "^2.2.6", "dayjs": "^1.9.4", "history": "^5.0.0", @@ -67,6 +66,7 @@ "@types/recharts": "^1.8.14", "@types/regression": "^2.0.0", "@types/yup": "^0.29.8", + "canvas": "^2.6.1", "cross-fetch": "^3.0.6", "msw": "^0.21.2" }, From 0cf2ddbbff494b2791a6f9d033759840275ef2ed Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Fri, 4 Jun 2021 11:37:41 +0200 Subject: [PATCH 089/102] docs: Link sqlite to postgres switch tutorial in create app page Signed-off-by: Himanshu Mishra --- docs/getting-started/create-an-app.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/getting-started/create-an-app.md b/docs/getting-started/create-an-app.md index fefb20b095..2e568664d9 100644 --- a/docs/getting-started/create-an-app.md +++ b/docs/getting-started/create-an-app.md @@ -23,7 +23,8 @@ Backstage provides a utility for creating new apps. It guides you through the initial setup of selecting the name of the app and a database for the backend. The database options are either SQLite or PostgreSQL, where the latter requires you to set up a separate database instance. If in doubt, choose SQLite, but -don't worry about the choice, it's easy to change later! +don't worry about the choice, it's easy to change later! Here is a +[tutorial](../tutorials/switching-sqlite-postgres.md) for it. The easiest way to run the create app package is with `npx`: From e71458c208964509b1351b4bbc895522c88db224 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 4 Jun 2021 12:30:56 +0200 Subject: [PATCH 090/102] Standard @testing-library/react-hooks version Signed-off-by: Eric Peterson --- package.json | 1 - plugins/search/package.json | 2 +- yarn.lock | 62 +++++++++++++------------------------ 3 files changed, 22 insertions(+), 43 deletions(-) diff --git a/package.json b/package.json index 7169f0d8e8..1ffeb490a3 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ ] }, "resolutions": { - "**/@types/react": "^16.9.0", "**/@roadiehq/**/@backstage/core": "*", "**/@roadiehq/**/@backstage/plugin-catalog": "*", "**/@roadiehq/**/@backstage/catalog-model": "*", diff --git a/plugins/search/package.json b/plugins/search/package.json index f2c10b89c4..a74091e497 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -52,7 +52,7 @@ "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/react-hooks": "^7.0.0", + "@testing-library/react-hooks": "^3.4.2", "@testing-library/user-event": "^13.1.8", "@types/react": "^16.9", "@types/jest": "^26.0.7", diff --git a/yarn.lock b/yarn.lock index fa077b9d5f..799631f8bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1702,7 +1702,7 @@ to-fast-properties "^2.0.0" "@backstage/catalog-model@^0.7.4": - version "0.8.0" + version "0.8.1" dependencies: "@backstage/config" "^0.1.5" "@backstage/errors" "^0.1.1" @@ -1716,7 +1716,7 @@ yup "^0.29.3" "@backstage/catalog-model@^0.7.9": - version "0.8.0" + version "0.8.1" dependencies: "@backstage/config" "^0.1.5" "@backstage/errors" "^0.1.1" @@ -1746,16 +1746,16 @@ react-use "^17.2.4" "@backstage/plugin-catalog@^0.5.1": - version "0.6.0" + version "0.6.1" dependencies: "@backstage/catalog-client" "^0.3.12" - "@backstage/catalog-model" "^0.8.0" - "@backstage/core" "^0.7.11" + "@backstage/catalog-model" "^0.8.1" + "@backstage/core" "^0.7.12" "@backstage/errors" "^0.1.1" - "@backstage/integration" "^0.5.4" + "@backstage/integration" "^0.5.5" "@backstage/integration-react" "^0.1.2" - "@backstage/plugin-catalog-react" "^0.2.0" - "@backstage/theme" "^0.2.7" + "@backstage/plugin-catalog-react" "^0.2.1" + "@backstage/theme" "^0.2.8" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" "@material-ui/lab" "4.0.0-alpha.45" @@ -5700,17 +5700,6 @@ "@babel/runtime" "^7.5.4" "@types/testing-library__react-hooks" "^3.4.0" -"@testing-library/react-hooks@^7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-7.0.0.tgz#dd6d37a7e018f147a3b9153137f10e013be8472b" - integrity sha512-WFBGH8DWdIGGBHt6PBtQPe2v4Kbj9vQ1sQ9qLBTmwn1PNggngint4MTE/IiWCYhPbyTW3oc/7X62DObMn/AjQQ== - dependencies: - "@babel/runtime" "^7.12.5" - "@types/react" ">=16.9.0" - "@types/react-dom" ">=16.9.0" - "@types/react-test-renderer" ">=16.9.0" - react-error-boundary "^3.1.0" - "@testing-library/react@^11.2.5": version "11.2.6" resolved "https://registry.npmjs.org/@testing-library/react/-/react-11.2.6.tgz#586a23adc63615985d85be0c903f374dab19200b" @@ -6678,13 +6667,6 @@ "@types/webpack" "^4" "@types/webpack-dev-server" "*" -"@types/react-dom@>=16.9.0": - version "17.0.5" - resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.5.tgz#df44eed5b8d9e0b13bb0cd38e0ea6572a1231227" - integrity sha512-ikqukEhH4H9gr4iJCmQVNzTB307kROe3XFfHAOTxOXPOw7lAoEXnM5KWTkzeANGL5Ce6ABfiMl/zJBYNi7ObmQ== - dependencies: - "@types/react" "*" - "@types/react-dom@^16.9.8": version "16.9.8" resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423" @@ -6728,13 +6710,6 @@ dependencies: "@types/react" "*" -"@types/react-test-renderer@>=16.9.0": - version "17.0.1" - resolved "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-17.0.1.tgz#3120f7d1c157fba9df0118dae20cb0297ee0e06b" - integrity sha512-3Fi2O6Zzq/f3QR9dRnlnHso9bMl7weKCviFmfF6B4LS1Uat6Hkm15k0ZAQuDz+UBq6B3+g+NM6IT2nr5QgPzCw== - dependencies: - "@types/react" "*" - "@types/react-text-truncate@^0.14.0": version "0.14.0" resolved "https://registry.npmjs.org/@types/react-text-truncate/-/react-text-truncate-0.14.0.tgz#588bbabbc7f2a13815e805f3a48942db73fe65fe" @@ -6756,7 +6731,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@16.4.6", "@types/react@>=16.9.0", "@types/react@^16.9", "@types/react@^16.9.0": +"@types/react@*", "@types/react@^16.9": version "16.14.8" resolved "https://registry.npmjs.org/@types/react/-/react-16.14.8.tgz#4aee3ab004cb98451917c9b7ada3c7d7e52db3fe" integrity sha512-QN0/Qhmx+l4moe7WJuTxNiTsjBwlBGHqKGvInSQCBdo7Qio0VtOqwsC0Wq7q3PbJlB0cR4Y4CVo1OOe6BOsOmA== @@ -6765,6 +6740,13 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/react@16.4.6": + version "16.4.6" + resolved "https://artifactory.spotify.net/artifactory/api/npm/virtual-npm/@types/react/-/react-16.4.6.tgz#5024957c6bcef4f02823accf5974faba2e54fada" + integrity sha1-UCSVfGvO9PAoI6zPWXT6ui5U+to= + dependencies: + csstype "^2.2.0" + "@types/reactcss@*": version "1.2.3" resolved "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.3.tgz#af28ae11bbb277978b99d04d1eedfd068ca71834" @@ -11067,6 +11049,11 @@ cssstyle@^2.2.0: dependencies: cssom "~0.3.6" +csstype@^2.2.0: + version "2.6.17" + resolved "https://artifactory.spotify.net/artifactory/api/npm/virtual-npm/csstype/-/csstype-2.6.17.tgz#4cf30eb87e1d1a005d8b6510f95292413f6a1c0e" + integrity sha1-TPMOuH4dGgBdi2UQ+VKSQT9qHA4= + csstype@^2.5.2, csstype@^2.5.7, csstype@^2.6.7: version "2.6.9" resolved "https://registry.npmjs.org/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098" @@ -22206,13 +22193,6 @@ react-draggable@^4.0.3: classnames "^2.2.5" prop-types "^15.6.0" -react-error-boundary@^3.1.0: - version "3.1.3" - resolved "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.3.tgz#276bfa05de8ac17b863587c9e0647522c25e2a0b" - integrity sha512-A+F9HHy9fvt9t8SNDlonq01prnU8AmkjvGKV4kk8seB9kU3xMEO8J/PQlLVmoOIDODl5U2kufSBs4vrWIqhsAA== - dependencies: - "@babel/runtime" "^7.12.5" - react-error-overlay@^6.0.7, react-error-overlay@^6.0.9: version "6.0.9" resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a" From 7f7443308a0715236d8a47168f3a435be197f037 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Jun 2021 04:18:44 +0000 Subject: [PATCH 091/102] chore(deps): bump @typescript-eslint/eslint-plugin from 4.15.2 to 4.26.0 Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.15.2 to 4.26.0. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.26.0/packages/eslint-plugin) --- updated-dependencies: - dependency-name: "@typescript-eslint/eslint-plugin" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .changeset/old-crabs-jump.md | 5 ++ packages/cli/package.json | 2 +- yarn.lock | 119 ++++++++++++++++++----------------- 3 files changed, 69 insertions(+), 57 deletions(-) create mode 100644 .changeset/old-crabs-jump.md diff --git a/.changeset/old-crabs-jump.md b/.changeset/old-crabs-jump.md new file mode 100644 index 0000000000..9441f3aeb7 --- /dev/null +++ b/.changeset/old-crabs-jump.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Updated dependencies diff --git a/packages/cli/package.json b/packages/cli/package.json index e7126e5fe4..e0d534dfa0 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -52,7 +52,7 @@ "@types/start-server-webpack-plugin": "^2.2.0", "@types/webpack-env": "^1.15.2", "@types/webpack-node-externals": "^2.5.0", - "@typescript-eslint/eslint-plugin": "^v4.15.2", + "@typescript-eslint/eslint-plugin": "^v4.26.0", "@typescript-eslint/parser": "^v4.14.0", "@yarnpkg/lockfile": "^1.1.0", "babel-plugin-dynamic-import-node": "^2.3.3", diff --git a/yarn.lock b/yarn.lock index 25202523c2..d3b8e0a46d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6297,7 +6297,7 @@ dependencies: "@types/json-schema" "*" -"@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": +"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.7": version "7.0.7" resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== @@ -7141,31 +7141,31 @@ resolved "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.2.tgz#808c9fa7e4517274ed555fa158f2de4b4f468e71" integrity sha512-HrCIVMLjE1MOozVoD86622S7aunluLb2PJdPfb3nYiEtohm8mIB/vyv0Fd37AdeMFrTUQXEunw78YloMA3Qilg== -"@typescript-eslint/eslint-plugin@^v4.15.2": - version "4.15.2" - resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.15.2.tgz#981b26b4076c62a5a55873fbef3fe98f83360c61" - integrity sha512-uiQQeu9tWl3f1+oK0yoAv9lt/KXO24iafxgQTkIYO/kitruILGx3uH+QtIAHqxFV+yIsdnJH+alel9KuE3J15Q== +"@typescript-eslint/eslint-plugin@^v4.26.0": + version "4.26.0" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.26.0.tgz#12bbd6ebd5e7fabd32e48e1e60efa1f3554a3242" + integrity sha512-yA7IWp+5Qqf+TLbd8b35ySFOFzUfL7i+4If50EqvjT6w35X8Lv0eBHb6rATeWmucks37w+zV+tWnOXI9JlG6Eg== dependencies: - "@typescript-eslint/experimental-utils" "4.15.2" - "@typescript-eslint/scope-manager" "4.15.2" - debug "^4.1.1" + "@typescript-eslint/experimental-utils" "4.26.0" + "@typescript-eslint/scope-manager" "4.26.0" + debug "^4.3.1" functional-red-black-tree "^1.0.1" - lodash "^4.17.15" - regexpp "^3.0.0" - semver "^7.3.2" - tsutils "^3.17.1" + lodash "^4.17.21" + regexpp "^3.1.0" + semver "^7.3.5" + tsutils "^3.21.0" -"@typescript-eslint/experimental-utils@4.15.2", "@typescript-eslint/experimental-utils@^4.0.1": - version "4.15.2" - resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.15.2.tgz#5efd12355bd5b535e1831282e6cf465b9a71cf36" - integrity sha512-Fxoshw8+R5X3/Vmqwsjc8nRO/7iTysRtDqx6rlfLZ7HbT8TZhPeQqbPjTyk2RheH3L8afumecTQnUc9EeXxohQ== +"@typescript-eslint/experimental-utils@4.26.0", "@typescript-eslint/experimental-utils@^4.0.1": + version "4.26.0" + resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.26.0.tgz#ba7848b3f088659cdf71bce22454795fc55be99a" + integrity sha512-TH2FO2rdDm7AWfAVRB5RSlbUhWxGVuxPNzGT7W65zVfl8H/WeXTk1e69IrcEVsBslrQSTDKQSaJD89hwKrhdkw== dependencies: - "@types/json-schema" "^7.0.3" - "@typescript-eslint/scope-manager" "4.15.2" - "@typescript-eslint/types" "4.15.2" - "@typescript-eslint/typescript-estree" "4.15.2" - eslint-scope "^5.0.0" - eslint-utils "^2.0.0" + "@types/json-schema" "^7.0.7" + "@typescript-eslint/scope-manager" "4.26.0" + "@typescript-eslint/types" "4.26.0" + "@typescript-eslint/typescript-estree" "4.26.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" "@typescript-eslint/parser@^v4.14.0": version "4.14.0" @@ -7185,23 +7185,23 @@ "@typescript-eslint/types" "4.14.0" "@typescript-eslint/visitor-keys" "4.14.0" -"@typescript-eslint/scope-manager@4.15.2": - version "4.15.2" - resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.15.2.tgz#5725bda656995960ae1d004bfd1cd70320f37f4f" - integrity sha512-Zm0tf/MSKuX6aeJmuXexgdVyxT9/oJJhaCkijv0DvJVT3ui4zY6XYd6iwIo/8GEZGy43cd7w1rFMiCLHbRzAPQ== +"@typescript-eslint/scope-manager@4.26.0": + version "4.26.0" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.26.0.tgz#60d1a71df162404e954b9d1c6343ff3bee496194" + integrity sha512-G6xB6mMo4xVxwMt5lEsNTz3x4qGDt0NSGmTBNBPJxNsrTXJSm21c6raeYroS2OwQsOyIXqKZv266L/Gln1BWqg== dependencies: - "@typescript-eslint/types" "4.15.2" - "@typescript-eslint/visitor-keys" "4.15.2" + "@typescript-eslint/types" "4.26.0" + "@typescript-eslint/visitor-keys" "4.26.0" "@typescript-eslint/types@4.14.0": version "4.14.0" resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.14.0.tgz#d8a8202d9b58831d6fd9cee2ba12f8a5a5dd44b6" integrity sha512-VsQE4VvpldHrTFuVPY1ZnHn/Txw6cZGjL48e+iBxTi2ksa9DmebKjAeFmTVAYoSkTk7gjA7UqJ7pIsyifTsI4A== -"@typescript-eslint/types@4.15.2": - version "4.15.2" - resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.15.2.tgz#04acf3a2dc8001a88985291744241e732ef22c60" - integrity sha512-r7lW7HFkAarfUylJ2tKndyO9njwSyoy6cpfDKWPX6/ctZA+QyaYscAHXVAfJqtnY6aaTwDYrOhp+ginlbc7HfQ== +"@typescript-eslint/types@4.26.0": + version "4.26.0" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.26.0.tgz#7c6732c0414f0a69595f4f846ebe12616243d546" + integrity sha512-rADNgXl1kS/EKnDr3G+m7fB9yeJNnR9kF7xMiXL6mSIWpr3Wg5MhxyfEXy/IlYthsqwBqHOr22boFbf/u6O88A== "@typescript-eslint/typescript-estree@4.14.0": version "4.14.0" @@ -7217,18 +7217,18 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/typescript-estree@4.15.2": - version "4.15.2" - resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.15.2.tgz#c2f7a1e94f3428d229d5ecff3ead6581ee9b62fa" - integrity sha512-cGR8C2g5SPtHTQvAymEODeqx90pJHadWsgTtx6GbnTWKqsg7yp6Eaya9nFzUd4KrKhxdYTTFBiYeTPQaz/l8bw== +"@typescript-eslint/typescript-estree@4.26.0": + version "4.26.0" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.26.0.tgz#aea17a40e62dc31c63d5b1bbe9a75783f2ce7109" + integrity sha512-GHUgahPcm9GfBuy3TzdsizCcPjKOAauG9xkz9TR8kOdssz2Iz9jRCSQm6+aVFa23d5NcSpo1GdHGSQKe0tlcbg== dependencies: - "@typescript-eslint/types" "4.15.2" - "@typescript-eslint/visitor-keys" "4.15.2" - debug "^4.1.1" - globby "^11.0.1" + "@typescript-eslint/types" "4.26.0" + "@typescript-eslint/visitor-keys" "4.26.0" + debug "^4.3.1" + globby "^11.0.3" is-glob "^4.0.1" - semver "^7.3.2" - tsutils "^3.17.1" + semver "^7.3.5" + tsutils "^3.21.0" "@typescript-eslint/visitor-keys@4.14.0": version "4.14.0" @@ -7238,12 +7238,12 @@ "@typescript-eslint/types" "4.14.0" eslint-visitor-keys "^2.0.0" -"@typescript-eslint/visitor-keys@4.15.2": - version "4.15.2" - resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.15.2.tgz#3d1c7979ce75bf6acf9691109bd0d6b5706192b9" - integrity sha512-TME1VgSb7wTwgENN5KVj4Nqg25hP8DisXxNBojM4Nn31rYaNDIocNm5cmjOFfh42n7NVERxWrDFoETO/76ePyg== +"@typescript-eslint/visitor-keys@4.26.0": + version "4.26.0" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.26.0.tgz#26d2583169222815be4dcd1da4fe5459bc3bcc23" + integrity sha512-cw4j8lH38V1ycGBbF+aFiLUls9Z0Bw8QschP3mkth50BbWzgFS33ISIgBzUMuQ2IdahoEv/rXstr8Zhlz4B1Zg== dependencies: - "@typescript-eslint/types" "4.15.2" + "@typescript-eslint/types" "4.26.0" eslint-visitor-keys "^2.0.0" "@webassemblyjs/ast@1.9.0": @@ -12692,7 +12692,7 @@ eslint-scope@^4.0.3: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-scope@^5.0.0, eslint-scope@^5.1.1: +eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== @@ -12700,13 +12700,20 @@ eslint-scope@^5.0.0, eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-utils@^2.0.0, eslint-utils@^2.1.0: +eslint-utils@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== dependencies: eslint-visitor-keys "^1.1.0" +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" @@ -14307,7 +14314,7 @@ globby@11.0.1: merge2 "^1.3.0" slash "^3.0.0" -globby@11.0.3: +globby@11.0.3, globby@^11.0.3: version "11.0.3" resolved "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb" integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg== @@ -22956,7 +22963,7 @@ regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.0: define-properties "^1.1.3" es-abstract "^1.17.0-next.1" -regexpp@^3.0.0, regexpp@^3.1.0: +regexpp@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== @@ -23686,7 +23693,7 @@ semver@7.0.0: resolved "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@~7.3.0: +semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@~7.3.0: version "7.3.5" resolved "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== @@ -25809,10 +25816,10 @@ tslib@~2.1.0: resolved "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== -tsutils@^3.17.1: - version "3.17.1" - resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" - integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== +tsutils@^3.17.1, tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== dependencies: tslib "^1.8.1" From abd53e76a0a3337f66bfda95c3f52bc7e846fc90 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 4 Jun 2021 14:34:32 +0200 Subject: [PATCH 092/102] Only set default filter values on initial mount. Signed-off-by: Eric Peterson --- .../src/components/SearchFilterNext/SearchFilterNext.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx b/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx index 239707e5ab..7beef58bc9 100644 --- a/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx +++ b/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx @@ -62,7 +62,8 @@ const CheckboxFilter = ({ [name]: defaultValue, })); } - }, [name, defaultValue, setFilters]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const handleChange = (e: ChangeEvent) => { const { @@ -121,7 +122,8 @@ const SelectFilter = ({ [name]: defaultValue, })); } - }, [name, defaultValue, setFilters]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const handleChange = (e: ChangeEvent<{ value: unknown }>) => { const { From d97afd79a393cb461255251354e9a960f63c746a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 4 Jun 2021 15:40:24 +0200 Subject: [PATCH 093/102] fix private resolutions in yarn.lock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index f2de6ed848..6e416fa6e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6742,8 +6742,8 @@ "@types/react@16.4.6": version "16.4.6" - resolved "https://artifactory.spotify.net/artifactory/api/npm/virtual-npm/@types/react/-/react-16.4.6.tgz#5024957c6bcef4f02823accf5974faba2e54fada" - integrity sha1-UCSVfGvO9PAoI6zPWXT6ui5U+to= + resolved "https://registry.npmjs.org/@types/react/-/react-16.4.6.tgz#5024957c6bcef4f02823accf5974faba2e54fada" + integrity sha512-9LDZdhsuKSc+DjY65SjBkA958oBWcTWSVWAd2cD9XqKBjhGw1KzAkRhWRw2eIsXvaIE/TOTjjKMFVC+JA1iU4g== dependencies: csstype "^2.2.0" @@ -11051,8 +11051,8 @@ cssstyle@^2.2.0: csstype@^2.2.0: version "2.6.17" - resolved "https://artifactory.spotify.net/artifactory/api/npm/virtual-npm/csstype/-/csstype-2.6.17.tgz#4cf30eb87e1d1a005d8b6510f95292413f6a1c0e" - integrity sha1-TPMOuH4dGgBdi2UQ+VKSQT9qHA4= + resolved "https://registry.npmjs.org/csstype/-/csstype-2.6.17.tgz#4cf30eb87e1d1a005d8b6510f95292413f6a1c0e" + integrity sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A== csstype@^2.5.2, csstype@^2.5.7, csstype@^2.6.7: version "2.6.9" From af6acd24ba01ebe56575b33490b9895c6fc011e4 Mon Sep 17 00:00:00 2001 From: Juan Lulkin Date: Wed, 2 Jun 2021 11:55:08 +0200 Subject: [PATCH 094/102] Adds script to check wrong registries in all yarn.lock files Signed-off-by: Juan Lulkin --- microsite/yarn.lock | 8 +++--- package.json | 6 +++-- yarn.lock | 66 ++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 68 insertions(+), 12 deletions(-) diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 4033968a2e..9834ef2396 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -911,7 +911,7 @@ "@spotify/prettier-config@^10.0.0": version "10.0.0" - resolved "https://registry.yarnpkg.com/@spotify/prettier-config/-/prettier-config-10.0.0.tgz#fa076d98d2e7e6c53dd3d86a696307a7010bd056" + resolved "https://registry.npmjs.org/@spotify/prettier-config/-/prettier-config-10.0.0.tgz#fa076d98d2e7e6c53dd3d86a696307a7010bd056" integrity sha512-VYOdo8P7lIScAkl02nB9KpUAuOYMManryBIBuKJkAw5D3aVtLobfmdIKvdV6MqEmGMEQPbn7w/UpnjJYhUH+IA== "@types/cheerio@^0.22.8": @@ -1039,7 +1039,7 @@ argparse@^1.0.10, argparse@^1.0.7: argparse@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== arr-diff@^4.0.0: @@ -3864,7 +3864,7 @@ js-yaml@^3.13.1, js-yaml@^3.8.1: js-yaml@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" @@ -5199,7 +5199,7 @@ prepend-http@^2.0.0: prettier@^2.3.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.0.tgz#b6a5bf1284026ae640f17f7ff5658a7567fc0d18" + resolved "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz#b6a5bf1284026ae640f17f7ff5658a7567fc0d18" integrity sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w== prismjs@^1.22.0: diff --git a/package.json b/package.json index 4998c43ebb..a1e16aa020 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "lerna": "lerna", "storybook": "yarn workspace storybook start", "build-storybook": "yarn workspace storybook build-storybook", - "prepare": "husky install" + "prepare": "husky install", + "lock:check": "yarn-lock-check" }, "workspaces": { "packages": [ @@ -66,7 +67,8 @@ "lint-staged": "^10.1.0", "prettier": "^2.2.1", "recursive-readdir": "^2.2.2", - "shx": "^0.3.2" + "shx": "^0.3.2", + "yarn-lock-check": "^1.0.3" }, "prettier": "@spotify/prettier-config", "lint-staged": { diff --git a/yarn.lock b/yarn.lock index 6e416fa6e4..4ddbebd5ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6108,7 +6108,7 @@ resolved "https://registry.npmjs.org/@types/glob-base/-/glob-base-0.3.0.tgz#a581d688347e10e50dd7c17d6f2880a10354319d" integrity sha1-pYHWiDR+EOUN18F9byiAoQNUMZ0= -"@types/glob@*", "@types/glob@^7.1.1": +"@types/glob@*", "@types/glob@^7.1.1", "@types/glob@^7.1.3": version "7.1.3" resolved "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== @@ -6507,6 +6507,11 @@ resolved "https://registry.npmjs.org/@types/node/-/node-13.13.45.tgz#e6676bcca092bae5751d015f074a234d5a82eb63" integrity sha512-703YTEp8AwQeapI0PTXDOj+Bs/mtdV/k9VcTP7z/de+lx6XjFMKdB+JhKnK+6PZ5za7omgZ3V6qm/dNkMj/Zow== +"@types/node@^15.6.1": + version "15.12.0" + resolved "https://registry.npmjs.org/@types/node/-/node-15.12.0.tgz#6a459d261450a300e6865faeddb5af01c3389bb3" + integrity sha512-+aHJvoCsVhO2ZCuT4o5JtcPrCPyDE3+1nvbDprYes+pPkEsbjH7AGUCNtjMOXS0fqH14t+B7yLzaqSz92FPWyw== + "@types/normalize-package-data@^2.4.0": version "2.4.0" resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" @@ -9263,6 +9268,11 @@ buffers@~0.1.1: resolved "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" integrity sha1-skV5w77U1tOWru5tmorn9Ugqt7s= +builtin-modules@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= + builtin-modules@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484" @@ -9587,7 +9597,7 @@ chainsaw@~0.1.0: dependencies: traverse ">=0.3.0 <0.4" -chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: +chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -10159,7 +10169,7 @@ commander@2.3.0: resolved "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873" integrity sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM= -commander@^2.19.0, commander@^2.20.0, commander@^2.20.3, commander@^2.7.1, commander@~2.20.3: +commander@^2.12.1, commander@^2.19.0, commander@^2.20.0, commander@^2.20.3, commander@^2.7.1, commander@~2.20.3: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -15529,7 +15539,7 @@ inherits@2.0.3: resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@2.0.0: +ini@2.0.0, ini@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== @@ -23277,7 +23287,7 @@ resolve-url@^0.2.1: resolved "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.1.6, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.9.0: +resolve@^1.1.6, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.3.2, resolve@^1.9.0: version "1.20.0" resolved "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== @@ -25807,7 +25817,7 @@ tslib@2.0.0: resolved "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz#18d13fc2dce04051e20f074cc8387fd8089ce4f3" integrity sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g== -tslib@^1.10.0, tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: +tslib@^1.10.0, tslib@^1.11.1, tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -25827,6 +25837,32 @@ tslib@~2.1.0: resolved "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== +tslint@^6.1.3: + version "6.1.3" + resolved "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz#5c23b2eccc32487d5523bd3a470e9aa31789d904" + integrity sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg== + dependencies: + "@babel/code-frame" "^7.0.0" + builtin-modules "^1.1.1" + chalk "^2.3.0" + commander "^2.12.1" + diff "^4.0.1" + glob "^7.1.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + mkdirp "^0.5.3" + resolve "^1.3.2" + semver "^5.3.0" + tslib "^1.13.0" + tsutils "^2.29.0" + +tsutils@^2.29.0: + version "2.29.0" + resolved "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" + integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== + dependencies: + tslib "^1.8.1" + tsutils@^3.17.1, tsutils@^3.21.0: version "3.21.0" resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -25978,6 +26014,11 @@ typescript@^4.0.3, typescript@^4.1.3, typescript@~4.1.3: resolved "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3" integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw== +typescript@^4.3.2: + version "4.3.2" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805" + integrity sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw== + ua-parser-js@^0.7.18: version "0.7.28" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" @@ -27410,6 +27451,19 @@ yargs@^5.0.0: y18n "^3.2.1" yargs-parser "^3.2.0" +yarn-lock-check@^1.0.3: + version "1.0.4" + resolved "https://registry.npmjs.org/yarn-lock-check/-/yarn-lock-check-1.0.4.tgz#a0373de051be0c8442d8933070df7a45595263b4" + integrity sha512-Gj0wRN85c4OPZUlE7WsQ0a1COv38uyeWWR0YAvJr2Vxw1f32bwK19xySjUZlxt9o4QopJkd8g6x6CLv81OHwYg== + dependencies: + "@types/glob" "^7.1.3" + "@types/node" "^15.6.1" + "@yarnpkg/lockfile" "^1.1.0" + glob "^7.1.7" + ini "^2.0.0" + tslint "^6.1.3" + typescript "^4.3.2" + yauzl@^2.10.0: version "2.10.0" resolved "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" From 7b0eafddd6ac22131d13399d16900e80b436a97e Mon Sep 17 00:00:00 2001 From: Juan Lulkin Date: Wed, 2 Jun 2021 11:56:16 +0200 Subject: [PATCH 095/102] Adds ci step to check yarn locks Signed-off-by: Juan Lulkin --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a6d0f8589..4aa6cd9f8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,6 +76,9 @@ jobs: - name: prettier run: yarn prettier:check + - name: lock + run: yarn lock:check + - name: validate config run: yarn backstage-cli config:check --lax From 8bcad91738830061b29ff8648da341518c805b5a Mon Sep 17 00:00:00 2001 From: Juan Lulkin Date: Wed, 2 Jun 2021 22:08:18 +0200 Subject: [PATCH 096/102] Add lock check to microsite Signed-off-by: Juan Lulkin --- .github/workflows/microsite-build-check.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/microsite-build-check.yml b/.github/workflows/microsite-build-check.yml index 881a7ca5e4..68a6c08cd9 100644 --- a/.github/workflows/microsite-build-check.yml +++ b/.github/workflows/microsite-build-check.yml @@ -40,6 +40,10 @@ jobs: run: yarn prettier:check working-directory: microsite + - name: lock + run: yarn lock:check + working-directory: microsite + - name: build microsite run: yarn build working-directory: microsite From 72a4a7675100136aac33bbf4a4a955298abee83e Mon Sep 17 00:00:00 2001 From: Juan Lulkin Date: Thu, 3 Jun 2021 12:21:18 +0200 Subject: [PATCH 097/102] Adds yarn-lock-check to microsite Signed-off-by: Juan Lulkin --- microsite/package.json | 6 +- microsite/yarn.lock | 138 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 138 insertions(+), 6 deletions(-) diff --git a/microsite/package.json b/microsite/package.json index 595debcc65..62630463aa 100644 --- a/microsite/package.json +++ b/microsite/package.json @@ -12,13 +12,15 @@ "write-translations": "docusaurus-write-translations", "version": "docusaurus-version", "rename-version": "docusaurus-rename-version", - "verify:sidebars": "node ./scripts/verify-sidebars" + "verify:sidebars": "node ./scripts/verify-sidebars", + "lock:check": "yarn-lock-check" }, "devDependencies": { "@spotify/prettier-config": "^10.0.0", "docusaurus": "^2.0.0-alpha.70", "js-yaml": "^4.1.0", - "prettier": "^2.3.0" + "prettier": "^2.3.0", + "yarn-lock-check": "^1.0.3" }, "prettier": "@spotify/prettier-config" } diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 9834ef2396..6f6e7d53fd 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -9,6 +9,13 @@ dependencies: "@babel/highlight" "^7.0.0" +"@babel/code-frame@^7.0.0": + version "7.12.13" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" + integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g== + dependencies: + "@babel/highlight" "^7.12.13" + "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.11": version "7.12.11" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" @@ -220,6 +227,11 @@ resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== +"@babel/helper-validator-identifier@^7.14.0": + version "7.14.0" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz#d26cad8a47c65286b15df1547319a5d0bcf27288" + integrity sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A== + "@babel/helper-validator-option@^7.12.1", "@babel/helper-validator-option@^7.12.11": version "7.12.11" resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.11.tgz#d66cb8b7a3e7fe4c6962b32020a131ecf0847f4f" @@ -253,6 +265,15 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/highlight@^7.12.13": + version "7.14.0" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz#3197e375711ef6bf834e67d0daec88e4f46113cf" + integrity sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg== + dependencies: + "@babel/helper-validator-identifier" "^7.14.0" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@babel/parser@^7.12.10", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7": version "7.12.11" resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz#9ce3595bcd74bc5c466905e86c535b8b25011e79" @@ -921,16 +942,39 @@ dependencies: "@types/node" "*" +"@types/glob@^7.1.3": + version "7.1.3" + resolved "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" + integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + +"@types/minimatch@*": + version "3.0.4" + resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21" + integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA== + "@types/node@*": version "14.14.20" resolved "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz#f7974863edd21d1f8a494a73e8e2b3658615c340" integrity sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A== +"@types/node@^15.6.1": + version "15.9.0" + resolved "https://registry.npmjs.org/@types/node/-/node-15.9.0.tgz#0b7f6c33ca5618fe329a9d832b478b4964d325a8" + integrity sha512-AR1Vq1Ei1GaA5FjKL5PBqblTZsL5M+monvGSZwe6sSIdGiuu7Xr/pNwWJY+0ZQuN8AapD/XMB5IzBAyYRFbocA== + "@types/q@^1.5.1": version "1.5.4" resolved "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== +"@yarnpkg/lockfile@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== + accepts@~1.3.7: version "1.3.7" resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" @@ -1405,6 +1449,11 @@ buffer@^5.2.1: base64-js "^1.3.1" ieee754 "^1.1.13" +builtin-modules@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= + bytes@1: version "1.0.0" resolved "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8" @@ -1518,7 +1567,7 @@ caw@^2.0.0, caw@^2.0.1: tunnel-agent "^0.6.0" url-to-options "^1.0.1" -chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: +chalk@2.4.2, chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1709,7 +1758,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@^2.8.1: +commander@^2.12.1, commander@^2.8.1: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -2181,6 +2230,11 @@ diacritics-map@^0.1.0: resolved "https://registry.npmjs.org/diacritics-map/-/diacritics-map-0.1.0.tgz#6dfc0ff9d01000a2edf2865371cac316e94977af" integrity sha1-bfwP+dAQAKLt8oZTccrDFulJd68= +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + dir-glob@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034" @@ -3049,6 +3103,18 @@ glob@^7.0.0, glob@^7.0.5, glob@^7.1.2, glob@^7.1.3, glob@^7.1.6, glob@~7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.1, glob@^7.1.7: + version "7.1.7" + resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + global-modules@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" @@ -3460,6 +3526,11 @@ ini@^1.3.4, ini@^1.3.5: resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +ini@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + inquirer@6.5.0: version "6.5.0" resolved "https://registry.npmjs.org/inquirer/-/inquirer-6.5.0.tgz#2303317efc9a4ea7ec2e2df6f86569b734accf42" @@ -3567,6 +3638,13 @@ is-core-module@^2.1.0: dependencies: has "^1.0.3" +is-core-module@^2.2.0: + version "2.4.0" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1" + integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -4379,7 +4457,7 @@ mixin-deep@^1.1.3, mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@^0.5.1, mkdirp@^0.5.5, mkdirp@~0.5.1: +mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -5603,6 +5681,14 @@ resolve@^1.1.6, resolve@^1.10.0: is-core-module "^2.1.0" path-parse "^1.0.6" +resolve@^1.3.2: + version "1.20.0" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + responselike@1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" @@ -6351,11 +6437,37 @@ truncate-html@^1.0.3: "@types/cheerio" "^0.22.8" cheerio "0.22.0" -tslib@^1.9.0, tslib@^1.9.3: +tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslint@^6.1.3: + version "6.1.3" + resolved "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz#5c23b2eccc32487d5523bd3a470e9aa31789d904" + integrity sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg== + dependencies: + "@babel/code-frame" "^7.0.0" + builtin-modules "^1.1.1" + chalk "^2.3.0" + commander "^2.12.1" + diff "^4.0.1" + glob "^7.1.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + mkdirp "^0.5.3" + resolve "^1.3.2" + semver "^5.3.0" + tslib "^1.13.0" + tsutils "^2.29.0" + +tsutils@^2.29.0: + version "2.29.0" + resolved "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" + integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== + dependencies: + tslib "^1.8.1" + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -6381,6 +6493,11 @@ typedarray@^0.0.6: resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typescript@^4.3.2: + version "4.3.2" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805" + integrity sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw== + unbzip2-stream@^1.0.9: version "1.4.3" resolved "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" @@ -6650,6 +6767,19 @@ yargs@^2.3.0: dependencies: wordwrap "0.0.2" +yarn-lock-check@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/yarn-lock-check/-/yarn-lock-check-1.0.3.tgz#9d506a1544daf75750b44907b2d119fe393b4405" + integrity sha512-+jWUbTJdBZDbvOzjR+qkMZ+/ndM7d87ahCwQGJr5D5p4BT51gkQwYv4W5dJ48orcA2jI+3PmR72nwxS6PW6Mbg== + dependencies: + "@types/glob" "^7.1.3" + "@types/node" "^15.6.1" + "@yarnpkg/lockfile" "^1.1.0" + glob "^7.1.7" + ini "^2.0.0" + tslint "^6.1.3" + typescript "^4.3.2" + yauzl@^2.4.2: version "2.10.0" resolved "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" From 47f632b011db75b7f5d9c86378af3388f9d4366f Mon Sep 17 00:00:00 2001 From: Juan Lulkin Date: Thu, 3 Jun 2021 12:24:48 +0200 Subject: [PATCH 098/102] Try checking microsite from the root Signed-off-by: Juan Lulkin --- .github/workflows/microsite-build-check.yml | 1 - microsite/package.json | 6 +- microsite/yarn.lock | 138 +------------------- 3 files changed, 6 insertions(+), 139 deletions(-) diff --git a/.github/workflows/microsite-build-check.yml b/.github/workflows/microsite-build-check.yml index 68a6c08cd9..1df0d1de72 100644 --- a/.github/workflows/microsite-build-check.yml +++ b/.github/workflows/microsite-build-check.yml @@ -42,7 +42,6 @@ jobs: - name: lock run: yarn lock:check - working-directory: microsite - name: build microsite run: yarn build diff --git a/microsite/package.json b/microsite/package.json index 62630463aa..595debcc65 100644 --- a/microsite/package.json +++ b/microsite/package.json @@ -12,15 +12,13 @@ "write-translations": "docusaurus-write-translations", "version": "docusaurus-version", "rename-version": "docusaurus-rename-version", - "verify:sidebars": "node ./scripts/verify-sidebars", - "lock:check": "yarn-lock-check" + "verify:sidebars": "node ./scripts/verify-sidebars" }, "devDependencies": { "@spotify/prettier-config": "^10.0.0", "docusaurus": "^2.0.0-alpha.70", "js-yaml": "^4.1.0", - "prettier": "^2.3.0", - "yarn-lock-check": "^1.0.3" + "prettier": "^2.3.0" }, "prettier": "@spotify/prettier-config" } diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 6f6e7d53fd..9834ef2396 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -9,13 +9,6 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/code-frame@^7.0.0": - version "7.12.13" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" - integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g== - dependencies: - "@babel/highlight" "^7.12.13" - "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.11": version "7.12.11" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" @@ -227,11 +220,6 @@ resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== -"@babel/helper-validator-identifier@^7.14.0": - version "7.14.0" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz#d26cad8a47c65286b15df1547319a5d0bcf27288" - integrity sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A== - "@babel/helper-validator-option@^7.12.1", "@babel/helper-validator-option@^7.12.11": version "7.12.11" resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.11.tgz#d66cb8b7a3e7fe4c6962b32020a131ecf0847f4f" @@ -265,15 +253,6 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/highlight@^7.12.13": - version "7.14.0" - resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz#3197e375711ef6bf834e67d0daec88e4f46113cf" - integrity sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg== - dependencies: - "@babel/helper-validator-identifier" "^7.14.0" - chalk "^2.0.0" - js-tokens "^4.0.0" - "@babel/parser@^7.12.10", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7": version "7.12.11" resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz#9ce3595bcd74bc5c466905e86c535b8b25011e79" @@ -942,39 +921,16 @@ dependencies: "@types/node" "*" -"@types/glob@^7.1.3": - version "7.1.3" - resolved "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" - integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== - dependencies: - "@types/minimatch" "*" - "@types/node" "*" - -"@types/minimatch@*": - version "3.0.4" - resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21" - integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA== - "@types/node@*": version "14.14.20" resolved "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz#f7974863edd21d1f8a494a73e8e2b3658615c340" integrity sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A== -"@types/node@^15.6.1": - version "15.9.0" - resolved "https://registry.npmjs.org/@types/node/-/node-15.9.0.tgz#0b7f6c33ca5618fe329a9d832b478b4964d325a8" - integrity sha512-AR1Vq1Ei1GaA5FjKL5PBqblTZsL5M+monvGSZwe6sSIdGiuu7Xr/pNwWJY+0ZQuN8AapD/XMB5IzBAyYRFbocA== - "@types/q@^1.5.1": version "1.5.4" resolved "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== -"@yarnpkg/lockfile@^1.1.0": - version "1.1.0" - resolved "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" - integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== - accepts@~1.3.7: version "1.3.7" resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" @@ -1449,11 +1405,6 @@ buffer@^5.2.1: base64-js "^1.3.1" ieee754 "^1.1.13" -builtin-modules@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" - integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= - bytes@1: version "1.0.0" resolved "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8" @@ -1567,7 +1518,7 @@ caw@^2.0.0, caw@^2.0.1: tunnel-agent "^0.6.0" url-to-options "^1.0.1" -chalk@2.4.2, chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: +chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1758,7 +1709,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@^2.12.1, commander@^2.8.1: +commander@^2.8.1: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -2230,11 +2181,6 @@ diacritics-map@^0.1.0: resolved "https://registry.npmjs.org/diacritics-map/-/diacritics-map-0.1.0.tgz#6dfc0ff9d01000a2edf2865371cac316e94977af" integrity sha1-bfwP+dAQAKLt8oZTccrDFulJd68= -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - dir-glob@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034" @@ -3103,18 +3049,6 @@ glob@^7.0.0, glob@^7.0.5, glob@^7.1.2, glob@^7.1.3, glob@^7.1.6, glob@~7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.1, glob@^7.1.7: - version "7.1.7" - resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" - integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - global-modules@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" @@ -3526,11 +3460,6 @@ ini@^1.3.4, ini@^1.3.5: resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== -ini@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" - integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== - inquirer@6.5.0: version "6.5.0" resolved "https://registry.npmjs.org/inquirer/-/inquirer-6.5.0.tgz#2303317efc9a4ea7ec2e2df6f86569b734accf42" @@ -3638,13 +3567,6 @@ is-core-module@^2.1.0: dependencies: has "^1.0.3" -is-core-module@^2.2.0: - version "2.4.0" - resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1" - integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A== - dependencies: - has "^1.0.3" - is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -4457,7 +4379,7 @@ mixin-deep@^1.1.3, mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1: +mkdirp@^0.5.1, mkdirp@^0.5.5, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -5681,14 +5603,6 @@ resolve@^1.1.6, resolve@^1.10.0: is-core-module "^2.1.0" path-parse "^1.0.6" -resolve@^1.3.2: - version "1.20.0" - resolved "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" - integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== - dependencies: - is-core-module "^2.2.0" - path-parse "^1.0.6" - responselike@1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" @@ -6437,37 +6351,11 @@ truncate-html@^1.0.3: "@types/cheerio" "^0.22.8" cheerio "0.22.0" -tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: +tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslint@^6.1.3: - version "6.1.3" - resolved "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz#5c23b2eccc32487d5523bd3a470e9aa31789d904" - integrity sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg== - dependencies: - "@babel/code-frame" "^7.0.0" - builtin-modules "^1.1.1" - chalk "^2.3.0" - commander "^2.12.1" - diff "^4.0.1" - glob "^7.1.1" - js-yaml "^3.13.1" - minimatch "^3.0.4" - mkdirp "^0.5.3" - resolve "^1.3.2" - semver "^5.3.0" - tslib "^1.13.0" - tsutils "^2.29.0" - -tsutils@^2.29.0: - version "2.29.0" - resolved "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" - integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== - dependencies: - tslib "^1.8.1" - tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -6493,11 +6381,6 @@ typedarray@^0.0.6: resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^4.3.2: - version "4.3.2" - resolved "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805" - integrity sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw== - unbzip2-stream@^1.0.9: version "1.4.3" resolved "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" @@ -6767,19 +6650,6 @@ yargs@^2.3.0: dependencies: wordwrap "0.0.2" -yarn-lock-check@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/yarn-lock-check/-/yarn-lock-check-1.0.3.tgz#9d506a1544daf75750b44907b2d119fe393b4405" - integrity sha512-+jWUbTJdBZDbvOzjR+qkMZ+/ndM7d87ahCwQGJr5D5p4BT51gkQwYv4W5dJ48orcA2jI+3PmR72nwxS6PW6Mbg== - dependencies: - "@types/glob" "^7.1.3" - "@types/node" "^15.6.1" - "@yarnpkg/lockfile" "^1.1.0" - glob "^7.1.7" - ini "^2.0.0" - tslint "^6.1.3" - typescript "^4.3.2" - yauzl@^2.4.2: version "2.10.0" resolved "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" From 69fe0e4445f6094e11035b8a464f9b5a4f884d16 Mon Sep 17 00:00:00 2001 From: Juan Lulkin Date: Thu, 3 Jun 2021 12:27:32 +0200 Subject: [PATCH 099/102] Revert "Try checking microsite from the root" This reverts commit 82299dd56761a999d37958d5dceab1f351d88b45. Signed-off-by: Juan Lulkin --- .github/workflows/microsite-build-check.yml | 1 + microsite/package.json | 6 +- microsite/yarn.lock | 138 +++++++++++++++++++- 3 files changed, 139 insertions(+), 6 deletions(-) diff --git a/.github/workflows/microsite-build-check.yml b/.github/workflows/microsite-build-check.yml index 1df0d1de72..68a6c08cd9 100644 --- a/.github/workflows/microsite-build-check.yml +++ b/.github/workflows/microsite-build-check.yml @@ -42,6 +42,7 @@ jobs: - name: lock run: yarn lock:check + working-directory: microsite - name: build microsite run: yarn build diff --git a/microsite/package.json b/microsite/package.json index 595debcc65..62630463aa 100644 --- a/microsite/package.json +++ b/microsite/package.json @@ -12,13 +12,15 @@ "write-translations": "docusaurus-write-translations", "version": "docusaurus-version", "rename-version": "docusaurus-rename-version", - "verify:sidebars": "node ./scripts/verify-sidebars" + "verify:sidebars": "node ./scripts/verify-sidebars", + "lock:check": "yarn-lock-check" }, "devDependencies": { "@spotify/prettier-config": "^10.0.0", "docusaurus": "^2.0.0-alpha.70", "js-yaml": "^4.1.0", - "prettier": "^2.3.0" + "prettier": "^2.3.0", + "yarn-lock-check": "^1.0.3" }, "prettier": "@spotify/prettier-config" } diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 9834ef2396..6f6e7d53fd 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -9,6 +9,13 @@ dependencies: "@babel/highlight" "^7.0.0" +"@babel/code-frame@^7.0.0": + version "7.12.13" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" + integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g== + dependencies: + "@babel/highlight" "^7.12.13" + "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.11": version "7.12.11" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" @@ -220,6 +227,11 @@ resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== +"@babel/helper-validator-identifier@^7.14.0": + version "7.14.0" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz#d26cad8a47c65286b15df1547319a5d0bcf27288" + integrity sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A== + "@babel/helper-validator-option@^7.12.1", "@babel/helper-validator-option@^7.12.11": version "7.12.11" resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.11.tgz#d66cb8b7a3e7fe4c6962b32020a131ecf0847f4f" @@ -253,6 +265,15 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/highlight@^7.12.13": + version "7.14.0" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz#3197e375711ef6bf834e67d0daec88e4f46113cf" + integrity sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg== + dependencies: + "@babel/helper-validator-identifier" "^7.14.0" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@babel/parser@^7.12.10", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7": version "7.12.11" resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz#9ce3595bcd74bc5c466905e86c535b8b25011e79" @@ -921,16 +942,39 @@ dependencies: "@types/node" "*" +"@types/glob@^7.1.3": + version "7.1.3" + resolved "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" + integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + +"@types/minimatch@*": + version "3.0.4" + resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21" + integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA== + "@types/node@*": version "14.14.20" resolved "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz#f7974863edd21d1f8a494a73e8e2b3658615c340" integrity sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A== +"@types/node@^15.6.1": + version "15.9.0" + resolved "https://registry.npmjs.org/@types/node/-/node-15.9.0.tgz#0b7f6c33ca5618fe329a9d832b478b4964d325a8" + integrity sha512-AR1Vq1Ei1GaA5FjKL5PBqblTZsL5M+monvGSZwe6sSIdGiuu7Xr/pNwWJY+0ZQuN8AapD/XMB5IzBAyYRFbocA== + "@types/q@^1.5.1": version "1.5.4" resolved "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== +"@yarnpkg/lockfile@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== + accepts@~1.3.7: version "1.3.7" resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" @@ -1405,6 +1449,11 @@ buffer@^5.2.1: base64-js "^1.3.1" ieee754 "^1.1.13" +builtin-modules@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= + bytes@1: version "1.0.0" resolved "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8" @@ -1518,7 +1567,7 @@ caw@^2.0.0, caw@^2.0.1: tunnel-agent "^0.6.0" url-to-options "^1.0.1" -chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: +chalk@2.4.2, chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1709,7 +1758,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@^2.8.1: +commander@^2.12.1, commander@^2.8.1: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -2181,6 +2230,11 @@ diacritics-map@^0.1.0: resolved "https://registry.npmjs.org/diacritics-map/-/diacritics-map-0.1.0.tgz#6dfc0ff9d01000a2edf2865371cac316e94977af" integrity sha1-bfwP+dAQAKLt8oZTccrDFulJd68= +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + dir-glob@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034" @@ -3049,6 +3103,18 @@ glob@^7.0.0, glob@^7.0.5, glob@^7.1.2, glob@^7.1.3, glob@^7.1.6, glob@~7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.1, glob@^7.1.7: + version "7.1.7" + resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + global-modules@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" @@ -3460,6 +3526,11 @@ ini@^1.3.4, ini@^1.3.5: resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +ini@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + inquirer@6.5.0: version "6.5.0" resolved "https://registry.npmjs.org/inquirer/-/inquirer-6.5.0.tgz#2303317efc9a4ea7ec2e2df6f86569b734accf42" @@ -3567,6 +3638,13 @@ is-core-module@^2.1.0: dependencies: has "^1.0.3" +is-core-module@^2.2.0: + version "2.4.0" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1" + integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -4379,7 +4457,7 @@ mixin-deep@^1.1.3, mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@^0.5.1, mkdirp@^0.5.5, mkdirp@~0.5.1: +mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -5603,6 +5681,14 @@ resolve@^1.1.6, resolve@^1.10.0: is-core-module "^2.1.0" path-parse "^1.0.6" +resolve@^1.3.2: + version "1.20.0" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + responselike@1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" @@ -6351,11 +6437,37 @@ truncate-html@^1.0.3: "@types/cheerio" "^0.22.8" cheerio "0.22.0" -tslib@^1.9.0, tslib@^1.9.3: +tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslint@^6.1.3: + version "6.1.3" + resolved "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz#5c23b2eccc32487d5523bd3a470e9aa31789d904" + integrity sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg== + dependencies: + "@babel/code-frame" "^7.0.0" + builtin-modules "^1.1.1" + chalk "^2.3.0" + commander "^2.12.1" + diff "^4.0.1" + glob "^7.1.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + mkdirp "^0.5.3" + resolve "^1.3.2" + semver "^5.3.0" + tslib "^1.13.0" + tsutils "^2.29.0" + +tsutils@^2.29.0: + version "2.29.0" + resolved "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" + integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== + dependencies: + tslib "^1.8.1" + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -6381,6 +6493,11 @@ typedarray@^0.0.6: resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typescript@^4.3.2: + version "4.3.2" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805" + integrity sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw== + unbzip2-stream@^1.0.9: version "1.4.3" resolved "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" @@ -6650,6 +6767,19 @@ yargs@^2.3.0: dependencies: wordwrap "0.0.2" +yarn-lock-check@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/yarn-lock-check/-/yarn-lock-check-1.0.3.tgz#9d506a1544daf75750b44907b2d119fe393b4405" + integrity sha512-+jWUbTJdBZDbvOzjR+qkMZ+/ndM7d87ahCwQGJr5D5p4BT51gkQwYv4W5dJ48orcA2jI+3PmR72nwxS6PW6Mbg== + dependencies: + "@types/glob" "^7.1.3" + "@types/node" "^15.6.1" + "@yarnpkg/lockfile" "^1.1.0" + glob "^7.1.7" + ini "^2.0.0" + tslint "^6.1.3" + typescript "^4.3.2" + yauzl@^2.4.2: version "2.10.0" resolved "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" From c6f4588c4fae7441be3292c4ccd11479dc95e5f9 Mon Sep 17 00:00:00 2001 From: Juan Lulkin Date: Thu, 3 Jun 2021 12:52:52 +0200 Subject: [PATCH 100/102] Makes lock check lookup npmrc up recursively Signed-off-by: Juan Lulkin --- microsite/package.json | 2 +- package.json | 2 +- yarn.lock | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/microsite/package.json b/microsite/package.json index 62630463aa..66b3fc9b80 100644 --- a/microsite/package.json +++ b/microsite/package.json @@ -20,7 +20,7 @@ "docusaurus": "^2.0.0-alpha.70", "js-yaml": "^4.1.0", "prettier": "^2.3.0", - "yarn-lock-check": "^1.0.3" + "yarn-lock-check": "^1.0.4" }, "prettier": "@spotify/prettier-config" } diff --git a/package.json b/package.json index a1e16aa020..6a663c877e 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "prettier": "^2.2.1", "recursive-readdir": "^2.2.2", "shx": "^0.3.2", - "yarn-lock-check": "^1.0.3" + "yarn-lock-check": "^1.0.4" }, "prettier": "@spotify/prettier-config", "lint-staged": { diff --git a/yarn.lock b/yarn.lock index 4ddbebd5ed..f62473d07e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -27451,7 +27451,7 @@ yargs@^5.0.0: y18n "^3.2.1" yargs-parser "^3.2.0" -yarn-lock-check@^1.0.3: +yarn-lock-check@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/yarn-lock-check/-/yarn-lock-check-1.0.4.tgz#a0373de051be0c8442d8933070df7a45595263b4" integrity sha512-Gj0wRN85c4OPZUlE7WsQ0a1COv38uyeWWR0YAvJr2Vxw1f32bwK19xySjUZlxt9o4QopJkd8g6x6CLv81OHwYg== From eb5b40412196f9861ffcdfe9a5d091cb943aaa18 Mon Sep 17 00:00:00 2001 From: Juan Lulkin Date: Thu, 3 Jun 2021 13:07:43 +0200 Subject: [PATCH 101/102] Update yarn.lock Signed-off-by: Juan Lulkin --- microsite/yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 6f6e7d53fd..a6a86df36f 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -6767,10 +6767,10 @@ yargs@^2.3.0: dependencies: wordwrap "0.0.2" -yarn-lock-check@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/yarn-lock-check/-/yarn-lock-check-1.0.3.tgz#9d506a1544daf75750b44907b2d119fe393b4405" - integrity sha512-+jWUbTJdBZDbvOzjR+qkMZ+/ndM7d87ahCwQGJr5D5p4BT51gkQwYv4W5dJ48orcA2jI+3PmR72nwxS6PW6Mbg== +yarn-lock-check@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/yarn-lock-check/-/yarn-lock-check-1.0.4.tgz#a0373de051be0c8442d8933070df7a45595263b4" + integrity sha512-Gj0wRN85c4OPZUlE7WsQ0a1COv38uyeWWR0YAvJr2Vxw1f32bwK19xySjUZlxt9o4QopJkd8g6x6CLv81OHwYg== dependencies: "@types/glob" "^7.1.3" "@types/node" "^15.6.1" From 937d90e376309fac54bc53de0ae6ab4aef5ce285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 4 Jun 2021 15:53:11 +0200 Subject: [PATCH 102/102] fixup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- yarn.lock | 70 +++++++++++++++++++++++++++---------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/yarn.lock b/yarn.lock index f62473d07e..f944c675a5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8965,7 +8965,7 @@ bluebird@~3.4.1: bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.11.9: version "4.12.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== body-parser@1.19.0, body-parser@^1.18.3: @@ -9068,7 +9068,7 @@ breakword@^1.0.5: brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + resolved "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= browser-process-hrtime@^1.0.0: @@ -12188,7 +12188,7 @@ element-resize-detector@^1.2.1: elliptic@^6.0.0: version "6.5.4" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + resolved "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== dependencies: bn.js "^4.11.9" @@ -13384,7 +13384,7 @@ fetch-readablestream@^0.2.0: figgy-pudding@^3.5.1: version "3.5.2" - resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" + resolved "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== figures@^1.7.0: @@ -13635,7 +13635,7 @@ for-own@^0.1.3: foreach@^2.0.4: version "2.0.5" - resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + resolved "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= forever-agent@~0.6.1: @@ -14716,7 +14716,7 @@ growl@1.9.2: growly@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + resolved "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= gtoken@^2.3.2: @@ -14895,7 +14895,7 @@ hash-stream-validation@^0.2.1, hash-stream-validation@^0.2.2: hash.js@^1.0.0, hash.js@^1.0.3: version "1.1.7" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + resolved "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== dependencies: inherits "^2.0.3" @@ -14964,7 +14964,7 @@ history@^5.0.0: hmac-drbg@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + resolved "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= dependencies: hash.js "^1.0.3" @@ -14985,7 +14985,7 @@ hoopy@^0.1.4: hosted-git-info@^2.1.4: version "2.8.9" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== hosted-git-info@^3.0.6: @@ -15526,7 +15526,7 @@ inflight@^1.0.4: inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== inherits@2.0.1: @@ -15546,7 +15546,7 @@ ini@2.0.0, ini@^2.0.0: ini@^1.3.2, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== init-package-json@^2.0.2: @@ -15854,9 +15854,9 @@ is-directory@^0.3.1: integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= is-docker@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156" - integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw== + version "2.2.1" + resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== is-dom@^1.0.9, is-dom@^1.1.0: version "1.1.0" @@ -16298,7 +16298,7 @@ isarray@^2.0.5: isexe@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= isobject@^2.0.0: @@ -17101,7 +17101,7 @@ json-parse-even-better-errors@^2.3.0: json-pointer@^0.6.0: version "0.6.1" - resolved "https://registry.yarnpkg.com/json-pointer/-/json-pointer-0.6.1.tgz#3c6caa6ac139e2599f5a1659d39852154015054d" + resolved "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.1.tgz#3c6caa6ac139e2599f5a1659d39852154015054d" integrity sha512-3OvjqKdCBvH41DLpV4iSt6v2XhZXV1bPB4OROuknvUXI7ZQNofieCPkmE26stEJ9zdQuvIxDHCuYhfgxFAAs+Q== dependencies: foreach "^2.0.4" @@ -17438,7 +17438,7 @@ keytar@^5.4.0: keyv-memcache@^1.2.5: version "1.2.5" - resolved "https://registry.yarnpkg.com/keyv-memcache/-/keyv-memcache-1.2.5.tgz#9097af5c617dc740e7300ebfd4a2efb8917429e3" + resolved "https://registry.npmjs.org/keyv-memcache/-/keyv-memcache-1.2.5.tgz#9097af5c617dc740e7300ebfd4a2efb8917429e3" integrity sha512-iG+GHlhXyV83gmlCtMFIqNYVvv0i0towAy42NvziUR7D+k9jp81fAq0lu66H4gooOnW+ojkdigRvRpL40j1f7w== dependencies: json-buffer "^3.0.1" @@ -17460,7 +17460,7 @@ keyv@^4.0.0: keyv@^4.0.3: version "4.0.3" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254" + resolved "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254" integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA== dependencies: json-buffer "3.0.1" @@ -18235,7 +18235,7 @@ lru-cache@^5.0.0, lru-cache@^5.1.1: lru-cache@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== dependencies: yallist "^4.0.0" @@ -18540,7 +18540,7 @@ memfs@^3.1.2: memjs@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/memjs/-/memjs-1.3.0.tgz#b7959b4ff3770e4c785463fd147f1e4fafd47a24" + resolved "https://registry.npmjs.org/memjs/-/memjs-1.3.0.tgz#b7959b4ff3770e4c785463fd147f1e4fafd47a24" integrity sha512-y/V9a0auepA9Lgyr4QieK6K2FczjHucEdTpSS+hHVNmVEkYxruXhkHu8n6DSRQ4HXHEE3cc6Sf9f88WCJXGXsQ== memoize-one@^5.1.1: @@ -18661,7 +18661,7 @@ merge2@^1.2.3, merge2@^1.3.0: merge@^2.1.0: version "2.1.1" - resolved "https://registry.yarnpkg.com/merge/-/merge-2.1.1.tgz#59ef4bf7e0b3e879186436e8481c06a6c162ca98" + resolved "https://registry.npmjs.org/merge/-/merge-2.1.1.tgz#59ef4bf7e0b3e879186436e8481c06a6c162ca98" integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== meros@1.1.4, meros@^1.1.2: @@ -18841,12 +18841,12 @@ mini-css-extract-plugin@^0.9.0: minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== minimalistic-crypto-utils@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + resolved "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= minimatch@0.3: @@ -19525,9 +19525,9 @@ node-modules-regexp@^1.0.0: integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= node-notifier@^8.0.0: - version "8.0.1" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.1.tgz#f86e89bbc925f2b068784b31f382afdc6ca56be1" - integrity sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA== + version "8.0.2" + resolved "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz#f3167a38ef0d2c8a866a83e318c1ba0efeb702c5" + integrity sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg== dependencies: growly "^1.3.0" is-wsl "^2.2.0" @@ -19904,7 +19904,7 @@ object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: object-path@^0.11.4: version "0.11.5" - resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.5.tgz#d4e3cf19601a5140a55a16ad712019a9c50b577a" + resolved "https://registry.npmjs.org/object-path/-/object-path-0.11.5.tgz#d4e3cf19601a5140a55a16ad712019a9c50b577a" integrity sha512-jgSbThcoR/s+XumvGMTMf81QVBmah+/Q7K7YduKeKVWL7N111unR2d6pZZarSk6kY/caeNxUDyxOvMWyzoU2eg== object-visit@^1.0.0: @@ -21724,7 +21724,7 @@ prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, property-expr@^2.0.2: version "2.0.4" - resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.4.tgz#37b925478e58965031bb612ec5b3260f8241e910" + resolved "https://registry.npmjs.org/property-expr/-/property-expr-2.0.4.tgz#37b925478e58965031bb612ec5b3260f8241e910" integrity sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg== property-information@^5.0.0: @@ -23927,7 +23927,7 @@ shelljs@^0.8.2, shelljs@^0.8.3, shelljs@^0.8.4: shellwords@^0.1.1: version "0.1.1" - resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + resolved "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== shortid@^2.2.14: @@ -24415,7 +24415,7 @@ sshpk@^1.7.0: ssri@^6.0.1: version "6.0.2" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5" + resolved "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5" integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q== dependencies: figgy-pudding "^3.5.1" @@ -26021,7 +26021,7 @@ typescript@^4.3.2: ua-parser-js@^0.7.18: version "0.7.28" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" + resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g== uc.micro@^1.0.1, uc.micro@^1.0.5: @@ -26499,7 +26499,7 @@ uuid@^7.0.3: uuid@^8.0.0, uuid@^8.2.0, uuid@^8.3.0, uuid@^8.3.2: version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== v8-compile-cache@^2.0.3: @@ -26862,7 +26862,7 @@ websocket-driver@>=0.5.1: websocket-extensions@>=0.1.1: version "0.1.4" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + resolved "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: @@ -27298,7 +27298,7 @@ xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: y18n@^3.2.1: version "3.2.2" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" + resolved "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ== y18n@^4.0.0: @@ -27323,7 +27323,7 @@ yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: yallist@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== yaml-ast-parser@0.0.43, yaml-ast-parser@^0.0.43: