diff --git a/.changeset/green-vans-peel.md b/.changeset/green-vans-peel.md new file mode 100644 index 0000000000..cd4d30139a --- /dev/null +++ b/.changeset/green-vans-peel.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Fix `EntityTypeFilter` so it produces unique case-insensitive set of available types diff --git a/.changeset/pretty-drinks-serve.md b/.changeset/pretty-drinks-serve.md new file mode 100644 index 0000000000..a012aba11f --- /dev/null +++ b/.changeset/pretty-drinks-serve.md @@ -0,0 +1,12 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Scaffolder: Added an 'eq' handlebars helper for use in software template YAML files. This can be used to execute a step depending on the value of an input, e.g.: + +```yaml +steps: + id: 'conditional-step' + action: 'custom-action' + if: '{{ eq parameters.myvalue "custom" }}', +``` diff --git a/.changeset/search-mighty-mice-collect.md b/.changeset/search-mighty-mice-collect.md new file mode 100644 index 0000000000..bad91ff853 --- /dev/null +++ b/.changeset/search-mighty-mice-collect.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-search-backend-node': minor +--- + +Change return value of `SearchEngine.index` to `Promise` to support +implementation of external search engines. diff --git a/microsite/blog/2021-05-20-adopting-backstage.md b/microsite/blog/2021-05-20-adopting-backstage.md index 191422004b..290436c875 100644 --- a/microsite/blog/2021-05-20-adopting-backstage.md +++ b/microsite/blog/2021-05-20-adopting-backstage.md @@ -132,4 +132,4 @@ Integrating infrastructure of this size and complexity can seem overwhelming. It ## More questions about adopting Backstage? -[Contact the Backstage team at Spotify.](https://calendly.com/spotify-backstage) We’ll share more about what we’ve learned from our experience here at Spotify — and from other companies who are already using Backstage to transform their developer experience. +[Contact the Backstage team at Spotify.](https://backstage.spotify.com/) We’ll share more about what we’ve learned from our experience here at Spotify — and from other companies who are already using Backstage to transform their developer experience. diff --git a/plugins/catalog-react/src/hooks/useEntityTypeFilter.tsx b/plugins/catalog-react/src/hooks/useEntityTypeFilter.tsx index 068e995522..5fa6b0f1a2 100644 --- a/plugins/catalog-react/src/hooks/useEntityTypeFilter.tsx +++ b/plugins/catalog-react/src/hooks/useEntityTypeFilter.tsx @@ -86,10 +86,11 @@ export function useEntityTypeFilter(): EntityTypeReturn { const countByType = entities.reduce((acc, entity) => { if (typeof entity.spec?.type !== 'string') return acc; - if (!acc[entity.spec.type]) { - acc[entity.spec.type] = 0; + const entityType = entity.spec.type.toLocaleLowerCase('en-US'); + if (!acc[entityType]) { + acc[entityType] = 0; } - acc[entity.spec.type] += 1; + acc[entityType] += 1; return acc; }, {} as Record); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts index 72163a50b4..b5e8d19e62 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts @@ -203,6 +203,39 @@ describe('TaskWorker', () => { expect((event?.body?.output as JsonObject).result).toBe('winning'); }); + it('should execute steps conditionally with eq helper', async () => { + const broker = new StorageTaskBroker(storage, logger); + const taskWorker = new TaskWorker({ + logger, + workingDirectory: os.tmpdir(), + actionRegistry, + taskBroker: broker, + }); + + const { taskId } = await broker.dispatch({ + steps: [ + { id: 'test', name: 'test', action: 'test-action' }, + { + id: 'conditional', + name: 'conditional', + action: 'test-action', + if: '{{ eq steps.test.output.testOutput "winning" }}', + }, + ], + output: { + result: '{{ steps.conditional.output.testOutput }}', + }, + values: {}, + }); + + const task = await broker.claim(); + await taskWorker.runOneTask(task); + + const { events } = await storage.listEvents({ taskId }); + const event = events.find(e => e.type === 'completion'); + expect((event?.body?.output as JsonObject).result).toBe('winning'); + }); + it('should skip steps conditionally', async () => { const broker = new StorageTaskBroker(storage, logger); const taskWorker = new TaskWorker({ diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index d6067280c3..b4ba936719 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -56,6 +56,8 @@ export class TaskWorker { this.handlebars.registerHelper('json', obj => JSON.stringify(obj)); this.handlebars.registerHelper('not', value => !isTruthy(value)); + + this.handlebars.registerHelper('eq', (a, b) => a === b); } start() { diff --git a/plugins/search-backend-node/api-report.md b/plugins/search-backend-node/api-report.md index ccb7475573..ff35515389 100644 --- a/plugins/search-backend-node/api-report.md +++ b/plugins/search-backend-node/api-report.md @@ -32,7 +32,7 @@ export class LunrSearchEngine implements SearchEngine { // (undocumented) protected docStore: Record; // (undocumented) - index(type: string, documents: IndexableDocument[]): void; + index(type: string, documents: IndexableDocument[]): Promise; // (undocumented) protected logger: Logger_2; // (undocumented) @@ -57,7 +57,7 @@ export class Scheduler { // @public export interface SearchEngine { - index(type: string, documents: IndexableDocument[]): void; + index(type: string, documents: IndexableDocument[]): Promise; query(query: SearchQuery): Promise; setTranslator(translator: QueryTranslator): void; } diff --git a/plugins/search-backend-node/src/IndexBuilder.ts b/plugins/search-backend-node/src/IndexBuilder.ts index 0cabb61f38..9bbbd73067 100644 --- a/plugins/search-backend-node/src/IndexBuilder.ts +++ b/plugins/search-backend-node/src/IndexBuilder.ts @@ -140,7 +140,7 @@ export class IndexBuilder { } // pushing documents to index to a configured search engine. - this.searchEngine.index(type, documents); + await this.searchEngine.index(type, documents); }, this.collators[type].refreshInterval * 1000); }); diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts index 3c7945cbc3..3d59ba4b6f 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts @@ -15,9 +15,9 @@ */ import { getVoidLogger } from '@backstage/backend-common'; -import { ConcreteLunrQuery, LunrSearchEngine } from './LunrSearchEngine'; -import { SearchEngine } from '../types'; import lunr from 'lunr'; +import { SearchEngine } from '../types'; +import { ConcreteLunrQuery, LunrSearchEngine } from './LunrSearchEngine'; /** * Just used to test the default translator shipped with LunrSearchEngine. @@ -45,7 +45,7 @@ describe('LunrSearchEngine', () => { testLunrSearchEngine.setTranslator(translatorSpy); // When: querying the search engine - testLunrSearchEngine.query({ + await testLunrSearchEngine.query({ term: 'testTerm', filters: {}, pageCursor: '', @@ -259,7 +259,7 @@ describe('LunrSearchEngine', () => { ]; // Mock indexing of 1 document - testLunrSearchEngine.index('test-index', mockDocuments); + await testLunrSearchEngine.index('test-index', mockDocuments); // Perform search query const mockedSearchResult = await testLunrSearchEngine.query({ @@ -282,7 +282,7 @@ describe('LunrSearchEngine', () => { ]; // Mock indexing of 1 document - testLunrSearchEngine.index('test-index', mockDocuments); + await testLunrSearchEngine.index('test-index', mockDocuments); // Perform search query const mockedSearchResult = await testLunrSearchEngine.query({ @@ -315,7 +315,7 @@ describe('LunrSearchEngine', () => { ]; // Mock indexing of 1 document - testLunrSearchEngine.index('test-index', mockDocuments); + await testLunrSearchEngine.index('test-index', mockDocuments); // Perform search query const mockedSearchResult = await testLunrSearchEngine.query({ @@ -347,7 +347,7 @@ describe('LunrSearchEngine', () => { ]; // Mock indexing of 1 document - testLunrSearchEngine.index('test-index', mockDocuments); + await testLunrSearchEngine.index('test-index', mockDocuments); // Perform search query const mockedSearchResult = await testLunrSearchEngine.query({ @@ -379,7 +379,7 @@ describe('LunrSearchEngine', () => { ]; // Mock indexing of 1 document - testLunrSearchEngine.index('test-index', mockDocuments); + await testLunrSearchEngine.index('test-index', mockDocuments); // Perform search query const mockedSearchResult = await testLunrSearchEngine.query({ @@ -412,7 +412,7 @@ describe('LunrSearchEngine', () => { ]; // Mock indexing of 1 document - testLunrSearchEngine.index('test-index', mockDocuments); + await testLunrSearchEngine.index('test-index', mockDocuments); // Perform search query const mockedSearchResult = await testLunrSearchEngine.query({ @@ -445,7 +445,7 @@ describe('LunrSearchEngine', () => { ]; // Mock indexing of 1 document - testLunrSearchEngine.index('test-index', mockDocuments); + await testLunrSearchEngine.index('test-index', mockDocuments); // Perform search query const mockedSearchResult = await testLunrSearchEngine.query({ @@ -483,7 +483,7 @@ describe('LunrSearchEngine', () => { ]; // Mock indexing of 2 documents - testLunrSearchEngine.index('test-index', mockDocuments); + await testLunrSearchEngine.index('test-index', mockDocuments); // Perform search query const mockedSearchResult = await testLunrSearchEngine.query({ @@ -527,8 +527,8 @@ describe('LunrSearchEngine', () => { ]; // Mock 2 indices with 1 document each - testLunrSearchEngine.index('test-index', mockDocuments); - testLunrSearchEngine.index('test-index-2', mockDocuments2); + await testLunrSearchEngine.index('test-index', mockDocuments); + await testLunrSearchEngine.index('test-index-2', mockDocuments2); // Perform search query scoped to "test-index-2" with a filter on the field "extraField" const mockedSearchResult = await testLunrSearchEngine.query({ term: 'testTitle', @@ -565,7 +565,7 @@ describe('LunrSearchEngine', () => { ]; // Mock indexing of 2 documents - testLunrSearchEngine.index('test-index', mockDocuments); + await testLunrSearchEngine.index('test-index', mockDocuments); // Perform search query const mockedSearchResult = await testLunrSearchEngine.query({ @@ -618,8 +618,8 @@ describe('LunrSearchEngine', () => { ]; // Mock 2 indices with 2 documents each - testLunrSearchEngine.index('test-index', mockDocuments); - testLunrSearchEngine.index('test-index-2', mockDocuments2); + await testLunrSearchEngine.index('test-index', mockDocuments); + await testLunrSearchEngine.index('test-index-2', mockDocuments2); // Perform search query scoped to "test-index-2" const mockedSearchResult = await testLunrSearchEngine.query({ @@ -661,7 +661,7 @@ describe('LunrSearchEngine', () => { ]; // call index func and ensure the index func was invoked. - testLunrSearchEngine.index('test-index', mockDocuments); + await testLunrSearchEngine.index('test-index', mockDocuments); expect(indexSpy).toHaveBeenCalled(); expect(indexSpy).toHaveBeenCalledWith('test-index', [ { title: 'testTerm', text: 'testText', location: 'test/location' }, diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts index 0979fa4c90..e832e49f85 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts @@ -15,13 +15,13 @@ */ import { - SearchQuery, IndexableDocument, + SearchQuery, SearchResultSet, } from '@backstage/search-common'; import lunr from 'lunr'; import { Logger } from 'winston'; -import { SearchEngine, QueryTranslator } from '../types'; +import { QueryTranslator, SearchEngine } from '../types'; export type ConcreteLunrQuery = { lunrQueryBuilder: lunr.Index.QueryBuilder; @@ -113,7 +113,7 @@ export class LunrSearchEngine implements SearchEngine { this.translator = translator; } - index(type: string, documents: IndexableDocument[]): void { + async index(type: string, documents: IndexableDocument[]): Promise { const lunrBuilder = new lunr.Builder(); lunrBuilder.pipeline.add(lunr.trimmer, lunr.stopWordFilter, lunr.stemmer); @@ -139,7 +139,7 @@ export class LunrSearchEngine implements SearchEngine { this.lunrIndices[type] = lunrBuilder.build(); } - query(query: SearchQuery): Promise { + async query(query: SearchQuery): Promise { const { lunrQueryBuilder, documentTypes } = this.translator( query, ) as ConcreteLunrQuery; @@ -183,6 +183,6 @@ export class LunrSearchEngine implements SearchEngine { }), }; - return Promise.resolve(realResultSet); + return realResultSet; } } diff --git a/plugins/search-backend-node/src/types.ts b/plugins/search-backend-node/src/types.ts index a3b828fb58..28dc1237ba 100644 --- a/plugins/search-backend-node/src/types.ts +++ b/plugins/search-backend-node/src/types.ts @@ -67,7 +67,7 @@ export interface SearchEngine { /** * Add the given documents to the SearchEngine index of the given type. */ - index(type: string, documents: IndexableDocument[]): void; + index(type: string, documents: IndexableDocument[]): Promise; /** * Perform a search query against the SearchEngine. diff --git a/test.yaml b/test.yaml deleted file mode 100644 index e69de29bb2..0000000000