From 485a6c5f7b5f1dc39640014027bf8b6137d8b5c7 Mon Sep 17 00:00:00 2001 From: Paul Schultz Date: Tue, 4 Apr 2023 13:53:03 -0500 Subject: [PATCH 01/17] Finish TODO comment and refactor DefaultEntitiesCatalog.ts Signed-off-by: Paul Schultz --- .changeset/curly-rats-fold.md | 183 ++++++++++++++++++ .../src/service/DefaultEntitiesCatalog.ts | 109 ++++------- 2 files changed, 225 insertions(+), 67 deletions(-) create mode 100644 .changeset/curly-rats-fold.md diff --git a/.changeset/curly-rats-fold.md b/.changeset/curly-rats-fold.md new file mode 100644 index 0000000000..a5ea89b98e --- /dev/null +++ b/.changeset/curly-rats-fold.md @@ -0,0 +1,183 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +- Finished TODO to remove code snippet that was supposed to be removed in April 2022 +- Refactored `parsePagination()`, `stringifyPagination()`, and `addCondition()` to be more readable +- In `parseFilter()`, the `isNegationEntityFilter` check is earlier for faster recursion + +```diff +diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts +index ba165f96af..2023c19e13 100644 +--- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts ++++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts +@@ -14,11 +14,7 @@ + * limitations under the License. + */ + +-import { +- Entity, +- parseEntityRef, +- stringifyEntityRef, +-} from '@backstage/catalog-model'; ++import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; + import { InputError, NotFoundError } from '@backstage/errors'; + import { Knex } from 'knex'; + import { isEqual, chunk as lodashChunk } from 'lodash'; +@@ -64,43 +60,47 @@ const defaultSortField: EntityOrder = { + + const DEFAULT_LIMIT = 20; + +-function parsePagination(input?: EntityPagination): { +- limit?: number; +- offset?: number; +-} { ++function parsePagination(input?: EntityPagination): EntityPagination { + if (!input) { + return {}; + } + + let { limit, offset } = input; + +- if (input.after !== undefined) { +- let cursor; +- try { +- const json = Buffer.from(input.after, 'base64').toString('utf8'); +- cursor = JSON.parse(json); +- } catch { +- throw new InputError('Malformed after cursor, could not be parsed'); +- } +- if (cursor.limit !== undefined) { +- if (!Number.isInteger(cursor.limit)) { +- throw new InputError('Malformed after cursor, limit was not an number'); +- } +- limit = cursor.limit; ++ if (input.after === undefined) { ++ return { limit, offset }; ++ } ++ ++ let cursor; ++ try { ++ const json = Buffer.from(input.after, 'base64').toString('utf8'); ++ cursor = JSON.parse(json); ++ } catch { ++ throw new InputError('Malformed after cursor, could not be parsed'); ++ } ++ ++ if (cursor.limit !== undefined) { ++ if (!Number.isInteger(cursor.limit)) { ++ throw new InputError('Malformed after cursor, limit was not an number'); + } +- if (cursor.offset !== undefined) { +- if (!Number.isInteger(cursor.offset)) { +- throw new InputError('Malformed after cursor, offset was not a number'); +- } +- offset = cursor.offset; ++ limit = cursor.limit; ++ } ++ ++ if (cursor.offset !== undefined) { ++ if (!Number.isInteger(cursor.offset)) { ++ throw new InputError('Malformed after cursor, offset was not a number'); + } ++ offset = cursor.offset; + } + + return { limit, offset }; + } + +-function stringifyPagination(input: { limit: number; offset: number }) { +- const json = JSON.stringify({ limit: input.limit, offset: input.offset }); ++function stringifyPagination( ++ input: Required>, ++): string { ++ const { limit, offset } = input; ++ const json = JSON.stringify({ limit, offset }); + const base64 = Buffer.from(json, 'utf8').toString('base64'); + return base64; + } +@@ -111,24 +111,21 @@ function addCondition( + filter: EntitiesSearchFilter, + negate: boolean = false, + entityIdField = 'entity_id', +-) { ++): void { ++ const key = filter.key.toLowerCase(); ++ const values = filter.values?.map(v => v.toLowerCase()); ++ + // 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') + .select('search.entity_id') +- .where({ key: filter.key.toLowerCase() }) ++ .where({ key }) + .andWhere(function keyFilter() { +- if (filter.values) { +- if (filter.values.length === 1) { +- this.where({ value: filter.values[0].toLowerCase() }); +- } else { +- this.andWhere( +- 'value', +- 'in', +- filter.values.map(v => v.toLowerCase()), +- ); +- } ++ if (values?.length === 1) { ++ this.where({ value: values.at(0) }); ++ } else if (values) { ++ this.andWhere('value', 'in', values); + } + }); + queryBuilder.andWhere(entityIdField, negate ? 'not in' : 'in', matchQuery); +@@ -159,16 +156,16 @@ function parseFilter( + negate: boolean = false, + entityIdField = 'entity_id', + ): Knex.QueryBuilder { ++ if (isNegationEntityFilter(filter)) { ++ return parseFilter(filter.not, query, db, !negate, entityIdField); ++ } ++ + if (isEntitiesSearchFilter(filter)) { + return query.andWhere(function filterFunction() { + addCondition(this, db, filter, negate, entityIdField); + }); + } + +- if (isNegationEntityFilter(filter)) { +- return parseFilter(filter.not, query, db, !negate, entityIdField); +- } +- + return query[negate ? 'andWhereNot' : 'andWhere'](function filterFunction() { + if (isOrEntityFilter(filter)) { + for (const subFilter of filter.anyOf ?? []) { +@@ -274,28 +271,6 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { + entities = entities.map(e => request.fields!(e)); + } + +- // TODO(freben): This is added as a compatibility guarantee, until we can be +- // sure that all adopters have re-stitched their entities so that the new +- // targetRef field is present on them, and that they have stopped consuming +- // the now-removed old field +- // TODO(jhaals): Remove this in April 2022 +- for (const entity of entities) { +- if (entity.relations) { +- for (const relation of entity.relations as any) { +- if (!relation.targetRef && relation.target) { +- // This is the case where an old-form entity, not yet stitched with +- // the updated code, was in the database +- relation.targetRef = stringifyEntityRef(relation.target); +- } else if (!relation.target && relation.targetRef) { +- // This is the case where a new-form entity, stitched with the +- // updated code, was in the database but we still want to produce +- // the old data shape as well for compatibility reasons +- relation.target = parseEntityRef(relation.targetRef); +- } +- } +- } +- } +- + return { + entities, + pageInfo, +``` diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts index ba165f96af..2023c19e13 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts @@ -14,11 +14,7 @@ * limitations under the License. */ -import { - Entity, - parseEntityRef, - stringifyEntityRef, -} from '@backstage/catalog-model'; +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; import { InputError, NotFoundError } from '@backstage/errors'; import { Knex } from 'knex'; import { isEqual, chunk as lodashChunk } from 'lodash'; @@ -64,43 +60,47 @@ const defaultSortField: EntityOrder = { const DEFAULT_LIMIT = 20; -function parsePagination(input?: EntityPagination): { - limit?: number; - offset?: number; -} { +function parsePagination(input?: EntityPagination): EntityPagination { if (!input) { return {}; } let { limit, offset } = input; - if (input.after !== undefined) { - let cursor; - try { - const json = Buffer.from(input.after, 'base64').toString('utf8'); - cursor = JSON.parse(json); - } catch { - throw new InputError('Malformed after cursor, could not be parsed'); + if (input.after === undefined) { + return { limit, offset }; + } + + let cursor; + try { + const json = Buffer.from(input.after, 'base64').toString('utf8'); + cursor = JSON.parse(json); + } catch { + throw new InputError('Malformed after cursor, could not be parsed'); + } + + if (cursor.limit !== undefined) { + if (!Number.isInteger(cursor.limit)) { + throw new InputError('Malformed after cursor, limit was not an number'); } - if (cursor.limit !== undefined) { - if (!Number.isInteger(cursor.limit)) { - throw new InputError('Malformed after cursor, limit was not an number'); - } - limit = cursor.limit; - } - if (cursor.offset !== undefined) { - if (!Number.isInteger(cursor.offset)) { - throw new InputError('Malformed after cursor, offset was not a number'); - } - offset = cursor.offset; + limit = cursor.limit; + } + + if (cursor.offset !== undefined) { + if (!Number.isInteger(cursor.offset)) { + throw new InputError('Malformed after cursor, offset was not a number'); } + offset = cursor.offset; } return { limit, offset }; } -function stringifyPagination(input: { limit: number; offset: number }) { - const json = JSON.stringify({ limit: input.limit, offset: input.offset }); +function stringifyPagination( + input: Required>, +): string { + const { limit, offset } = input; + const json = JSON.stringify({ limit, offset }); const base64 = Buffer.from(json, 'utf8').toString('base64'); return base64; } @@ -111,24 +111,21 @@ function addCondition( filter: EntitiesSearchFilter, negate: boolean = false, entityIdField = 'entity_id', -) { +): void { + const key = filter.key.toLowerCase(); + const values = filter.values?.map(v => v.toLowerCase()); + // 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') .select('search.entity_id') - .where({ key: filter.key.toLowerCase() }) + .where({ key }) .andWhere(function keyFilter() { - if (filter.values) { - if (filter.values.length === 1) { - this.where({ value: filter.values[0].toLowerCase() }); - } else { - this.andWhere( - 'value', - 'in', - filter.values.map(v => v.toLowerCase()), - ); - } + if (values?.length === 1) { + this.where({ value: values.at(0) }); + } else if (values) { + this.andWhere('value', 'in', values); } }); queryBuilder.andWhere(entityIdField, negate ? 'not in' : 'in', matchQuery); @@ -159,16 +156,16 @@ function parseFilter( negate: boolean = false, entityIdField = 'entity_id', ): Knex.QueryBuilder { + if (isNegationEntityFilter(filter)) { + return parseFilter(filter.not, query, db, !negate, entityIdField); + } + if (isEntitiesSearchFilter(filter)) { return query.andWhere(function filterFunction() { addCondition(this, db, filter, negate, entityIdField); }); } - if (isNegationEntityFilter(filter)) { - return parseFilter(filter.not, query, db, !negate, entityIdField); - } - return query[negate ? 'andWhereNot' : 'andWhere'](function filterFunction() { if (isOrEntityFilter(filter)) { for (const subFilter of filter.anyOf ?? []) { @@ -274,28 +271,6 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { entities = entities.map(e => request.fields!(e)); } - // TODO(freben): This is added as a compatibility guarantee, until we can be - // sure that all adopters have re-stitched their entities so that the new - // targetRef field is present on them, and that they have stopped consuming - // the now-removed old field - // TODO(jhaals): Remove this in April 2022 - for (const entity of entities) { - if (entity.relations) { - for (const relation of entity.relations as any) { - if (!relation.targetRef && relation.target) { - // This is the case where an old-form entity, not yet stitched with - // the updated code, was in the database - relation.targetRef = stringifyEntityRef(relation.target); - } else if (!relation.target && relation.targetRef) { - // This is the case where a new-form entity, stitched with the - // updated code, was in the database but we still want to produce - // the old data shape as well for compatibility reasons - relation.target = parseEntityRef(relation.targetRef); - } - } - } - } - return { entities, pageInfo, From 293e6aee192adbfbd88cbd87bc32539892d32d7c Mon Sep 17 00:00:00 2001 From: Paul Schultz Date: Tue, 4 Apr 2023 14:31:35 -0500 Subject: [PATCH 02/17] Remove test for the TODO code snippet Signed-off-by: Paul Schultz --- .changeset/curly-rats-fold.md | 73 +++++++++++++++++++ .../service/DefaultEntitiesCatalog.test.ts | 58 --------------- 2 files changed, 73 insertions(+), 58 deletions(-) diff --git a/.changeset/curly-rats-fold.md b/.changeset/curly-rats-fold.md index a5ea89b98e..a20fc4d288 100644 --- a/.changeset/curly-rats-fold.md +++ b/.changeset/curly-rats-fold.md @@ -3,6 +3,7 @@ --- - Finished TODO to remove code snippet that was supposed to be removed in April 2022 +- Removed test for the TODO code snippet - Refactored `parsePagination()`, `stringifyPagination()`, and `addCondition()` to be more readable - In `parseFilter()`, the `isNegationEntityFilter` check is earlier for faster recursion @@ -181,3 +182,75 @@ index ba165f96af..2023c19e13 100644 entities, pageInfo, ``` + +```diff +diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts +index 08ae295834..31d4d06971 100644 +--- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts ++++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts +@@ -507,64 +507,6 @@ describe('DefaultEntitiesCatalog', () => { + }, + ); + +- it.each(databases.eachSupportedId())( +- 'should return both target and targetRef for entities', +- async databaseId => { +- await createDatabase(databaseId); +- await addEntity( +- { +- apiVersion: 'a', +- kind: 'k', +- metadata: { name: 'one' }, +- spec: {}, +- relations: [{ type: 'r', targetRef: 'x:y/z' } as any], +- }, +- [], +- ); +- await addEntity( +- { +- apiVersion: 'a', +- kind: 'k', +- metadata: { name: 'two' }, +- spec: {}, +- relations: [ +- { +- type: 'r', +- target: { kind: 'x', namespace: 'y', name: 'z' }, +- } as any, +- ], +- }, +- [], +- ); +- const catalog = new DefaultEntitiesCatalog({ +- database: knex, +- logger: getVoidLogger(), +- stitcher, +- }); +- +- const { entities } = await catalog.entities(); +- +- expect( +- entities.find(e => e.metadata.name === 'one')!.relations, +- ).toEqual([ +- { +- type: 'r', +- targetRef: 'x:y/z', +- target: { kind: 'x', namespace: 'y', name: 'z' }, +- }, +- ]); +- expect( +- entities.find(e => e.metadata.name === 'two')!.relations, +- ).toEqual([ +- { +- type: 'r', +- targetRef: 'x:y/z', +- target: { kind: 'x', namespace: 'y', name: 'z' }, +- }, +- ]); +- }, +- ); +- + it.each(databases.eachSupportedId())( + 'can order and combine with filtering, %p', + async databaseId => { +``` diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts index 08ae295834..31d4d06971 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts @@ -507,64 +507,6 @@ describe('DefaultEntitiesCatalog', () => { }, ); - it.each(databases.eachSupportedId())( - 'should return both target and targetRef for entities', - async databaseId => { - await createDatabase(databaseId); - await addEntity( - { - apiVersion: 'a', - kind: 'k', - metadata: { name: 'one' }, - spec: {}, - relations: [{ type: 'r', targetRef: 'x:y/z' } as any], - }, - [], - ); - await addEntity( - { - apiVersion: 'a', - kind: 'k', - metadata: { name: 'two' }, - spec: {}, - relations: [ - { - type: 'r', - target: { kind: 'x', namespace: 'y', name: 'z' }, - } as any, - ], - }, - [], - ); - const catalog = new DefaultEntitiesCatalog({ - database: knex, - logger: getVoidLogger(), - stitcher, - }); - - const { entities } = await catalog.entities(); - - expect( - entities.find(e => e.metadata.name === 'one')!.relations, - ).toEqual([ - { - type: 'r', - targetRef: 'x:y/z', - target: { kind: 'x', namespace: 'y', name: 'z' }, - }, - ]); - expect( - entities.find(e => e.metadata.name === 'two')!.relations, - ).toEqual([ - { - type: 'r', - targetRef: 'x:y/z', - target: { kind: 'x', namespace: 'y', name: 'z' }, - }, - ]); - }, - ); - it.each(databases.eachSupportedId())( 'can order and combine with filtering, %p', async databaseId => { From e0a93a048d2e3aad75e084c45c55a33efc405a91 Mon Sep 17 00:00:00 2001 From: Paul Schultz Date: Wed, 19 Apr 2023 12:33:50 -0500 Subject: [PATCH 03/17] Remove breaking changes Signed-off-by: Paul Schultz --- .changeset/curly-rats-fold.md | 106 +----------------- .../service/DefaultEntitiesCatalog.test.ts | 58 ++++++++++ .../src/service/DefaultEntitiesCatalog.ts | 28 ++++- 3 files changed, 86 insertions(+), 106 deletions(-) diff --git a/.changeset/curly-rats-fold.md b/.changeset/curly-rats-fold.md index a20fc4d288..072f3dcc6c 100644 --- a/.changeset/curly-rats-fold.md +++ b/.changeset/curly-rats-fold.md @@ -2,10 +2,7 @@ '@backstage/plugin-catalog-backend': patch --- -- Finished TODO to remove code snippet that was supposed to be removed in April 2022 -- Removed test for the TODO code snippet -- Refactored `parsePagination()`, `stringifyPagination()`, and `addCondition()` to be more readable -- In `parseFilter()`, the `isNegationEntityFilter` check is earlier for faster recursion +- Internal refactoring for performance in the service handlers ```diff diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts @@ -152,105 +149,4 @@ index ba165f96af..2023c19e13 100644 return query[negate ? 'andWhereNot' : 'andWhere'](function filterFunction() { if (isOrEntityFilter(filter)) { for (const subFilter of filter.anyOf ?? []) { -@@ -274,28 +271,6 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { - entities = entities.map(e => request.fields!(e)); - } - -- // TODO(freben): This is added as a compatibility guarantee, until we can be -- // sure that all adopters have re-stitched their entities so that the new -- // targetRef field is present on them, and that they have stopped consuming -- // the now-removed old field -- // TODO(jhaals): Remove this in April 2022 -- for (const entity of entities) { -- if (entity.relations) { -- for (const relation of entity.relations as any) { -- if (!relation.targetRef && relation.target) { -- // This is the case where an old-form entity, not yet stitched with -- // the updated code, was in the database -- relation.targetRef = stringifyEntityRef(relation.target); -- } else if (!relation.target && relation.targetRef) { -- // This is the case where a new-form entity, stitched with the -- // updated code, was in the database but we still want to produce -- // the old data shape as well for compatibility reasons -- relation.target = parseEntityRef(relation.targetRef); -- } -- } -- } -- } -- - return { - entities, - pageInfo, -``` - -```diff -diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts -index 08ae295834..31d4d06971 100644 ---- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts -+++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts -@@ -507,64 +507,6 @@ describe('DefaultEntitiesCatalog', () => { - }, - ); - -- it.each(databases.eachSupportedId())( -- 'should return both target and targetRef for entities', -- async databaseId => { -- await createDatabase(databaseId); -- await addEntity( -- { -- apiVersion: 'a', -- kind: 'k', -- metadata: { name: 'one' }, -- spec: {}, -- relations: [{ type: 'r', targetRef: 'x:y/z' } as any], -- }, -- [], -- ); -- await addEntity( -- { -- apiVersion: 'a', -- kind: 'k', -- metadata: { name: 'two' }, -- spec: {}, -- relations: [ -- { -- type: 'r', -- target: { kind: 'x', namespace: 'y', name: 'z' }, -- } as any, -- ], -- }, -- [], -- ); -- const catalog = new DefaultEntitiesCatalog({ -- database: knex, -- logger: getVoidLogger(), -- stitcher, -- }); -- -- const { entities } = await catalog.entities(); -- -- expect( -- entities.find(e => e.metadata.name === 'one')!.relations, -- ).toEqual([ -- { -- type: 'r', -- targetRef: 'x:y/z', -- target: { kind: 'x', namespace: 'y', name: 'z' }, -- }, -- ]); -- expect( -- entities.find(e => e.metadata.name === 'two')!.relations, -- ).toEqual([ -- { -- type: 'r', -- targetRef: 'x:y/z', -- target: { kind: 'x', namespace: 'y', name: 'z' }, -- }, -- ]); -- }, -- ); -- - it.each(databases.eachSupportedId())( - 'can order and combine with filtering, %p', - async databaseId => { ``` diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts index 31d4d06971..08ae295834 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts @@ -507,6 +507,64 @@ describe('DefaultEntitiesCatalog', () => { }, ); + it.each(databases.eachSupportedId())( + 'should return both target and targetRef for entities', + async databaseId => { + await createDatabase(databaseId); + await addEntity( + { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'one' }, + spec: {}, + relations: [{ type: 'r', targetRef: 'x:y/z' } as any], + }, + [], + ); + await addEntity( + { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'two' }, + spec: {}, + relations: [ + { + type: 'r', + target: { kind: 'x', namespace: 'y', name: 'z' }, + } as any, + ], + }, + [], + ); + const catalog = new DefaultEntitiesCatalog({ + database: knex, + logger: getVoidLogger(), + stitcher, + }); + + const { entities } = await catalog.entities(); + + expect( + entities.find(e => e.metadata.name === 'one')!.relations, + ).toEqual([ + { + type: 'r', + targetRef: 'x:y/z', + target: { kind: 'x', namespace: 'y', name: 'z' }, + }, + ]); + expect( + entities.find(e => e.metadata.name === 'two')!.relations, + ).toEqual([ + { + type: 'r', + targetRef: 'x:y/z', + target: { kind: 'x', namespace: 'y', name: 'z' }, + }, + ]); + }, + ); + it.each(databases.eachSupportedId())( 'can order and combine with filtering, %p', async databaseId => { diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts index 2023c19e13..9e78d7cd2c 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; +import { + Entity, + parseEntityRef, + stringifyEntityRef, +} from '@backstage/catalog-model'; import { InputError, NotFoundError } from '@backstage/errors'; import { Knex } from 'knex'; import { isEqual, chunk as lodashChunk } from 'lodash'; @@ -271,6 +275,28 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { entities = entities.map(e => request.fields!(e)); } + // TODO(freben): This is added as a compatibility guarantee, until we can be + // sure that all adopters have re-stitched their entities so that the new + // targetRef field is present on them, and that they have stopped consuming + // the now-removed old field + // TODO(jhaals): Remove this in April 2022 + for (const entity of entities) { + if (entity.relations) { + for (const relation of entity.relations as any) { + if (!relation.targetRef && relation.target) { + // This is the case where an old-form entity, not yet stitched with + // the updated code, was in the database + relation.targetRef = stringifyEntityRef(relation.target); + } else if (!relation.target && relation.targetRef) { + // This is the case where a new-form entity, stitched with the + // updated code, was in the database but we still want to produce + // the old data shape as well for compatibility reasons + relation.target = parseEntityRef(relation.targetRef); + } + } + } + } + return { entities, pageInfo, From 2c5661f38996250de759d6bb22129fa9536aa457 Mon Sep 17 00:00:00 2001 From: Scott Guymer Date: Fri, 28 Apr 2023 16:36:35 +0200 Subject: [PATCH 04/17] Allow endpoint to be configured for sqs event publisher Signed-off-by: Scott Guymer --- .changeset/cold-laws-turn.md | 5 +++++ .../src/publisher/AwsSqsConsumingEventPublisher.ts | 6 +++++- .../events-backend-module-aws-sqs/src/publisher/config.ts | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .changeset/cold-laws-turn.md diff --git a/.changeset/cold-laws-turn.md b/.changeset/cold-laws-turn.md new file mode 100644 index 0000000000..6220f088db --- /dev/null +++ b/.changeset/cold-laws-turn.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-events-backend-module-aws-sqs': minor +--- + +Allow endpoint configuration for sqs, enabling use of localstack for testing. diff --git a/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts b/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts index e649e2ee83..4f4e6ea55d 100644 --- a/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts +++ b/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts @@ -68,7 +68,11 @@ export class AwsSqsConsumingEventPublisher implements EventPublisher { VisibilityTimeout: config.visibilityTimeout?.as('seconds'), WaitTimeSeconds: config.pollingWaitTime.as('seconds'), }; - this.sqs = new SQSClient({ region: config.region }); + + this.sqs = new SQSClient({ + region: config.region, + endpoint: config.endpoint, + }); this.queueUrl = config.queueUrl; this.taskTimeoutSeconds = config.timeout.as('seconds'); diff --git a/plugins/events-backend-module-aws-sqs/src/publisher/config.ts b/plugins/events-backend-module-aws-sqs/src/publisher/config.ts index c6b74ea8d3..47cd82857c 100644 --- a/plugins/events-backend-module-aws-sqs/src/publisher/config.ts +++ b/plugins/events-backend-module-aws-sqs/src/publisher/config.ts @@ -31,6 +31,7 @@ export interface AwsSqsEventSourceConfig { topic: string; visibilityTimeout?: Duration; waitTimeAfterEmptyReceive: Duration; + endpoint?: string; } // TODO(pjungermann): validation could be improved similar to `convertToHumanDuration` at @backstage/backend-tasks From b10f3e365585e1b113b1b785afb89f3d043de338 Mon Sep 17 00:00:00 2001 From: Scott Guymer Date: Fri, 28 Apr 2023 16:56:28 +0200 Subject: [PATCH 05/17] Add config mapping Signed-off-by: Scott Guymer --- .../events-backend-module-aws-sqs/src/publisher/config.test.ts | 2 ++ plugins/events-backend-module-aws-sqs/src/publisher/config.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/plugins/events-backend-module-aws-sqs/src/publisher/config.test.ts b/plugins/events-backend-module-aws-sqs/src/publisher/config.test.ts index 1fa9df4ce3..6d7a08d7a7 100644 --- a/plugins/events-backend-module-aws-sqs/src/publisher/config.test.ts +++ b/plugins/events-backend-module-aws-sqs/src/publisher/config.test.ts @@ -79,6 +79,7 @@ describe('readConfig', () => { url: 'https://fake1.queue.url', visibilityTimeout: { minutes: 5 }, waitTime: { seconds: 10 }, + endpoint: 'https://fake.endpoint.url', }, timeout: { minutes: 5 }, waitTimeAfterEmptyReceive: { seconds: 30 }, @@ -97,6 +98,7 @@ describe('readConfig', () => { expect(publisherConfigs[0].topic).toEqual('fake1'); expect(publisherConfigs[0].region).toEqual('eu-west-1'); expect(publisherConfigs[0].queueUrl).toEqual('https://fake1.queue.url'); + expect(publisherConfigs[0].endpoint).toEqual('https://fake.endpoint.url'); expect(publisherConfigs[0].pollingWaitTime.as('seconds')).toBe(10); expect(publisherConfigs[0].timeout.as('seconds')).toBe(300); expect(publisherConfigs[0].waitTimeAfterEmptyReceive.as('seconds')).toBe( diff --git a/plugins/events-backend-module-aws-sqs/src/publisher/config.ts b/plugins/events-backend-module-aws-sqs/src/publisher/config.ts index 47cd82857c..eeb650ff6c 100644 --- a/plugins/events-backend-module-aws-sqs/src/publisher/config.ts +++ b/plugins/events-backend-module-aws-sqs/src/publisher/config.ts @@ -75,6 +75,7 @@ export function readConfig(config: Config): AwsSqsEventSourceConfig[] { } const queueUrl = topicConfig.getString('queue.url'); const region = topicConfig.getString('queue.region'); + const endpoint = topicConfig.getOptionalString('queue.endpoint'); const visibilityTimeout = readOptionalDuration( topicConfig, 'queue.visibilityTimeout', @@ -107,6 +108,7 @@ export function readConfig(config: Config): AwsSqsEventSourceConfig[] { return { pollingWaitTime, queueUrl, + endpoint, region, timeout, topic, From bd101cefd3763ca952a05ce32e5de69ad966272c Mon Sep 17 00:00:00 2001 From: danyelleac Date: Thu, 4 May 2023 16:48:40 -0300 Subject: [PATCH 06/17] github-org-edited-team-description Signed-off-by: danyelleac --- .changeset/wet-dolphins-love.md | 5 +++++ .../src/providers/GithubOrgEntityProvider.test.ts | 1 + .../src/providers/GithubOrgEntityProvider.ts | 9 ++++++++- 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 .changeset/wet-dolphins-love.md diff --git a/.changeset/wet-dolphins-love.md b/.changeset/wet-dolphins-love.md new file mode 100644 index 0000000000..1b22821f81 --- /dev/null +++ b/.changeset/wet-dolphins-love.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-github': major +--- + +Adding description in function 'onTeamEditedInOrganization` in event of team.edited diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts index ff42e42718..77f0a050c1 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts @@ -834,6 +834,7 @@ describe('GithubOrgEntityProvider', () => { }, name: 'mygroup-with-spaces', }, + description: 'description-from-the-new-team', apiVersion: 'backstage.io/v1alpha1', kind: 'Group', spec: { diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts index 02e225c938..c181510a67 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts @@ -371,14 +371,21 @@ export class GithubOrgEntityProvider assignGroupsToUsers(usersToRebuild, groups); buildOrgHierarchy(groups); - const oldName = event.changes.name?.from || ''; + const oldName = event.changes.name?.from || event.team.name; const oldSlug = oldName.toLowerCase().replaceAll(/\s/gi, '-'); + const oldDescription = + event.changes.description?.from || event.team.description; + const oldDescriptionSlug = oldDescription + ?.toLowerCase() + .replaceAll(/\s/gi, '-'); + const { removed } = createDeltaOperation(org, [ { ...group, metadata: { name: oldSlug, + description: oldDescriptionSlug, }, }, ]); From 6028b54ebf101caa7db08146dd3605b3408823f9 Mon Sep 17 00:00:00 2001 From: danyelleac Date: Thu, 4 May 2023 17:10:01 -0300 Subject: [PATCH 07/17] test-github-org-edited-team-description Signed-off-by: danyelleac --- .../src/providers/GithubOrgEntityProvider.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts index 77f0a050c1..3e58c1473f 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts @@ -833,8 +833,8 @@ describe('GithubOrgEntityProvider', () => { 'url:https://github.com/orgs/backstage/teams/mygroup-with-spaces', }, name: 'mygroup-with-spaces', + description: 'description-from-the-new-team', }, - description: 'description-from-the-new-team', apiVersion: 'backstage.io/v1alpha1', kind: 'Group', spec: { From 447172cb6ac5f29e957b6529eecd4fe21402d93e Mon Sep 17 00:00:00 2001 From: PIYUSH NEGI <43876655+npiyush97@users.noreply.github.com> Date: Fri, 5 May 2023 22:08:29 +0530 Subject: [PATCH 08/17] Update docusaurus.config.js related to fix => https://github.com/backstage/backstage/issues/17620 forget to add link here Signed-off-by: PIYUSH NEGI <43876655+npiyush97@users.noreply.github.com> --- microsite/docusaurus.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/docusaurus.config.js b/microsite/docusaurus.config.js index e6137b572b..d0f6bb277f 100644 --- a/microsite/docusaurus.config.js +++ b/microsite/docusaurus.config.js @@ -241,7 +241,7 @@ module.exports = { }, { label: 'Subscribe to our newsletter', - to: 'https://mailchi.mp/spotify/backstage-community', + to: 'https://info.backstage.spotify.com/newsletter_subscribe', }, { label: 'CNCF Incubation', From 45eca2b37234afb46713c19714d4cd04f1293b60 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 May 2023 22:37:28 +0000 Subject: [PATCH 09/17] chore(deps): update dependency @types/node to v16.18.26 Signed-off-by: Renovate Bot --- yarn.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3c0183f052..26a1bd3736 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16222,9 +16222,9 @@ __metadata: linkType: hard "@types/node@npm:*, @types/node@npm:>=12.12.47, @types/node@npm:>=13.7.0": - version: 20.0.0 - resolution: "@types/node@npm:20.0.0" - checksum: 7dadc41081eee634fd0b19e46dfb0a74f8296ee562533118f7c2f2b54dcaba7124961d506db425fff74d8e8288610b93939cd06fa723f053c72b1a7e87aa4ddc + version: 20.1.0 + resolution: "@types/node@npm:20.1.0" + checksum: c6d9afa9aa78b4b4348c69ece94975be70346b144c278f1395694a10ef919d7db300018101b6f9245e6bdd76674a5327d2d14829092f3d5295e858cff5f22a66 languageName: node linkType: hard @@ -16243,9 +16243,9 @@ __metadata: linkType: hard "@types/node@npm:^14.14.31": - version: 14.18.44 - resolution: "@types/node@npm:14.18.44" - checksum: 650527d288956649da15917512236815c3e00f8a72a8d4f316dac88180d0b074884eab944ef2d404f0b394ef56e62e7e5891c786c19f4474365672782a1923b0 + version: 14.18.45 + resolution: "@types/node@npm:14.18.45" + checksum: 2857cd1912d70f6a97b9d8085088b12ab80bcaa06e4ea20535020167bfcbc6bea9469fafc437acddd34b07e3d94b1d35bcbd51d960c8841ab1a675166e97d8b9 languageName: node linkType: hard @@ -16257,16 +16257,16 @@ __metadata: linkType: hard "@types/node@npm:^16.0.0, @types/node@npm:^16.11.26, @types/node@npm:^16.9.2": - version: 16.18.25 - resolution: "@types/node@npm:16.18.25" - checksum: 181760ad6b54fcc498dfeb249e98bbf0be51d7c35e92e760e1a82004fa42b86e8c33a8f8dd7743b5ef872bda0753d9e6a5b8e3f0aed63e9eb79b4e65760c1fbe + version: 16.18.26 + resolution: "@types/node@npm:16.18.26" + checksum: 2a746b3f9e97e00aa5d1d8cce36c5b75d5504df15e82647ad1fadbc3435ea4491203d044bbaa595edc2ab25a9ab820793f9d38dba753ad150929335fa21e24d4 languageName: node linkType: hard "@types/node@npm:^18.11.17": - version: 18.16.4 - resolution: "@types/node@npm:18.16.4" - checksum: 21d575391c88be9f3568675de59d571597cfc122efa32939ae2697c2218c6ccfb6ba0d4b5d9301905e6c4964f43add42569249b15d2e12d68b0aedaf891ec9a5 + version: 18.16.5 + resolution: "@types/node@npm:18.16.5" + checksum: b6df96a76a7655cd944794197cf63f5269e5034041bc355f4f243cb3f185ad3e1948011b6f0ac66c22434e50bc53a20ec3a9d95ad284f0e3819813d309d1650b languageName: node linkType: hard From cb805f0887e92dc53916d88270de79b7c2ae64e2 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 6 May 2023 11:32:43 +0200 Subject: [PATCH 10/17] OWNERS: add org member sennyeya Signed-off-by: Patrik Oldsberg --- OWNERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/OWNERS.md b/OWNERS.md index 0a567ee269..0d2040d770 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -92,6 +92,7 @@ Scope: The TechDocs plugin and related tooling | Adam Harvey | Cisco | [adamdmharvey](https://github.com/adamdmharvey) | `adamharvey#3739` | | Andre Wanlin | Keyloop | [awanlin](https://github.com/awanlin) | `Ahhhndre#3095` | | Andrew Thauer | Wealthsimple | [andrewthauer](https://github.com/andrewthauer) | `andrewthauer#3060` | +| Aramis Sennyey | | [sennyeya](https://github.com/sennyeya) | `Aramis#7984` | | Brian Fletcher | Roadie.io | [punkle](https://github.com/punkle) | `Brian Fletcher#7051` | | Carlos Esteban Lopez Jaramillo | VMWare | [luchillo17](https://github.com/luchillo17) | `luchillo17#8777` | | David Tuite | Roadie.io | [dtuite](https://github.com/dtuite) | `David Tuite (roadie.io)#1010` | From 3b865ae7d0106bedd6977362184e1ce08e7fe661 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 6 May 2023 23:44:42 +0000 Subject: [PATCH 11/17] fix(deps): update dependency terser-webpack-plugin to v5.3.8 Signed-off-by: Renovate Bot --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 26a1bd3736..2190c984d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38262,14 +38262,14 @@ __metadata: linkType: hard "terser-webpack-plugin@npm:*, terser-webpack-plugin@npm:^5.1.3": - version: 5.3.7 - resolution: "terser-webpack-plugin@npm:5.3.7" + version: 5.3.8 + resolution: "terser-webpack-plugin@npm:5.3.8" dependencies: "@jridgewell/trace-mapping": ^0.3.17 jest-worker: ^27.4.5 schema-utils: ^3.1.1 serialize-javascript: ^6.0.1 - terser: ^5.16.5 + terser: ^5.16.8 peerDependencies: webpack: ^5.1.0 peerDependenciesMeta: @@ -38279,13 +38279,13 @@ __metadata: optional: true uglify-js: optional: true - checksum: 095e699fdeeb553cdf2c6f75f983949271b396d9c201d7ae9fc633c45c1c1ad14c7257ef9d51ccc62213dd3e97f875870ba31550f6d4f1b6674f2615562da7f7 + checksum: 0ffc2a1949b1fd60ef9c815c4629b9817656db612bb58c5db96e3b04204c86afd142b115392e48733364edc7bf95131f54c10174c05f046ba8f2adead6b06c3c languageName: node linkType: hard -"terser@npm:^5.10.0, terser@npm:^5.16.5": - version: 5.16.6 - resolution: "terser@npm:5.16.6" +"terser@npm:^5.10.0, terser@npm:^5.16.8": + version: 5.17.1 + resolution: "terser@npm:5.17.1" dependencies: "@jridgewell/source-map": ^0.3.2 acorn: ^8.5.0 @@ -38293,7 +38293,7 @@ __metadata: source-map-support: ~0.5.20 bin: terser: bin/terser - checksum: f763a7bcc7b98cb2bfc41434f7b92bfe8a701a12c92ea6049377736c8e6de328240d654a20dfe15ce170fd783491b9873fad9f4cd8fee4f6c6fb8ca407859dee + checksum: 69b0e80e3c4084db2819de4d6ae8a2ba79f2fcd7ed6df40fe4b602ec7bfd8e889cc63c7d5268f30990ffecbf6eeda18f857adad9386fe2c2331b398d58ed855c languageName: node linkType: hard From be4fa53fab8602f632a641ab0d762e18fa40611e Mon Sep 17 00:00:00 2001 From: David Weber Date: Sun, 7 May 2023 16:04:54 +0200 Subject: [PATCH 12/17] fix: fix description links when clicking entry in radar Signed-off-by: David Weber --- .changeset/fair-suits-warn.md | 5 +++++ plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx | 3 +++ plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx | 1 + 3 files changed, 9 insertions(+) create mode 100644 .changeset/fair-suits-warn.md diff --git a/.changeset/fair-suits-warn.md b/.changeset/fair-suits-warn.md new file mode 100644 index 0000000000..3edc2aa1ca --- /dev/null +++ b/.changeset/fair-suits-warn.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-tech-radar': patch +--- + +Fix description links when clicking entry in radar. diff --git a/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx b/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx index af9e840ead..66940cd1d7 100644 --- a/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx +++ b/plugins/tech-radar/src/components/RadarEntry/RadarEntry.tsx @@ -26,6 +26,7 @@ export type Props = { value: number; color: string; url?: string; + links?: Array<{ title: string; url: string }>; moved?: number; description?: string; timeline?: EntrySnapshot[]; @@ -73,6 +74,7 @@ const RadarEntry = (props: Props): JSX.Element => { title, color, url, + links, value, x, y, @@ -112,6 +114,7 @@ const RadarEntry = (props: Props): JSX.Element => { description={description ? description : 'no description'} timeline={timeline ? timeline : []} url={url} + links={links} /> )} {description ? ( diff --git a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx index 957017692b..0fb6e3a36a 100644 --- a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx +++ b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.tsx @@ -73,6 +73,7 @@ const RadarPlot = (props: Props): JSX.Element => { color={entry.color || ''} value={(entry?.index || 0) + 1} url={entry.url} + links={entry.links} description={entry.description} moved={entry.moved} title={entry.title} From 4eab7f546cc1e2426d156a378a6eef871d8a90cd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 7 May 2023 22:53:47 +0000 Subject: [PATCH 13/17] fix(deps): update dependency @keyv/redis to v2.5.8 Signed-off-by: Renovate Bot --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2190c984d0..bae4aa0e17 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12103,11 +12103,11 @@ __metadata: linkType: hard "@keyv/redis@npm:^2.5.3": - version: 2.5.7 - resolution: "@keyv/redis@npm:2.5.7" + version: 2.5.8 + resolution: "@keyv/redis@npm:2.5.8" dependencies: - ioredis: ^5.3.1 - checksum: e0ed1b6602afcc339bb2bf014d8801e6b7a3b6392d0f8e4ccfc59d919f59229b8787c50c48fc51636625ffdf55cf1ef6c450425a73bd88f9b866e74f1b3e25f8 + ioredis: ^5.3.2 + checksum: 1050c4997b62e701112b48e5375c4e53a5e49454c1621fbb00b1c2272e55a575f9ace49c5179800f73e6f07ce247a4cf6e1260a6713ad1392fd8f8825079771f languageName: node linkType: hard @@ -26548,9 +26548,9 @@ __metadata: languageName: node linkType: hard -"ioredis@npm:^5.3.1": - version: 5.3.1 - resolution: "ioredis@npm:5.3.1" +"ioredis@npm:^5.3.2": + version: 5.3.2 + resolution: "ioredis@npm:5.3.2" dependencies: "@ioredis/commands": ^1.1.1 cluster-key-slot: ^1.1.0 @@ -26561,7 +26561,7 @@ __metadata: redis-errors: ^1.2.0 redis-parser: ^3.0.0 standard-as-callback: ^2.1.0 - checksum: 6c98cb8f8772ad4bb2b6b7a0a224e4a63f4759f9a28e84205f18788552f08221d51f391c10892dca01a1b10b2804d0a7e6082b0b7decd65538a5fb6b0f4f1f82 + checksum: 9a23559133e862a768778301efb68ae8c2af3c33562174b54a4c2d6574b976e85c75a4c34857991af733e35c48faf4c356e7daa8fb0a3543d85ff1768c8754bc languageName: node linkType: hard From acca8966465c418530549a3e8f43df7077bb62b2 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Mon, 8 May 2023 08:41:22 +0300 Subject: [PATCH 14/17] fix: remove object-hash dependency from home plugin The object hash doesn't work if the component props include React components such as icons. This is true for example the HomepageToolkit component. Removed the hashing of the component props completely as the hash is totally optional to be used as a key in the CustomHomepageGrid. Signed-off-by: Heikki Hellgren --- .changeset/chilly-taxis-shop.md | 5 ++++ packages/app/src/components/home/HomePage.tsx | 30 ++++++++++++++++++- plugins/home/package.json | 1 - .../CustomHomepage/CustomHomepageGrid.tsx | 5 ++-- yarn.lock | 1 - 5 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 .changeset/chilly-taxis-shop.md diff --git a/.changeset/chilly-taxis-shop.md b/.changeset/chilly-taxis-shop.md new file mode 100644 index 0000000000..9e2e73a5e3 --- /dev/null +++ b/.changeset/chilly-taxis-shop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-home': patch +--- + +Remove object-hash dependency diff --git a/packages/app/src/components/home/HomePage.tsx b/packages/app/src/components/home/HomePage.tsx index 26a7ab60f3..da1d0d425c 100644 --- a/packages/app/src/components/home/HomePage.tsx +++ b/packages/app/src/components/home/HomePage.tsx @@ -21,12 +21,15 @@ import { ClockConfig, HomePageStarredEntities, CustomHomepageGrid, + HomePageToolkit, + HomePageCompanyLogo, } from '@backstage/plugin-home'; import { Content, Header, Page } from '@backstage/core-components'; import { HomePageSearchBar } from '@backstage/plugin-search'; import { HomePageCalendar } from '@backstage/plugin-gcalendar'; import { MicrosoftCalendarCard } from '@backstage/plugin-microsoft-calendar'; import React from 'react'; +import HomeIcon from '@material-ui/icons/Home'; const clockConfigs: ClockConfig[] = [ { @@ -55,12 +58,26 @@ const timeFormat: Intl.DateTimeFormatOptions = { const defaultConfig = [ { - component: 'HomePageSearchBar', + component: 'CompanyLogo', x: 0, y: 0, width: 12, height: 1, }, + { + component: 'WelcomeTitle', + x: 0, + y: 1, + width: 12, + height: 1, + }, + { + component: 'HomePageSearchBar', + x: 0, + y: 2, + width: 12, + height: 1, + }, ]; export const homePage = ( @@ -78,6 +95,17 @@ export const homePage = ( + + + , + }, + ]} + /> diff --git a/plugins/home/package.json b/plugins/home/package.json index e8eb7f3ba9..607524147e 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -48,7 +48,6 @@ "@rjsf/validator-ajv8": "5.6.0", "@types/react": "^16.13.1 || ^17.0.0", "lodash": "^4.17.21", - "object-hash": "^3.0.0", "react-grid-layout": "^1.3.4", "react-resizable": "^3.0.4", "react-use": "^17.2.4", diff --git a/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx b/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx index 232fbd12c2..6af9044cbd 100644 --- a/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx +++ b/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx @@ -46,7 +46,6 @@ import { CardConfig } from '../../extensions'; // eslint-disable-next-line new-cap const ResponsiveGrid = WidthProvider(Responsive); -const hash = require('object-hash'); const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -336,14 +335,14 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => { ...widget.component.props, ...(w.settings ?? {}), }; - const propsHash = hash(widgetProps, {}); + return (
- + {editMode && ( Date: Mon, 8 May 2023 12:51:41 +0200 Subject: [PATCH 15/17] feature(plugin-search-react): Extend InputProps on In the current implementation of it is possible to overwrite the default InputProps by passing a prop of the same name. But sometimes is necessary to extend only, for example when one is interested on changing the clear button only. The clear button is supplied in the endAdornment, part of the InputProps, but at the same time the startAdornment is also on it. To be able to replace the clear button implementation without affecting the rest, this patch proposes a change where `InputProps` is exposed in the same manner as `inputProps` (notice the change from capital I to i). Where the received object may overwrite keys using the spread operator. Signed-off-by: Renan Mendes Carvalho --- .changeset/ninety-mugs-sin.md | 5 +++++ .../src/components/SearchBar/SearchBar.stories.tsx | 7 ++++++- .../search-react/src/components/SearchBar/SearchBar.tsx | 6 ++++-- 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 .changeset/ninety-mugs-sin.md diff --git a/.changeset/ninety-mugs-sin.md b/.changeset/ninety-mugs-sin.md new file mode 100644 index 0000000000..6b71a00667 --- /dev/null +++ b/.changeset/ninety-mugs-sin.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-react': minor +--- + + accepts InputProp property that can override keys from default diff --git a/plugins/search-react/src/components/SearchBar/SearchBar.stories.tsx b/plugins/search-react/src/components/SearchBar/SearchBar.stories.tsx index 559ae1776e..7e97d790a7 100644 --- a/plugins/search-react/src/components/SearchBar/SearchBar.stories.tsx +++ b/plugins/search-react/src/components/SearchBar/SearchBar.stories.tsx @@ -92,13 +92,18 @@ const useStyles = makeStyles({ borderRadius: '50px', margin: 'auto', }, + notchedOutline: { + borderStyle: 'none', + }, }); export const CustomStyles = () => { const classes = useStyles(); return ( - + ); }; diff --git a/plugins/search-react/src/components/SearchBar/SearchBar.tsx b/plugins/search-react/src/components/SearchBar/SearchBar.tsx index 913f515aaa..cfd0538694 100644 --- a/plugins/search-react/src/components/SearchBar/SearchBar.tsx +++ b/plugins/search-react/src/components/SearchBar/SearchBar.tsx @@ -80,7 +80,8 @@ export const SearchBarBase: ForwardRefExoticComponent = value: defaultValue, label, placeholder, - inputProps: defaultInputProps = {}, + inputProps = {}, + InputProps = {}, endAdornment, ...rest } = props; @@ -168,10 +169,11 @@ export const SearchBarBase: ForwardRefExoticComponent = endAdornment: clearButton ? clearButtonEndAdornment : endAdornment, + ...InputProps, }} inputProps={{ 'aria-label': ariaLabel, - ...defaultInputProps, + ...inputProps, }} fullWidth={fullWidth} onChange={handleChange} From 1f802f03ad509d80b342041ca2a67b0d557c5c97 Mon Sep 17 00:00:00 2001 From: Danyelle Amarante <90638175+Danyelleac@users.noreply.github.com> Date: Mon, 8 May 2023 09:19:54 -0300 Subject: [PATCH 16/17] Update .changeset/wet-dolphins-love.md Co-authored-by: Johan Haals Signed-off-by: Danyelle Amarante <90638175+Danyelleac@users.noreply.github.com> --- .changeset/wet-dolphins-love.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/wet-dolphins-love.md b/.changeset/wet-dolphins-love.md index 1b22821f81..3aa7f776a7 100644 --- a/.changeset/wet-dolphins-love.md +++ b/.changeset/wet-dolphins-love.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-backend-module-github': major --- -Adding description in function 'onTeamEditedInOrganization` in event of team.edited +Updated the `team.edited` event emitted from `GithubOrgEntityProvider` to also include teams description. From b3a8de44b400d374793fb5eb5b4e0da3188e21f7 Mon Sep 17 00:00:00 2001 From: Paul Schultz Date: Mon, 8 May 2023 08:39:51 -0500 Subject: [PATCH 17/17] update changeset Signed-off-by: Paul Schultz --- .changeset/curly-rats-fold.md | 149 +--------------------------------- 1 file changed, 1 insertion(+), 148 deletions(-) diff --git a/.changeset/curly-rats-fold.md b/.changeset/curly-rats-fold.md index 072f3dcc6c..c4cd8d514b 100644 --- a/.changeset/curly-rats-fold.md +++ b/.changeset/curly-rats-fold.md @@ -2,151 +2,4 @@ '@backstage/plugin-catalog-backend': patch --- -- Internal refactoring for performance in the service handlers - -```diff -diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts -index ba165f96af..2023c19e13 100644 ---- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts -+++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts -@@ -14,11 +14,7 @@ - * limitations under the License. - */ - --import { -- Entity, -- parseEntityRef, -- stringifyEntityRef, --} from '@backstage/catalog-model'; -+import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; - import { InputError, NotFoundError } from '@backstage/errors'; - import { Knex } from 'knex'; - import { isEqual, chunk as lodashChunk } from 'lodash'; -@@ -64,43 +60,47 @@ const defaultSortField: EntityOrder = { - - const DEFAULT_LIMIT = 20; - --function parsePagination(input?: EntityPagination): { -- limit?: number; -- offset?: number; --} { -+function parsePagination(input?: EntityPagination): EntityPagination { - if (!input) { - return {}; - } - - let { limit, offset } = input; - -- if (input.after !== undefined) { -- let cursor; -- try { -- const json = Buffer.from(input.after, 'base64').toString('utf8'); -- cursor = JSON.parse(json); -- } catch { -- throw new InputError('Malformed after cursor, could not be parsed'); -- } -- if (cursor.limit !== undefined) { -- if (!Number.isInteger(cursor.limit)) { -- throw new InputError('Malformed after cursor, limit was not an number'); -- } -- limit = cursor.limit; -+ if (input.after === undefined) { -+ return { limit, offset }; -+ } -+ -+ let cursor; -+ try { -+ const json = Buffer.from(input.after, 'base64').toString('utf8'); -+ cursor = JSON.parse(json); -+ } catch { -+ throw new InputError('Malformed after cursor, could not be parsed'); -+ } -+ -+ if (cursor.limit !== undefined) { -+ if (!Number.isInteger(cursor.limit)) { -+ throw new InputError('Malformed after cursor, limit was not an number'); - } -- if (cursor.offset !== undefined) { -- if (!Number.isInteger(cursor.offset)) { -- throw new InputError('Malformed after cursor, offset was not a number'); -- } -- offset = cursor.offset; -+ limit = cursor.limit; -+ } -+ -+ if (cursor.offset !== undefined) { -+ if (!Number.isInteger(cursor.offset)) { -+ throw new InputError('Malformed after cursor, offset was not a number'); - } -+ offset = cursor.offset; - } - - return { limit, offset }; - } - --function stringifyPagination(input: { limit: number; offset: number }) { -- const json = JSON.stringify({ limit: input.limit, offset: input.offset }); -+function stringifyPagination( -+ input: Required>, -+): string { -+ const { limit, offset } = input; -+ const json = JSON.stringify({ limit, offset }); - const base64 = Buffer.from(json, 'utf8').toString('base64'); - return base64; - } -@@ -111,24 +111,21 @@ function addCondition( - filter: EntitiesSearchFilter, - negate: boolean = false, - entityIdField = 'entity_id', --) { -+): void { -+ const key = filter.key.toLowerCase(); -+ const values = filter.values?.map(v => v.toLowerCase()); -+ - // 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') - .select('search.entity_id') -- .where({ key: filter.key.toLowerCase() }) -+ .where({ key }) - .andWhere(function keyFilter() { -- if (filter.values) { -- if (filter.values.length === 1) { -- this.where({ value: filter.values[0].toLowerCase() }); -- } else { -- this.andWhere( -- 'value', -- 'in', -- filter.values.map(v => v.toLowerCase()), -- ); -- } -+ if (values?.length === 1) { -+ this.where({ value: values.at(0) }); -+ } else if (values) { -+ this.andWhere('value', 'in', values); - } - }); - queryBuilder.andWhere(entityIdField, negate ? 'not in' : 'in', matchQuery); -@@ -159,16 +156,16 @@ function parseFilter( - negate: boolean = false, - entityIdField = 'entity_id', - ): Knex.QueryBuilder { -+ if (isNegationEntityFilter(filter)) { -+ return parseFilter(filter.not, query, db, !negate, entityIdField); -+ } -+ - if (isEntitiesSearchFilter(filter)) { - return query.andWhere(function filterFunction() { - addCondition(this, db, filter, negate, entityIdField); - }); - } - -- if (isNegationEntityFilter(filter)) { -- return parseFilter(filter.not, query, db, !negate, entityIdField); -- } -- - return query[negate ? 'andWhereNot' : 'andWhere'](function filterFunction() { - if (isOrEntityFilter(filter)) { - for (const subFilter of filter.anyOf ?? []) { -``` +Internal refactoring for performance in the service handlers