@backstage/plugin-catalog-backend 1.28.0 → 1.28.1-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # @backstage/plugin-catalog-backend
2
2
 
3
+ ## 1.28.1-next.0
4
+
5
+ ### Patch Changes
6
+
7
+ - f159b25: Compute deltas more efficiently, which generally leads to less wasted processing cycles
8
+ - 56511ba: Be more aggressive in dequeueing entities for stitching
9
+ - 71152e3: Correctly report stitching queue length
10
+ - 3ab57c6: Support changing location keys on existing entities, in delta mutations
11
+ - 2924ffe: Compute some metrics using search table facet aggregations instead of reading the full refresh state
12
+ - Updated dependencies
13
+ - @backstage/integration@1.16.0-next.0
14
+ - @backstage/backend-plugin-api@1.0.3-next.0
15
+ - @backstage/plugin-search-backend-module-catalog@0.2.6-next.0
16
+ - @backstage/plugin-events-node@0.4.6-next.0
17
+ - @backstage/catalog-client@1.8.1-next.0
18
+ - @backstage/backend-openapi-utils@0.3.1-next.0
19
+ - @backstage/catalog-model@1.7.1
20
+ - @backstage/config@1.3.0
21
+ - @backstage/errors@1.2.5
22
+ - @backstage/types@1.2.0
23
+ - @backstage/plugin-catalog-common@1.1.1
24
+ - @backstage/plugin-catalog-node@1.14.1-next.0
25
+ - @backstage/plugin-permission-common@0.8.2
26
+ - @backstage/plugin-permission-node@0.8.6-next.0
27
+
3
28
  ## 1.28.0
4
29
 
5
30
  ### Minor Changes
@@ -145,14 +145,36 @@ class DefaultProviderDatabase {
145
145
  }
146
146
  async createDelta(tx, options) {
147
147
  if (options.type === "delta") {
148
- return {
149
- toAdd: [],
150
- toUpsert: options.added.map((e) => ({
151
- deferred: e,
152
- hash: util.generateStableHash(e.entity)
153
- })),
154
- toRemove: options.removed.map((e) => e.entityRef)
155
- };
148
+ const toAdd2 = new Array();
149
+ const toUpsert2 = new Array();
150
+ const toRemove2 = options.removed.map((e) => e.entityRef);
151
+ for (const chunk of lodash__default.default.chunk(options.added, 1e3)) {
152
+ const entityRefs = chunk.map((e) => catalogModel.stringifyEntityRef(e.entity));
153
+ const rows = await tx("refresh_state").select(["entity_ref", "unprocessed_hash", "location_key"]).whereIn("entity_ref", entityRefs);
154
+ const oldStates = new Map(
155
+ rows.map((row) => [
156
+ row.entity_ref,
157
+ {
158
+ unprocessed_hash: row.unprocessed_hash,
159
+ location_key: row.location_key
160
+ }
161
+ ])
162
+ );
163
+ chunk.forEach((deferred, i) => {
164
+ const entityRef = entityRefs[i];
165
+ const newHash = util.generateStableHash(deferred.entity);
166
+ const oldState = oldStates.get(entityRef);
167
+ if (oldState === void 0) {
168
+ toAdd2.push({ deferred, hash: newHash });
169
+ } else if ((deferred.locationKey ?? null) !== (oldState.location_key ?? null)) {
170
+ toRemove2.push(entityRef);
171
+ toAdd2.push({ deferred, hash: newHash });
172
+ } else if (newHash !== oldState.unprocessed_hash) {
173
+ toUpsert2.push({ deferred, hash: newHash });
174
+ }
175
+ });
176
+ }
177
+ return { toAdd: toAdd2, toUpsert: toUpsert2, toRemove: toRemove2 };
156
178
  }
157
179
  const oldRefs = await tx(
158
180
  "refresh_state_references"
@@ -186,7 +208,7 @@ class DefaultProviderDatabase {
186
208
  const upsertItem = { deferred: item.deferred, hash: item.hash };
187
209
  if (!oldRef) {
188
210
  toAdd.push(upsertItem);
189
- } else if ((oldRef?.locationKey ?? void 0) !== (item.deferred.locationKey ?? void 0)) {
211
+ } else if ((oldRef.locationKey ?? void 0) !== (item.deferred.locationKey ?? void 0)) {
190
212
  toRemove.push(item.ref);
191
213
  toAdd.push(upsertItem);
192
214
  } else if (oldRef.oldEntityHash !== item.hash) {
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultProviderDatabase.cjs.js","sources":["../../src/database/DefaultProviderDatabase.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { DeferredEntity } from '@backstage/plugin-catalog-node';\nimport { Knex } from 'knex';\nimport lodash from 'lodash';\nimport { v4 as uuid } from 'uuid';\nimport { rethrowError } from './conversion';\nimport { deleteWithEagerPruningOfChildren } from './operations/provider/deleteWithEagerPruningOfChildren';\nimport { refreshByRefreshKeys } from './operations/provider/refreshByRefreshKeys';\nimport { checkLocationKeyConflict } from './operations/refreshState/checkLocationKeyConflict';\nimport { insertUnprocessedEntity } from './operations/refreshState/insertUnprocessedEntity';\nimport { updateUnprocessedEntity } from './operations/refreshState/updateUnprocessedEntity';\nimport { DbRefreshStateReferencesRow, DbRefreshStateRow } from './tables';\nimport {\n ProviderDatabase,\n RefreshByKeyOptions,\n ReplaceUnprocessedEntitiesOptions,\n Transaction,\n} from './types';\nimport { generateStableHash } from './util';\nimport {\n LoggerService,\n isDatabaseConflictError,\n} from '@backstage/backend-plugin-api';\n\n// The number of items that are sent per batch to the database layer, when\n// doing .batchInsert calls to knex. This needs to be low enough to not cause\n// errors in the underlying engine due to exceeding query limits, but large\n// enough to get the speed benefits.\nconst BATCH_SIZE = 50;\n\nexport class DefaultProviderDatabase implements ProviderDatabase {\n constructor(\n private readonly options: {\n database: Knex;\n logger: LoggerService;\n },\n ) {}\n\n async transaction<T>(fn: (tx: Transaction) => Promise<T>): Promise<T> {\n try {\n let result: T | undefined = undefined;\n await this.options.database.transaction(\n async tx => {\n // We can't return here, as knex swallows the return type in case the\n // transaction is rolled back:\n // https://github.com/knex/knex/blob/e37aeaa31c8ef9c1b07d2e4d3ec6607e557d800d/lib/transaction.js#L136\n result = await fn(tx);\n },\n {\n // If we explicitly trigger a rollback, don't fail.\n doNotRejectOnRollback: true,\n },\n );\n return result!;\n } catch (e) {\n this.options.logger.debug(`Error during transaction, ${e}`);\n throw rethrowError(e);\n }\n }\n\n async replaceUnprocessedEntities(\n txOpaque: Transaction,\n options: ReplaceUnprocessedEntitiesOptions,\n ): Promise<void> {\n const tx = txOpaque as Knex.Transaction;\n const { toAdd, toUpsert, toRemove } = await this.createDelta(tx, options);\n\n if (toRemove.length) {\n const removedCount = await deleteWithEagerPruningOfChildren({\n knex: tx,\n entityRefs: toRemove,\n sourceKey: options.sourceKey,\n });\n this.options.logger.debug(\n `removed, ${removedCount} entities: ${JSON.stringify(toRemove)}`,\n );\n }\n\n if (toAdd.length) {\n // The reason for this chunking, rather than just massively batch\n // inserting the entire payload, is that we fall back to the individual\n // upsert mechanism below on conflicts. That path is massively slower than\n // the fast batch path, so we don't want to end up accidentally having to\n // for example item-by-item upsert tens of thousands of entities in a\n // large initial delivery dump. The implication is that the size of these\n // chunks needs to weigh the benefit of fast successful inserts, against\n // the drawback of super slow but more rare fallbacks. There's quickly\n // diminishing returns though with turning up this value way high.\n for (const chunk of lodash.chunk(toAdd, 50)) {\n try {\n await tx.batchInsert(\n 'refresh_state',\n chunk.map(item => ({\n entity_id: uuid(),\n entity_ref: stringifyEntityRef(item.deferred.entity),\n unprocessed_entity: JSON.stringify(item.deferred.entity),\n unprocessed_hash: item.hash,\n errors: '',\n location_key: item.deferred.locationKey,\n next_update_at: tx.fn.now(),\n last_discovery_at: tx.fn.now(),\n })),\n BATCH_SIZE,\n );\n await tx.batchInsert(\n 'refresh_state_references',\n chunk.map(item => ({\n source_key: options.sourceKey,\n target_entity_ref: stringifyEntityRef(item.deferred.entity),\n })),\n BATCH_SIZE,\n );\n } catch (error) {\n if (!isDatabaseConflictError(error)) {\n throw error;\n } else {\n this.options.logger.debug(\n `Fast insert path failed, falling back to slow path, ${error}`,\n );\n toUpsert.push(...chunk);\n }\n }\n }\n }\n\n if (toUpsert.length) {\n for (const {\n deferred: { entity, locationKey },\n hash,\n } of toUpsert) {\n const entityRef = stringifyEntityRef(entity);\n\n try {\n let ok = await updateUnprocessedEntity({\n tx,\n entity,\n hash,\n locationKey,\n });\n if (!ok) {\n ok = await insertUnprocessedEntity({\n tx,\n entity,\n hash,\n locationKey,\n logger: this.options.logger,\n });\n }\n\n await tx<DbRefreshStateReferencesRow>('refresh_state_references')\n .where('target_entity_ref', entityRef)\n .andWhere({ source_key: options.sourceKey })\n .delete();\n\n if (ok) {\n await tx<DbRefreshStateReferencesRow>(\n 'refresh_state_references',\n ).insert({\n source_key: options.sourceKey,\n target_entity_ref: entityRef,\n });\n } else {\n const conflictingKey = await checkLocationKeyConflict({\n tx,\n entityRef,\n locationKey,\n });\n if (conflictingKey) {\n this.options.logger.warn(\n `Source ${options.sourceKey} detected conflicting entityRef ${entityRef} already referenced by ${conflictingKey} and now also ${locationKey}`,\n );\n }\n }\n } catch (error) {\n this.options.logger.error(\n `Failed to add '${entityRef}' from source '${options.sourceKey}', ${error}`,\n );\n }\n }\n }\n }\n\n async refreshByRefreshKeys(\n txOpaque: Transaction,\n options: RefreshByKeyOptions,\n ) {\n const tx = txOpaque as Knex.Transaction;\n await refreshByRefreshKeys({ tx, keys: options.keys });\n }\n\n private async createDelta(\n tx: Knex.Transaction,\n options: ReplaceUnprocessedEntitiesOptions,\n ): Promise<{\n toAdd: { deferred: DeferredEntity; hash: string }[];\n toUpsert: { deferred: DeferredEntity; hash: string }[];\n toRemove: string[];\n }> {\n if (options.type === 'delta') {\n return {\n toAdd: [],\n toUpsert: options.added.map(e => ({\n deferred: e,\n hash: generateStableHash(e.entity),\n })),\n toRemove: options.removed.map(e => e.entityRef),\n };\n }\n\n // Grab all of the existing references from the same source, and their locationKeys as well\n const oldRefs = await tx<DbRefreshStateReferencesRow>(\n 'refresh_state_references',\n )\n .leftJoin<DbRefreshStateRow>('refresh_state', {\n target_entity_ref: 'entity_ref',\n })\n .where({ source_key: options.sourceKey })\n .select({\n target_entity_ref: 'refresh_state_references.target_entity_ref',\n location_key: 'refresh_state.location_key',\n unprocessed_hash: 'refresh_state.unprocessed_hash',\n });\n\n const items = options.items.map(deferred => ({\n deferred,\n ref: stringifyEntityRef(deferred.entity),\n hash: generateStableHash(deferred.entity),\n }));\n\n const oldRefsSet = new Map(\n oldRefs.map(r => [\n r.target_entity_ref,\n {\n locationKey: r.location_key,\n oldEntityHash: r.unprocessed_hash,\n },\n ]),\n );\n const newRefsSet = new Set(items.map(item => item.ref));\n\n const toAdd = new Array<{ deferred: DeferredEntity; hash: string }>();\n const toUpsert = new Array<{ deferred: DeferredEntity; hash: string }>();\n const toRemove = oldRefs\n .map(row => row.target_entity_ref)\n .filter(ref => !newRefsSet.has(ref));\n\n for (const item of items) {\n const oldRef = oldRefsSet.get(item.ref);\n const upsertItem = { deferred: item.deferred, hash: item.hash };\n if (!oldRef) {\n // Add any entity that does not exist in the database\n toAdd.push(upsertItem);\n } else if (\n (oldRef?.locationKey ?? undefined) !==\n (item.deferred.locationKey ?? undefined)\n ) {\n // Remove and then re-add any entity that exists, but with a different location key\n toRemove.push(item.ref);\n toAdd.push(upsertItem);\n } else if (oldRef.oldEntityHash !== item.hash) {\n // Entities with modifications should be pushed through too\n toUpsert.push(upsertItem);\n }\n }\n\n return { toAdd, toUpsert, toRemove };\n }\n}\n"],"names":["rethrowError","deleteWithEagerPruningOfChildren","lodash","uuid","stringifyEntityRef","isDatabaseConflictError","updateUnprocessedEntity","insertUnprocessedEntity","checkLocationKeyConflict","refreshByRefreshKeys","generateStableHash"],"mappings":"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"}
1
+ {"version":3,"file":"DefaultProviderDatabase.cjs.js","sources":["../../src/database/DefaultProviderDatabase.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { DeferredEntity } from '@backstage/plugin-catalog-node';\nimport { Knex } from 'knex';\nimport lodash from 'lodash';\nimport { v4 as uuid } from 'uuid';\nimport { rethrowError } from './conversion';\nimport { deleteWithEagerPruningOfChildren } from './operations/provider/deleteWithEagerPruningOfChildren';\nimport { refreshByRefreshKeys } from './operations/provider/refreshByRefreshKeys';\nimport { checkLocationKeyConflict } from './operations/refreshState/checkLocationKeyConflict';\nimport { insertUnprocessedEntity } from './operations/refreshState/insertUnprocessedEntity';\nimport { updateUnprocessedEntity } from './operations/refreshState/updateUnprocessedEntity';\nimport { DbRefreshStateReferencesRow, DbRefreshStateRow } from './tables';\nimport {\n ProviderDatabase,\n RefreshByKeyOptions,\n ReplaceUnprocessedEntitiesOptions,\n Transaction,\n} from './types';\nimport { generateStableHash } from './util';\nimport {\n LoggerService,\n isDatabaseConflictError,\n} from '@backstage/backend-plugin-api';\n\n// The number of items that are sent per batch to the database layer, when\n// doing .batchInsert calls to knex. This needs to be low enough to not cause\n// errors in the underlying engine due to exceeding query limits, but large\n// enough to get the speed benefits.\nconst BATCH_SIZE = 50;\n\nexport class DefaultProviderDatabase implements ProviderDatabase {\n constructor(\n private readonly options: {\n database: Knex;\n logger: LoggerService;\n },\n ) {}\n\n async transaction<T>(fn: (tx: Transaction) => Promise<T>): Promise<T> {\n try {\n let result: T | undefined = undefined;\n await this.options.database.transaction(\n async tx => {\n // We can't return here, as knex swallows the return type in case the\n // transaction is rolled back:\n // https://github.com/knex/knex/blob/e37aeaa31c8ef9c1b07d2e4d3ec6607e557d800d/lib/transaction.js#L136\n result = await fn(tx);\n },\n {\n // If we explicitly trigger a rollback, don't fail.\n doNotRejectOnRollback: true,\n },\n );\n return result!;\n } catch (e) {\n this.options.logger.debug(`Error during transaction, ${e}`);\n throw rethrowError(e);\n }\n }\n\n async replaceUnprocessedEntities(\n txOpaque: Knex | Transaction,\n options: ReplaceUnprocessedEntitiesOptions,\n ): Promise<void> {\n const tx = txOpaque as Knex | Knex.Transaction;\n const { toAdd, toUpsert, toRemove } = await this.createDelta(tx, options);\n\n if (toRemove.length) {\n const removedCount = await deleteWithEagerPruningOfChildren({\n knex: tx,\n entityRefs: toRemove,\n sourceKey: options.sourceKey,\n });\n this.options.logger.debug(\n `removed, ${removedCount} entities: ${JSON.stringify(toRemove)}`,\n );\n }\n\n if (toAdd.length) {\n // The reason for this chunking, rather than just massively batch\n // inserting the entire payload, is that we fall back to the individual\n // upsert mechanism below on conflicts. That path is massively slower than\n // the fast batch path, so we don't want to end up accidentally having to\n // for example item-by-item upsert tens of thousands of entities in a\n // large initial delivery dump. The implication is that the size of these\n // chunks needs to weigh the benefit of fast successful inserts, against\n // the drawback of super slow but more rare fallbacks. There's quickly\n // diminishing returns though with turning up this value way high.\n for (const chunk of lodash.chunk(toAdd, 50)) {\n try {\n await tx.batchInsert(\n 'refresh_state',\n chunk.map(item => ({\n entity_id: uuid(),\n entity_ref: stringifyEntityRef(item.deferred.entity),\n unprocessed_entity: JSON.stringify(item.deferred.entity),\n unprocessed_hash: item.hash,\n errors: '',\n location_key: item.deferred.locationKey,\n next_update_at: tx.fn.now(),\n last_discovery_at: tx.fn.now(),\n })),\n BATCH_SIZE,\n );\n await tx.batchInsert(\n 'refresh_state_references',\n chunk.map(item => ({\n source_key: options.sourceKey,\n target_entity_ref: stringifyEntityRef(item.deferred.entity),\n })),\n BATCH_SIZE,\n );\n } catch (error) {\n if (!isDatabaseConflictError(error)) {\n throw error;\n } else {\n this.options.logger.debug(\n `Fast insert path failed, falling back to slow path, ${error}`,\n );\n toUpsert.push(...chunk);\n }\n }\n }\n }\n\n if (toUpsert.length) {\n for (const {\n deferred: { entity, locationKey },\n hash,\n } of toUpsert) {\n const entityRef = stringifyEntityRef(entity);\n\n try {\n let ok = await updateUnprocessedEntity({\n tx,\n entity,\n hash,\n locationKey,\n });\n if (!ok) {\n ok = await insertUnprocessedEntity({\n tx,\n entity,\n hash,\n locationKey,\n logger: this.options.logger,\n });\n }\n\n await tx<DbRefreshStateReferencesRow>('refresh_state_references')\n .where('target_entity_ref', entityRef)\n .andWhere({ source_key: options.sourceKey })\n .delete();\n\n if (ok) {\n await tx<DbRefreshStateReferencesRow>(\n 'refresh_state_references',\n ).insert({\n source_key: options.sourceKey,\n target_entity_ref: entityRef,\n });\n } else {\n const conflictingKey = await checkLocationKeyConflict({\n tx,\n entityRef,\n locationKey,\n });\n if (conflictingKey) {\n this.options.logger.warn(\n `Source ${options.sourceKey} detected conflicting entityRef ${entityRef} already referenced by ${conflictingKey} and now also ${locationKey}`,\n );\n }\n }\n } catch (error) {\n this.options.logger.error(\n `Failed to add '${entityRef}' from source '${options.sourceKey}', ${error}`,\n );\n }\n }\n }\n }\n\n async refreshByRefreshKeys(\n txOpaque: Transaction,\n options: RefreshByKeyOptions,\n ) {\n const tx = txOpaque as Knex.Transaction;\n await refreshByRefreshKeys({ tx, keys: options.keys });\n }\n\n private async createDelta(\n tx: Knex | Knex.Transaction,\n options: ReplaceUnprocessedEntitiesOptions,\n ): Promise<{\n toAdd: { deferred: DeferredEntity; hash: string }[];\n toUpsert: { deferred: DeferredEntity; hash: string }[];\n toRemove: string[];\n }> {\n if (options.type === 'delta') {\n const toAdd = new Array<{ deferred: DeferredEntity; hash: string }>();\n const toUpsert = new Array<{ deferred: DeferredEntity; hash: string }>();\n const toRemove = options.removed.map(e => e.entityRef);\n\n for (const chunk of lodash.chunk(options.added, 1000)) {\n const entityRefs = chunk.map(e => stringifyEntityRef(e.entity));\n const rows = await tx<DbRefreshStateRow>('refresh_state')\n .select(['entity_ref', 'unprocessed_hash', 'location_key'])\n .whereIn('entity_ref', entityRefs);\n const oldStates = new Map(\n rows.map(row => [\n row.entity_ref,\n {\n unprocessed_hash: row.unprocessed_hash,\n location_key: row.location_key,\n },\n ]),\n );\n\n chunk.forEach((deferred, i) => {\n const entityRef = entityRefs[i];\n const newHash = generateStableHash(deferred.entity);\n const oldState = oldStates.get(entityRef);\n if (oldState === undefined) {\n // Add any entity that does not exist in the database\n toAdd.push({ deferred, hash: newHash });\n } else if (\n (deferred.locationKey ?? null) !== (oldState.location_key ?? null)\n ) {\n // Remove and then re-add any entity that exists, but with a different location key\n toRemove.push(entityRef);\n toAdd.push({ deferred, hash: newHash });\n } else if (newHash !== oldState.unprocessed_hash) {\n // Entities with modifications should be pushed through too\n toUpsert.push({ deferred, hash: newHash });\n }\n });\n }\n\n return { toAdd, toUpsert, toRemove };\n }\n\n // Grab all of the existing references from the same source, and their locationKeys as well\n const oldRefs = await tx<DbRefreshStateReferencesRow>(\n 'refresh_state_references',\n )\n .leftJoin<DbRefreshStateRow>('refresh_state', {\n target_entity_ref: 'entity_ref',\n })\n .where({ source_key: options.sourceKey })\n .select({\n target_entity_ref: 'refresh_state_references.target_entity_ref',\n location_key: 'refresh_state.location_key',\n unprocessed_hash: 'refresh_state.unprocessed_hash',\n });\n\n const items = options.items.map(deferred => ({\n deferred,\n ref: stringifyEntityRef(deferred.entity),\n hash: generateStableHash(deferred.entity),\n }));\n\n const oldRefsSet = new Map(\n oldRefs.map(r => [\n r.target_entity_ref,\n {\n locationKey: r.location_key,\n oldEntityHash: r.unprocessed_hash,\n },\n ]),\n );\n const newRefsSet = new Set(items.map(item => item.ref));\n\n const toAdd = new Array<{ deferred: DeferredEntity; hash: string }>();\n const toUpsert = new Array<{ deferred: DeferredEntity; hash: string }>();\n const toRemove = oldRefs\n .map(row => row.target_entity_ref)\n .filter(ref => !newRefsSet.has(ref));\n\n for (const item of items) {\n const oldRef = oldRefsSet.get(item.ref);\n const upsertItem = { deferred: item.deferred, hash: item.hash };\n if (!oldRef) {\n // Add any entity that does not exist in the database\n toAdd.push(upsertItem);\n } else if (\n (oldRef.locationKey ?? undefined) !==\n (item.deferred.locationKey ?? undefined)\n ) {\n // Remove and then re-add any entity that exists, but with a different location key\n toRemove.push(item.ref);\n toAdd.push(upsertItem);\n } else if (oldRef.oldEntityHash !== item.hash) {\n // Entities with modifications should be pushed through too\n toUpsert.push(upsertItem);\n }\n }\n\n return { toAdd, toUpsert, toRemove };\n }\n}\n"],"names":["rethrowError","deleteWithEagerPruningOfChildren","lodash","uuid","stringifyEntityRef","isDatabaseConflictError","updateUnprocessedEntity","insertUnprocessedEntity","checkLocationKeyConflict","refreshByRefreshKeys","toAdd","toUpsert","toRemove","generateStableHash"],"mappings":"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"}
@@ -2,7 +2,6 @@
2
2
 
3
3
  var metrics = require('../util/metrics.cjs.js');
4
4
  var api = require('@opentelemetry/api');
5
- var catalogModel = require('@backstage/catalog-model');
6
5
 
7
6
  function initDatabaseMetrics(knex) {
8
7
  const seenProm = /* @__PURE__ */ new Set();
@@ -14,18 +13,15 @@ function initDatabaseMetrics(knex) {
14
13
  help: "Total amount of entities in the catalog. DEPRECATED: Please use opentelemetry metrics instead.",
15
14
  labelNames: ["kind"],
16
15
  async collect() {
17
- const result = await knex("refresh_state").select(
18
- "entity_ref"
19
- );
20
- const results = result.map((row) => row.entity_ref.split(":")[0]).reduce((acc, e) => acc.set(e, (acc.get(e) || 0) + 1), /* @__PURE__ */ new Map());
21
- results.forEach((value, key) => {
22
- seenProm.add(key);
23
- this.set({ kind: key }, value);
16
+ const results = await knex("search").where("key", "=", "kind").whereNotNull("value").select({ kind: "value", count: knex.raw("count(*)") }).groupBy("value");
17
+ results.forEach(({ kind, count }) => {
18
+ seenProm.add(kind);
19
+ this.set({ kind }, Number(count));
24
20
  });
25
- seenProm.forEach((key) => {
26
- if (!results.has(key)) {
27
- this.set({ kind: key }, 0);
28
- seenProm.delete(key);
21
+ seenProm.forEach((kind) => {
22
+ if (!results.some((r) => r.kind === kind)) {
23
+ this.set({ kind }, 0);
24
+ seenProm.delete(kind);
29
25
  }
30
26
  });
31
27
  }
@@ -53,18 +49,15 @@ function initDatabaseMetrics(knex) {
53
49
  entities_count: meter.createObservableGauge("catalog_entities_count", {
54
50
  description: "Total amount of entities in the catalog"
55
51
  }).addCallback(async (gauge) => {
56
- const result = await knex("refresh_state").select(
57
- "entity_ref"
58
- );
59
- const results = result.map((row) => catalogModel.parseEntityRef(row.entity_ref).kind).reduce((acc, e) => acc.set(e, (acc.get(e) || 0) + 1), /* @__PURE__ */ new Map());
60
- results.forEach((value, key) => {
61
- seen.add(key);
62
- gauge.observe(value, { kind: key });
52
+ const results = await knex("search").where("key", "=", "kind").whereNotNull("value").select({ kind: "value", count: knex.raw("count(*)") }).groupBy("value");
53
+ results.forEach(({ kind, count }) => {
54
+ seen.add(kind);
55
+ gauge.observe(Number(count), { kind });
63
56
  });
64
- seen.forEach((key) => {
65
- if (!results.has(key)) {
66
- gauge.observe(0, { kind: key });
67
- seen.delete(key);
57
+ seen.forEach((kind) => {
58
+ if (!results.some((r) => r.kind === kind)) {
59
+ gauge.observe(0, { kind });
60
+ seen.delete(kind);
68
61
  }
69
62
  });
70
63
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.cjs.js","sources":["../../src/database/metrics.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Knex } from 'knex';\nimport { createGaugeMetric } from '../util/metrics';\nimport { DbRefreshStateRow, DbRelationsRow, DbLocationsRow } from './tables';\nimport { metrics } from '@opentelemetry/api';\nimport { parseEntityRef } from '@backstage/catalog-model';\n\nexport function initDatabaseMetrics(knex: Knex) {\n const seenProm = new Set<string>();\n const seen = new Set<string>();\n const meter = metrics.getMeter('default');\n return {\n entities_count_prom: createGaugeMetric({\n name: 'catalog_entities_count',\n help: 'Total amount of entities in the catalog. DEPRECATED: Please use opentelemetry metrics instead.',\n labelNames: ['kind'],\n async collect() {\n const result = await knex<DbRefreshStateRow>('refresh_state').select(\n 'entity_ref',\n );\n const results = result\n .map(row => row.entity_ref.split(':')[0])\n .reduce((acc, e) => acc.set(e, (acc.get(e) || 0) + 1), new Map());\n\n results.forEach((value, key) => {\n seenProm.add(key);\n this.set({ kind: key }, value);\n });\n\n // Set all the entities that were not seenProm to 0 and delete them from the seenProm set.\n seenProm.forEach(key => {\n if (!results.has(key)) {\n this.set({ kind: key }, 0);\n seenProm.delete(key);\n }\n });\n },\n }),\n registered_locations_prom: createGaugeMetric({\n name: 'catalog_registered_locations_count',\n help: 'Total amount of registered locations in the catalog. DEPRECATED: Please use opentelemetry metrics instead.',\n async collect() {\n const total = await knex<DbLocationsRow>('locations').count({\n count: '*',\n });\n this.set(Number(total[0].count));\n },\n }),\n relations_prom: createGaugeMetric({\n name: 'catalog_relations_count',\n help: 'Total amount of relations between entities. DEPRECATED: Please use opentelemetry metrics instead.',\n async collect() {\n const total = await knex<DbRelationsRow>('relations').count({\n count: '*',\n });\n this.set(Number(total[0].count));\n },\n }),\n entities_count: meter\n .createObservableGauge('catalog_entities_count', {\n description: 'Total amount of entities in the catalog',\n })\n .addCallback(async gauge => {\n const result = await knex<DbRefreshStateRow>('refresh_state').select(\n 'entity_ref',\n );\n const results = result\n .map(row => parseEntityRef(row.entity_ref).kind)\n .reduce((acc, e) => acc.set(e, (acc.get(e) || 0) + 1), new Map());\n\n results.forEach((value, key) => {\n seen.add(key);\n gauge.observe(value, { kind: key });\n });\n\n // Set all the entities that were not seen to 0 and delete them from the seen set.\n seen.forEach(key => {\n if (!results.has(key)) {\n gauge.observe(0, { kind: key });\n seen.delete(key);\n }\n });\n }),\n registered_locations: meter\n .createObservableGauge('catalog_registered_locations_count', {\n description: 'Total amount of registered locations in the catalog',\n })\n .addCallback(async gauge => {\n const total = await knex<DbLocationsRow>('locations').count({\n count: '*',\n });\n gauge.observe(Number(total[0].count));\n }),\n relations: meter\n .createObservableGauge('catalog_relations_count', {\n description: 'Total amount of relations between entities',\n })\n .addCallback(async gauge => {\n const total = await knex<DbRelationsRow>('relations').count({\n count: '*',\n });\n gauge.observe(Number(total[0].count));\n }),\n };\n}\n"],"names":["metrics","createGaugeMetric","parseEntityRef"],"mappings":"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"}
1
+ {"version":3,"file":"metrics.cjs.js","sources":["../../src/database/metrics.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Knex } from 'knex';\nimport { createGaugeMetric } from '../util/metrics';\nimport { DbRelationsRow, DbLocationsRow, DbSearchRow } from './tables';\nimport { metrics } from '@opentelemetry/api';\n\nexport function initDatabaseMetrics(knex: Knex) {\n const seenProm = new Set<string>();\n const seen = new Set<string>();\n const meter = metrics.getMeter('default');\n return {\n entities_count_prom: createGaugeMetric({\n name: 'catalog_entities_count',\n help: 'Total amount of entities in the catalog. DEPRECATED: Please use opentelemetry metrics instead.',\n labelNames: ['kind'],\n async collect() {\n const results = await knex<DbSearchRow>('search')\n .where('key', '=', 'kind')\n .whereNotNull('value')\n .select({ kind: 'value', count: knex.raw('count(*)') })\n .groupBy('value');\n\n results.forEach(({ kind, count }) => {\n seenProm.add(kind);\n this.set({ kind }, Number(count));\n });\n\n // Set all the entities that were not seenProm to 0 and delete them from the seenProm set.\n seenProm.forEach(kind => {\n if (!results.some(r => r.kind === kind)) {\n this.set({ kind }, 0);\n seenProm.delete(kind);\n }\n });\n },\n }),\n registered_locations_prom: createGaugeMetric({\n name: 'catalog_registered_locations_count',\n help: 'Total amount of registered locations in the catalog. DEPRECATED: Please use opentelemetry metrics instead.',\n async collect() {\n const total = await knex<DbLocationsRow>('locations').count({\n count: '*',\n });\n this.set(Number(total[0].count));\n },\n }),\n relations_prom: createGaugeMetric({\n name: 'catalog_relations_count',\n help: 'Total amount of relations between entities. DEPRECATED: Please use opentelemetry metrics instead.',\n async collect() {\n const total = await knex<DbRelationsRow>('relations').count({\n count: '*',\n });\n this.set(Number(total[0].count));\n },\n }),\n entities_count: meter\n .createObservableGauge('catalog_entities_count', {\n description: 'Total amount of entities in the catalog',\n })\n .addCallback(async gauge => {\n const results = await knex<DbSearchRow>('search')\n .where('key', '=', 'kind')\n .whereNotNull('value')\n .select({ kind: 'value', count: knex.raw('count(*)') })\n .groupBy('value');\n\n results.forEach(({ kind, count }) => {\n seen.add(kind);\n gauge.observe(Number(count), { kind });\n });\n\n // Set all the entities that were not seen to 0 and delete them from the seen set.\n seen.forEach(kind => {\n if (!results.some(r => r.kind === kind)) {\n gauge.observe(0, { kind });\n seen.delete(kind);\n }\n });\n }),\n registered_locations: meter\n .createObservableGauge('catalog_registered_locations_count', {\n description: 'Total amount of registered locations in the catalog',\n })\n .addCallback(async gauge => {\n const total = await knex<DbLocationsRow>('locations').count({\n count: '*',\n });\n gauge.observe(Number(total[0].count));\n }),\n relations: meter\n .createObservableGauge('catalog_relations_count', {\n description: 'Total amount of relations between entities',\n })\n .addCallback(async gauge => {\n const total = await knex<DbRelationsRow>('relations').count({\n count: '*',\n });\n gauge.observe(Number(total[0].count));\n }),\n };\n}\n"],"names":["metrics","createGaugeMetric"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"checkLocationKeyConflict.cjs.js","sources":["../../../../src/database/operations/refreshState/checkLocationKeyConflict.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Knex } from 'knex';\nimport { DbRefreshStateRow } from '../../tables';\n\n/**\n * Checks whether a refresh state exists for the given entity that has a\n * location key that does not match the provided location key.\n *\n * @returns The conflicting key if there is one.\n */\nexport async function checkLocationKeyConflict(options: {\n tx: Knex.Transaction;\n entityRef: string;\n locationKey?: string;\n}): Promise<string | undefined> {\n const { tx, entityRef, locationKey } = options;\n\n const row = await tx<DbRefreshStateRow>('refresh_state')\n .select('location_key')\n .where('entity_ref', entityRef)\n .first();\n\n const conflictingKey = row?.location_key;\n\n // If there's no existing key we can't have a conflict\n if (!conflictingKey) {\n return undefined;\n }\n\n if (conflictingKey !== locationKey) {\n return conflictingKey;\n }\n return undefined;\n}\n"],"names":[],"mappings":";;AAyBA,eAAsB,yBAAyB,OAIf,EAAA;AAC9B,EAAA,MAAM,EAAE,EAAA,EAAI,SAAW,EAAA,WAAA,EAAgB,GAAA,OAAA;AAEvC,EAAA,MAAM,GAAM,GAAA,MAAM,EAAsB,CAAA,eAAe,CACpD,CAAA,MAAA,CAAO,cAAc,CAAA,CACrB,KAAM,CAAA,YAAA,EAAc,SAAS,CAAA,CAC7B,KAAM,EAAA;AAET,EAAA,MAAM,iBAAiB,GAAK,EAAA,YAAA;AAG5B,EAAA,IAAI,CAAC,cAAgB,EAAA;AACnB,IAAO,OAAA,KAAA,CAAA;AAAA;AAGT,EAAA,IAAI,mBAAmB,WAAa,EAAA;AAClC,IAAO,OAAA,cAAA;AAAA;AAET,EAAO,OAAA,KAAA,CAAA;AACT;;;;"}
1
+ {"version":3,"file":"checkLocationKeyConflict.cjs.js","sources":["../../../../src/database/operations/refreshState/checkLocationKeyConflict.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Knex } from 'knex';\nimport { DbRefreshStateRow } from '../../tables';\n\n/**\n * Checks whether a refresh state exists for the given entity that has a\n * location key that does not match the provided location key.\n *\n * @returns The conflicting key if there is one.\n */\nexport async function checkLocationKeyConflict(options: {\n tx: Knex | Knex.Transaction;\n entityRef: string;\n locationKey?: string;\n}): Promise<string | undefined> {\n const { tx, entityRef, locationKey } = options;\n\n const row = await tx<DbRefreshStateRow>('refresh_state')\n .select('location_key')\n .where('entity_ref', entityRef)\n .first();\n\n const conflictingKey = row?.location_key;\n\n // If there's no existing key we can't have a conflict\n if (!conflictingKey) {\n return undefined;\n }\n\n if (conflictingKey !== locationKey) {\n return conflictingKey;\n }\n return undefined;\n}\n"],"names":[],"mappings":";;AAyBA,eAAsB,yBAAyB,OAIf,EAAA;AAC9B,EAAA,MAAM,EAAE,EAAA,EAAI,SAAW,EAAA,WAAA,EAAgB,GAAA,OAAA;AAEvC,EAAA,MAAM,GAAM,GAAA,MAAM,EAAsB,CAAA,eAAe,CACpD,CAAA,MAAA,CAAO,cAAc,CAAA,CACrB,KAAM,CAAA,YAAA,EAAc,SAAS,CAAA,CAC7B,KAAM,EAAA;AAET,EAAA,MAAM,iBAAiB,GAAK,EAAA,YAAA;AAG5B,EAAA,IAAI,CAAC,cAAgB,EAAA;AACnB,IAAO,OAAA,KAAA,CAAA;AAAA;AAGT,EAAA,IAAI,mBAAmB,WAAa,EAAA;AAClC,IAAO,OAAA,cAAA;AAAA;AAET,EAAO,OAAA,KAAA,CAAA;AACT;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"insertUnprocessedEntity.cjs.js","sources":["../../../../src/database/operations/refreshState/insertUnprocessedEntity.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Entity, stringifyEntityRef } from '@backstage/catalog-model';\nimport { Knex } from 'knex';\nimport { DbRefreshStateRow } from '../../tables';\nimport { v4 as uuid } from 'uuid';\nimport {\n LoggerService,\n isDatabaseConflictError,\n} from '@backstage/backend-plugin-api';\n\n/**\n * Attempts to insert a new refresh state row for the given entity, returning\n * true if successful and false if there was a conflict.\n */\nexport async function insertUnprocessedEntity(options: {\n tx: Knex.Transaction;\n entity: Entity;\n hash: string;\n locationKey?: string;\n logger: LoggerService;\n}): Promise<boolean> {\n const { tx, entity, hash, logger, locationKey } = options;\n\n const entityRef = stringifyEntityRef(entity);\n const serializedEntity = JSON.stringify(entity);\n\n try {\n let query = tx<DbRefreshStateRow>('refresh_state').insert({\n entity_id: uuid(),\n entity_ref: entityRef,\n unprocessed_entity: serializedEntity,\n unprocessed_hash: hash,\n errors: '',\n location_key: locationKey,\n next_update_at: tx.fn.now(),\n last_discovery_at: tx.fn.now(),\n });\n\n // TODO(Rugvip): only tested towards MySQL, Postgres and SQLite.\n // We have to do this because the only way to detect if there was a conflict with\n // SQLite is to catch the error, while Postgres needs to ignore the conflict to not\n // break the ongoing transaction.\n if (tx.client.config.client.includes('pg')) {\n query = query.onConflict('entity_ref').ignore() as any; // type here does not match runtime\n }\n\n // Postgres gives as an object with rowCount, SQLite gives us an array\n const result: { rowCount?: number; length?: number } = await query;\n return result.rowCount === 1 || result.length === 1;\n } catch (error) {\n // SQLite, or MySQL reached this rather than the rowCount check above\n if (!isDatabaseConflictError(error)) {\n throw error;\n } else {\n logger.debug(`Unable to insert a new refresh state row, ${error}`);\n return false;\n }\n }\n}\n"],"names":["stringifyEntityRef","uuid","isDatabaseConflictError"],"mappings":";;;;;;AA6BA,eAAsB,wBAAwB,OAMzB,EAAA;AACnB,EAAA,MAAM,EAAE,EAAI,EAAA,MAAA,EAAQ,IAAM,EAAA,MAAA,EAAQ,aAAgB,GAAA,OAAA;AAElD,EAAM,MAAA,SAAA,GAAYA,gCAAmB,MAAM,CAAA;AAC3C,EAAM,MAAA,gBAAA,GAAmB,IAAK,CAAA,SAAA,CAAU,MAAM,CAAA;AAE9C,EAAI,IAAA;AACF,IAAA,IAAI,KAAQ,GAAA,EAAA,CAAsB,eAAe,CAAA,CAAE,MAAO,CAAA;AAAA,MACxD,WAAWC,OAAK,EAAA;AAAA,MAChB,UAAY,EAAA,SAAA;AAAA,MACZ,kBAAoB,EAAA,gBAAA;AAAA,MACpB,gBAAkB,EAAA,IAAA;AAAA,MAClB,MAAQ,EAAA,EAAA;AAAA,MACR,YAAc,EAAA,WAAA;AAAA,MACd,cAAA,EAAgB,EAAG,CAAA,EAAA,CAAG,GAAI,EAAA;AAAA,MAC1B,iBAAA,EAAmB,EAAG,CAAA,EAAA,CAAG,GAAI;AAAA,KAC9B,CAAA;AAMD,IAAA,IAAI,GAAG,MAAO,CAAA,MAAA,CAAO,MAAO,CAAA,QAAA,CAAS,IAAI,CAAG,EAAA;AAC1C,MAAA,KAAA,GAAQ,KAAM,CAAA,UAAA,CAAW,YAAY,CAAA,CAAE,MAAO,EAAA;AAAA;AAIhD,IAAA,MAAM,SAAiD,MAAM,KAAA;AAC7D,IAAA,OAAO,MAAO,CAAA,QAAA,KAAa,CAAK,IAAA,MAAA,CAAO,MAAW,KAAA,CAAA;AAAA,WAC3C,KAAO,EAAA;AAEd,IAAI,IAAA,CAACC,wCAAwB,CAAA,KAAK,CAAG,EAAA;AACnC,MAAM,MAAA,KAAA;AAAA,KACD,MAAA;AACL,MAAO,MAAA,CAAA,KAAA,CAAM,CAA6C,0CAAA,EAAA,KAAK,CAAE,CAAA,CAAA;AACjE,MAAO,OAAA,KAAA;AAAA;AACT;AAEJ;;;;"}
1
+ {"version":3,"file":"insertUnprocessedEntity.cjs.js","sources":["../../../../src/database/operations/refreshState/insertUnprocessedEntity.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Entity, stringifyEntityRef } from '@backstage/catalog-model';\nimport { Knex } from 'knex';\nimport { DbRefreshStateRow } from '../../tables';\nimport { v4 as uuid } from 'uuid';\nimport {\n LoggerService,\n isDatabaseConflictError,\n} from '@backstage/backend-plugin-api';\n\n/**\n * Attempts to insert a new refresh state row for the given entity, returning\n * true if successful and false if there was a conflict.\n */\nexport async function insertUnprocessedEntity(options: {\n tx: Knex | Knex.Transaction;\n entity: Entity;\n hash: string;\n locationKey?: string;\n logger: LoggerService;\n}): Promise<boolean> {\n const { tx, entity, hash, logger, locationKey } = options;\n\n const entityRef = stringifyEntityRef(entity);\n const serializedEntity = JSON.stringify(entity);\n\n try {\n let query = tx<DbRefreshStateRow>('refresh_state').insert({\n entity_id: uuid(),\n entity_ref: entityRef,\n unprocessed_entity: serializedEntity,\n unprocessed_hash: hash,\n errors: '',\n location_key: locationKey,\n next_update_at: tx.fn.now(),\n last_discovery_at: tx.fn.now(),\n });\n\n // TODO(Rugvip): only tested towards MySQL, Postgres and SQLite.\n // We have to do this because the only way to detect if there was a conflict with\n // SQLite is to catch the error, while Postgres needs to ignore the conflict to not\n // break the ongoing transaction.\n if (tx.client.config.client.includes('pg')) {\n query = query.onConflict('entity_ref').ignore() as any; // type here does not match runtime\n }\n\n // Postgres gives as an object with rowCount, SQLite gives us an array\n const result: { rowCount?: number; length?: number } = await query;\n return result.rowCount === 1 || result.length === 1;\n } catch (error) {\n // SQLite, or MySQL reached this rather than the rowCount check above\n if (!isDatabaseConflictError(error)) {\n throw error;\n } else {\n logger.debug(`Unable to insert a new refresh state row, ${error}`);\n return false;\n }\n }\n}\n"],"names":["stringifyEntityRef","uuid","isDatabaseConflictError"],"mappings":";;;;;;AA6BA,eAAsB,wBAAwB,OAMzB,EAAA;AACnB,EAAA,MAAM,EAAE,EAAI,EAAA,MAAA,EAAQ,IAAM,EAAA,MAAA,EAAQ,aAAgB,GAAA,OAAA;AAElD,EAAM,MAAA,SAAA,GAAYA,gCAAmB,MAAM,CAAA;AAC3C,EAAM,MAAA,gBAAA,GAAmB,IAAK,CAAA,SAAA,CAAU,MAAM,CAAA;AAE9C,EAAI,IAAA;AACF,IAAA,IAAI,KAAQ,GAAA,EAAA,CAAsB,eAAe,CAAA,CAAE,MAAO,CAAA;AAAA,MACxD,WAAWC,OAAK,EAAA;AAAA,MAChB,UAAY,EAAA,SAAA;AAAA,MACZ,kBAAoB,EAAA,gBAAA;AAAA,MACpB,gBAAkB,EAAA,IAAA;AAAA,MAClB,MAAQ,EAAA,EAAA;AAAA,MACR,YAAc,EAAA,WAAA;AAAA,MACd,cAAA,EAAgB,EAAG,CAAA,EAAA,CAAG,GAAI,EAAA;AAAA,MAC1B,iBAAA,EAAmB,EAAG,CAAA,EAAA,CAAG,GAAI;AAAA,KAC9B,CAAA;AAMD,IAAA,IAAI,GAAG,MAAO,CAAA,MAAA,CAAO,MAAO,CAAA,QAAA,CAAS,IAAI,CAAG,EAAA;AAC1C,MAAA,KAAA,GAAQ,KAAM,CAAA,UAAA,CAAW,YAAY,CAAA,CAAE,MAAO,EAAA;AAAA;AAIhD,IAAA,MAAM,SAAiD,MAAM,KAAA;AAC7D,IAAA,OAAO,MAAO,CAAA,QAAA,KAAa,CAAK,IAAA,MAAA,CAAO,MAAW,KAAA,CAAA;AAAA,WAC3C,KAAO,EAAA;AAEd,IAAI,IAAA,CAACC,wCAAwB,CAAA,KAAK,CAAG,EAAA;AACnC,MAAM,MAAA,KAAA;AAAA,KACD,MAAA;AACL,MAAO,MAAA,CAAA,KAAA,CAAM,CAA6C,0CAAA,EAAA,KAAK,CAAE,CAAA,CAAA;AACjE,MAAO,OAAA,KAAA;AAAA;AACT;AAEJ;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"updateUnprocessedEntity.cjs.js","sources":["../../../../src/database/operations/refreshState/updateUnprocessedEntity.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Entity, stringifyEntityRef } from '@backstage/catalog-model';\nimport { Knex } from 'knex';\nimport { DbRefreshStateRow } from '../../tables';\n\n/**\n * Attempts to update an existing refresh state row, returning true if it was\n * updated and false if there was no entity with a matching ref and location key.\n *\n * Updating the entity will also cause it to be scheduled for immediate processing.\n */\nexport async function updateUnprocessedEntity(options: {\n tx: Knex.Transaction;\n entity: Entity;\n hash: string;\n locationKey?: string;\n}): Promise<boolean> {\n const { tx, entity, hash, locationKey } = options;\n\n const entityRef = stringifyEntityRef(entity);\n const serializedEntity = JSON.stringify(entity);\n\n const refreshResult = await tx<DbRefreshStateRow>('refresh_state')\n .update({\n unprocessed_entity: serializedEntity,\n unprocessed_hash: hash,\n location_key: locationKey,\n last_discovery_at: tx.fn.now(),\n // We only get to this point if a processed entity actually had any changes, or\n // if an entity provider requested this mutation, meaning that we can safely\n // bump the deferred entities to the front of the queue for immediate processing.\n next_update_at: tx.fn.now(),\n })\n .where('entity_ref', entityRef)\n .andWhere(inner => {\n if (!locationKey) {\n return inner.whereNull('location_key');\n }\n return inner\n .where('location_key', locationKey)\n .orWhereNull('location_key');\n });\n\n return refreshResult === 1;\n}\n"],"names":["stringifyEntityRef"],"mappings":";;;;AA0BA,eAAsB,wBAAwB,OAKzB,EAAA;AACnB,EAAA,MAAM,EAAE,EAAA,EAAI,MAAQ,EAAA,IAAA,EAAM,aAAgB,GAAA,OAAA;AAE1C,EAAM,MAAA,SAAA,GAAYA,gCAAmB,MAAM,CAAA;AAC3C,EAAM,MAAA,gBAAA,GAAmB,IAAK,CAAA,SAAA,CAAU,MAAM,CAAA;AAE9C,EAAA,MAAM,aAAgB,GAAA,MAAM,EAAsB,CAAA,eAAe,EAC9D,MAAO,CAAA;AAAA,IACN,kBAAoB,EAAA,gBAAA;AAAA,IACpB,gBAAkB,EAAA,IAAA;AAAA,IAClB,YAAc,EAAA,WAAA;AAAA,IACd,iBAAA,EAAmB,EAAG,CAAA,EAAA,CAAG,GAAI,EAAA;AAAA;AAAA;AAAA;AAAA,IAI7B,cAAA,EAAgB,EAAG,CAAA,EAAA,CAAG,GAAI;AAAA,GAC3B,CACA,CAAA,KAAA,CAAM,cAAc,SAAS,CAAA,CAC7B,SAAS,CAAS,KAAA,KAAA;AACjB,IAAA,IAAI,CAAC,WAAa,EAAA;AAChB,MAAO,OAAA,KAAA,CAAM,UAAU,cAAc,CAAA;AAAA;AAEvC,IAAA,OAAO,MACJ,KAAM,CAAA,cAAA,EAAgB,WAAW,CAAA,CACjC,YAAY,cAAc,CAAA;AAAA,GAC9B,CAAA;AAEH,EAAA,OAAO,aAAkB,KAAA,CAAA;AAC3B;;;;"}
1
+ {"version":3,"file":"updateUnprocessedEntity.cjs.js","sources":["../../../../src/database/operations/refreshState/updateUnprocessedEntity.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Entity, stringifyEntityRef } from '@backstage/catalog-model';\nimport { Knex } from 'knex';\nimport { DbRefreshStateRow } from '../../tables';\n\n/**\n * Attempts to update an existing refresh state row, returning true if it was\n * updated and false if there was no entity with a matching ref and location key.\n *\n * Updating the entity will also cause it to be scheduled for immediate processing.\n */\nexport async function updateUnprocessedEntity(options: {\n tx: Knex | Knex.Transaction;\n entity: Entity;\n hash: string;\n locationKey?: string;\n}): Promise<boolean> {\n const { tx, entity, hash, locationKey } = options;\n\n const entityRef = stringifyEntityRef(entity);\n const serializedEntity = JSON.stringify(entity);\n\n const refreshResult = await tx<DbRefreshStateRow>('refresh_state')\n .update({\n unprocessed_entity: serializedEntity,\n unprocessed_hash: hash,\n location_key: locationKey,\n last_discovery_at: tx.fn.now(),\n // We only get to this point if a processed entity actually had any changes, or\n // if an entity provider requested this mutation, meaning that we can safely\n // bump the deferred entities to the front of the queue for immediate processing.\n next_update_at: tx.fn.now(),\n })\n .where('entity_ref', entityRef)\n .andWhere(inner => {\n if (!locationKey) {\n return inner.whereNull('location_key');\n }\n return inner\n .where('location_key', locationKey)\n .orWhereNull('location_key');\n });\n\n return refreshResult === 1;\n}\n"],"names":["stringifyEntityRef"],"mappings":";;;;AA0BA,eAAsB,wBAAwB,OAKzB,EAAA;AACnB,EAAA,MAAM,EAAE,EAAA,EAAI,MAAQ,EAAA,IAAA,EAAM,aAAgB,GAAA,OAAA;AAE1C,EAAM,MAAA,SAAA,GAAYA,gCAAmB,MAAM,CAAA;AAC3C,EAAM,MAAA,gBAAA,GAAmB,IAAK,CAAA,SAAA,CAAU,MAAM,CAAA;AAE9C,EAAA,MAAM,aAAgB,GAAA,MAAM,EAAsB,CAAA,eAAe,EAC9D,MAAO,CAAA;AAAA,IACN,kBAAoB,EAAA,gBAAA;AAAA,IACpB,gBAAkB,EAAA,IAAA;AAAA,IAClB,YAAc,EAAA,WAAA;AAAA,IACd,iBAAA,EAAmB,EAAG,CAAA,EAAA,CAAG,GAAI,EAAA;AAAA;AAAA;AAAA;AAAA,IAI7B,cAAA,EAAgB,EAAG,CAAA,EAAA,CAAG,GAAI;AAAA,GAC3B,CACA,CAAA,KAAA,CAAM,cAAc,SAAS,CAAA,CAC7B,SAAS,CAAS,KAAA,KAAA;AACjB,IAAA,IAAI,CAAC,WAAa,EAAA;AAChB,MAAO,OAAA,KAAA,CAAM,UAAU,cAAc,CAAA;AAAA;AAEvC,IAAA,OAAO,MACJ,KAAM,CAAA,cAAA,EAAgB,WAAW,CAAA,CACjC,YAAY,cAAc,CAAA;AAAA,GAC9B,CAAA;AAEH,EAAA,OAAO,aAAkB,KAAA,CAAA;AAC3B;;;;"}
@@ -14,129 +14,130 @@ const scriptProtocolPattern = (
14
14
  async function performStitching(options) {
15
15
  const { knex, logger, entityRef } = options;
16
16
  const stitchTicket = options.stitchTicket ?? uuid.v4();
17
- const entityResult = await knex("refresh_state").where({ entity_ref: entityRef }).limit(1).select("entity_id");
18
- if (!entityResult.length) {
19
- return "abandoned";
20
- }
21
- await knex("final_entities").insert({
22
- entity_id: entityResult[0].entity_id,
23
- hash: "",
24
- entity_ref: entityRef,
25
- stitch_ticket: stitchTicket
26
- }).onConflict("entity_id").merge(["stitch_ticket"]);
27
- const [processedResult, relationsResult] = await Promise.all([
28
- knex.with("incoming_references", function incomingReferences(builder) {
29
- return builder.from("refresh_state_references").where({ target_entity_ref: entityRef }).count({ count: "*" });
30
- }).select({
31
- entityId: "refresh_state.entity_id",
32
- processedEntity: "refresh_state.processed_entity",
33
- errors: "refresh_state.errors",
34
- incomingReferenceCount: "incoming_references.count",
35
- previousHash: "final_entities.hash"
36
- }).from("refresh_state").where({ "refresh_state.entity_ref": entityRef }).crossJoin(knex.raw("incoming_references")).leftOuterJoin("final_entities", {
37
- "final_entities.entity_id": "refresh_state.entity_id"
38
- }),
39
- knex.distinct({
40
- relationType: "type",
41
- relationTarget: "target_entity_ref"
42
- }).from("relations").where({ source_entity_ref: entityRef }).orderBy("relationType", "asc").orderBy("relationTarget", "asc")
43
- ]);
44
- if (!processedResult.length) {
45
- logger.debug(
46
- `Unable to stitch ${entityRef}, item does not exist in refresh state table`
47
- );
48
- return "abandoned";
49
- }
50
- const {
51
- entityId,
52
- processedEntity,
53
- errors,
54
- incomingReferenceCount,
55
- previousHash
56
- } = processedResult[0];
57
- if (!processedEntity) {
58
- logger.debug(
59
- `Unable to stitch ${entityRef}, the entity has not yet been processed`
60
- );
61
- return "abandoned";
62
- }
63
- const entity = JSON.parse(processedEntity);
64
- const isOrphan = Number(incomingReferenceCount) === 0;
65
- let statusItems = [];
66
- if (isOrphan) {
67
- logger.debug(`${entityRef} is an orphan`);
68
- entity.metadata.annotations = {
69
- ...entity.metadata.annotations,
70
- ["backstage.io/orphan"]: "true"
71
- };
72
- }
73
- if (errors) {
74
- const parsedErrors = JSON.parse(errors);
75
- if (Array.isArray(parsedErrors) && parsedErrors.length) {
76
- statusItems = parsedErrors.map((e) => ({
77
- type: catalogClient.ENTITY_STATUS_CATALOG_PROCESSING_TYPE,
78
- level: "error",
79
- message: `${e.name}: ${e.message}`,
80
- error: e
81
- }));
17
+ let removeFromStitchQueueOnCompletion = options.strategy.mode === "deferred";
18
+ try {
19
+ const entityResult = await knex("refresh_state").where({ entity_ref: entityRef }).limit(1).select("entity_id");
20
+ if (!entityResult.length) {
21
+ return "abandoned";
82
22
  }
83
- }
84
- for (const annotation of [catalogModel.ANNOTATION_VIEW_URL, catalogModel.ANNOTATION_EDIT_URL]) {
85
- const value = entity.metadata.annotations?.[annotation];
86
- if (typeof value === "string" && scriptProtocolPattern.test(value)) {
87
- entity.metadata.annotations[annotation] = "https://backstage.io/annotation-rejected-for-security-reasons";
23
+ await knex("final_entities").insert({
24
+ entity_id: entityResult[0].entity_id,
25
+ hash: "",
26
+ entity_ref: entityRef,
27
+ stitch_ticket: stitchTicket
28
+ }).onConflict("entity_id").merge(["stitch_ticket"]);
29
+ const [processedResult, relationsResult] = await Promise.all([
30
+ knex.with("incoming_references", function incomingReferences(builder) {
31
+ return builder.from("refresh_state_references").where({ target_entity_ref: entityRef }).count({ count: "*" });
32
+ }).select({
33
+ entityId: "refresh_state.entity_id",
34
+ processedEntity: "refresh_state.processed_entity",
35
+ errors: "refresh_state.errors",
36
+ incomingReferenceCount: "incoming_references.count",
37
+ previousHash: "final_entities.hash"
38
+ }).from("refresh_state").where({ "refresh_state.entity_ref": entityRef }).crossJoin(knex.raw("incoming_references")).leftOuterJoin("final_entities", {
39
+ "final_entities.entity_id": "refresh_state.entity_id"
40
+ }),
41
+ knex.distinct({
42
+ relationType: "type",
43
+ relationTarget: "target_entity_ref"
44
+ }).from("relations").where({ source_entity_ref: entityRef }).orderBy("relationType", "asc").orderBy("relationTarget", "asc")
45
+ ]);
46
+ if (!processedResult.length) {
47
+ logger.debug(
48
+ `Unable to stitch ${entityRef}, item does not exist in refresh state table`
49
+ );
50
+ return "abandoned";
88
51
  }
89
- }
90
- entity.relations = relationsResult.filter(
91
- (row) => row.relationType
92
- /* exclude null row, if relevant */
93
- ).map((row) => ({
94
- type: row.relationType,
95
- targetRef: row.relationTarget
96
- }));
97
- if (statusItems.length) {
98
- entity.status = {
99
- ...entity.status,
100
- items: [...entity.status?.items ?? [], ...statusItems]
101
- };
102
- }
103
- const hash = util.generateStableHash(entity);
104
- if (hash === previousHash) {
105
- logger.debug(`Skipped stitching of ${entityRef}, no changes`);
106
- return "unchanged";
107
- }
108
- entity.metadata.uid = entityId;
109
- if (!entity.metadata.etag) {
110
- entity.metadata.etag = hash;
111
- }
112
- const searchEntries = buildEntitySearch.buildEntitySearch(entityId, entity);
113
- const amountOfRowsChanged = await knex("final_entities").update({
114
- final_entity: JSON.stringify(entity),
115
- hash,
116
- last_updated_at: knex.fn.now(),
117
- entity_ref: entityRef
118
- }).where("entity_id", entityId).where("stitch_ticket", stitchTicket).onConflict("entity_id").merge(["final_entity", "hash", "last_updated_at"]);
119
- const markDeferred = async () => {
120
- if (options.strategy.mode !== "deferred") {
121
- return;
52
+ const {
53
+ entityId,
54
+ processedEntity,
55
+ errors,
56
+ incomingReferenceCount,
57
+ previousHash
58
+ } = processedResult[0];
59
+ if (!processedEntity) {
60
+ logger.debug(
61
+ `Unable to stitch ${entityRef}, the entity has not yet been processed`
62
+ );
63
+ return "abandoned";
122
64
  }
123
- await markDeferredStitchCompleted.markDeferredStitchCompleted({
124
- knex,
125
- entityRef,
126
- stitchTicket
65
+ const entity = JSON.parse(processedEntity);
66
+ const isOrphan = Number(incomingReferenceCount) === 0;
67
+ let statusItems = [];
68
+ if (isOrphan) {
69
+ logger.debug(`${entityRef} is an orphan`);
70
+ entity.metadata.annotations = {
71
+ ...entity.metadata.annotations,
72
+ ["backstage.io/orphan"]: "true"
73
+ };
74
+ }
75
+ if (errors) {
76
+ const parsedErrors = JSON.parse(errors);
77
+ if (Array.isArray(parsedErrors) && parsedErrors.length) {
78
+ statusItems = parsedErrors.map((e) => ({
79
+ type: catalogClient.ENTITY_STATUS_CATALOG_PROCESSING_TYPE,
80
+ level: "error",
81
+ message: `${e.name}: ${e.message}`,
82
+ error: e
83
+ }));
84
+ }
85
+ }
86
+ for (const annotation of [catalogModel.ANNOTATION_VIEW_URL, catalogModel.ANNOTATION_EDIT_URL]) {
87
+ const value = entity.metadata.annotations?.[annotation];
88
+ if (typeof value === "string" && scriptProtocolPattern.test(value)) {
89
+ entity.metadata.annotations[annotation] = "https://backstage.io/annotation-rejected-for-security-reasons";
90
+ }
91
+ }
92
+ entity.relations = relationsResult.filter(
93
+ (row) => row.relationType
94
+ /* exclude null row, if relevant */
95
+ ).map((row) => ({
96
+ type: row.relationType,
97
+ targetRef: row.relationTarget
98
+ }));
99
+ if (statusItems.length) {
100
+ entity.status = {
101
+ ...entity.status,
102
+ items: [...entity.status?.items ?? [], ...statusItems]
103
+ };
104
+ }
105
+ const hash = util.generateStableHash(entity);
106
+ if (hash === previousHash) {
107
+ logger.debug(`Skipped stitching of ${entityRef}, no changes`);
108
+ return "unchanged";
109
+ }
110
+ entity.metadata.uid = entityId;
111
+ if (!entity.metadata.etag) {
112
+ entity.metadata.etag = hash;
113
+ }
114
+ const searchEntries = buildEntitySearch.buildEntitySearch(entityId, entity);
115
+ const amountOfRowsChanged = await knex("final_entities").update({
116
+ final_entity: JSON.stringify(entity),
117
+ hash,
118
+ last_updated_at: knex.fn.now()
119
+ }).where("entity_id", entityId).where("stitch_ticket", stitchTicket);
120
+ if (amountOfRowsChanged === 0) {
121
+ logger.debug(`Entity ${entityRef} is already stitched, skipping write.`);
122
+ return "abandoned";
123
+ }
124
+ await knex.transaction(async (trx) => {
125
+ await trx("search").where({ entity_id: entityId }).delete();
126
+ await trx.batchInsert("search", searchEntries, util.BATCH_SIZE);
127
127
  });
128
- };
129
- if (amountOfRowsChanged === 0) {
130
- logger.debug(`Entity ${entityRef} is already stitched, skipping write.`);
131
- await markDeferred();
132
- return "abandoned";
128
+ return "changed";
129
+ } catch (error) {
130
+ removeFromStitchQueueOnCompletion = false;
131
+ throw error;
132
+ } finally {
133
+ if (removeFromStitchQueueOnCompletion) {
134
+ await markDeferredStitchCompleted.markDeferredStitchCompleted({
135
+ knex,
136
+ entityRef,
137
+ stitchTicket
138
+ });
139
+ }
133
140
  }
134
- await knex.transaction(async (trx) => {
135
- await trx("search").where({ entity_id: entityId }).delete();
136
- await trx.batchInsert("search", searchEntries, util.BATCH_SIZE);
137
- });
138
- await markDeferred();
139
- return "changed";
140
141
  }
141
142
 
142
143
  exports.performStitching = performStitching;
@@ -1 +1 @@
1
- {"version":3,"file":"performStitching.cjs.js","sources":["../../../../src/database/operations/stitcher/performStitching.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ENTITY_STATUS_CATALOG_PROCESSING_TYPE } from '@backstage/catalog-client';\nimport {\n ANNOTATION_EDIT_URL,\n ANNOTATION_VIEW_URL,\n EntityRelation,\n} from '@backstage/catalog-model';\nimport { AlphaEntity, EntityStatusItem } from '@backstage/catalog-model/alpha';\nimport { SerializedError } from '@backstage/errors';\nimport { Knex } from 'knex';\nimport { v4 as uuid } from 'uuid';\nimport { StitchingStrategy } from '../../../stitching/types';\nimport {\n DbFinalEntitiesRow,\n DbRefreshStateRow,\n DbSearchRow,\n} from '../../tables';\nimport { buildEntitySearch } from './buildEntitySearch';\nimport { markDeferredStitchCompleted } from './markDeferredStitchCompleted';\nimport { BATCH_SIZE, generateStableHash } from './util';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\n// See https://github.com/facebook/react/blob/f0cf832e1d0c8544c36aa8b310960885a11a847c/packages/react-dom-bindings/src/shared/sanitizeURL.js\nconst scriptProtocolPattern =\n // eslint-disable-next-line no-control-regex\n /^[\\u0000-\\u001F ]*j[\\r\\n\\t]*a[\\r\\n\\t]*v[\\r\\n\\t]*a[\\r\\n\\t]*s[\\r\\n\\t]*c[\\r\\n\\t]*r[\\r\\n\\t]*i[\\r\\n\\t]*p[\\r\\n\\t]*t[\\r\\n\\t]*\\:/i;\n\n/**\n * Performs the act of stitching - to take all of the various outputs from the\n * ingestion process, and stitching them together into the final entity JSON\n * shape.\n */\nexport async function performStitching(options: {\n knex: Knex | Knex.Transaction;\n logger: LoggerService;\n strategy: StitchingStrategy;\n entityRef: string;\n stitchTicket?: string;\n}): Promise<'changed' | 'unchanged' | 'abandoned'> {\n const { knex, logger, entityRef } = options;\n const stitchTicket = options.stitchTicket ?? uuid();\n\n const entityResult = await knex<DbRefreshStateRow>('refresh_state')\n .where({ entity_ref: entityRef })\n .limit(1)\n .select('entity_id');\n if (!entityResult.length) {\n // Entity does no exist in refresh state table, no stitching required.\n return 'abandoned';\n }\n\n // Insert stitching ticket that will be compared before inserting the final entity.\n await knex<DbFinalEntitiesRow>('final_entities')\n .insert({\n entity_id: entityResult[0].entity_id,\n hash: '',\n entity_ref: entityRef,\n stitch_ticket: stitchTicket,\n })\n .onConflict('entity_id')\n .merge(['stitch_ticket']);\n\n // Selecting from refresh_state and final_entities should yield exactly\n // one row (except in abnormal cases where the stitch was invoked for\n // something that didn't exist at all, in which case it's zero rows).\n // The join with the temporary incoming_references still gives one row.\n const [processedResult, relationsResult] = await Promise.all([\n knex\n .with('incoming_references', function incomingReferences(builder) {\n return builder\n .from('refresh_state_references')\n .where({ target_entity_ref: entityRef })\n .count({ count: '*' });\n })\n .select({\n entityId: 'refresh_state.entity_id',\n processedEntity: 'refresh_state.processed_entity',\n errors: 'refresh_state.errors',\n incomingReferenceCount: 'incoming_references.count',\n previousHash: 'final_entities.hash',\n })\n .from('refresh_state')\n .where({ 'refresh_state.entity_ref': entityRef })\n .crossJoin(knex.raw('incoming_references'))\n .leftOuterJoin('final_entities', {\n 'final_entities.entity_id': 'refresh_state.entity_id',\n }),\n knex\n .distinct({\n relationType: 'type',\n relationTarget: 'target_entity_ref',\n })\n .from('relations')\n .where({ source_entity_ref: entityRef })\n .orderBy('relationType', 'asc')\n .orderBy('relationTarget', 'asc'),\n ]);\n\n // If there were no rows returned, it would mean that there was no\n // matching row even in the refresh_state. This can happen for example\n // if we emit a relation to something that hasn't been ingested yet.\n // It's safe to ignore this stitch attempt in that case.\n if (!processedResult.length) {\n logger.debug(\n `Unable to stitch ${entityRef}, item does not exist in refresh state table`,\n );\n return 'abandoned';\n }\n\n const {\n entityId,\n processedEntity,\n errors,\n incomingReferenceCount,\n previousHash,\n } = processedResult[0];\n\n // If there was no processed entity in place, the target hasn't been\n // through the processing steps yet. It's safe to ignore this stitch\n // attempt in that case, since another stitch will be triggered when\n // that processing has finished.\n if (!processedEntity) {\n logger.debug(\n `Unable to stitch ${entityRef}, the entity has not yet been processed`,\n );\n return 'abandoned';\n }\n\n // Grab the processed entity and stitch all of the relevant data into\n // it\n const entity = JSON.parse(processedEntity) as AlphaEntity;\n const isOrphan = Number(incomingReferenceCount) === 0;\n let statusItems: EntityStatusItem[] = [];\n\n if (isOrphan) {\n logger.debug(`${entityRef} is an orphan`);\n entity.metadata.annotations = {\n ...entity.metadata.annotations,\n ['backstage.io/orphan']: 'true',\n };\n }\n if (errors) {\n const parsedErrors = JSON.parse(errors) as SerializedError[];\n if (Array.isArray(parsedErrors) && parsedErrors.length) {\n statusItems = parsedErrors.map(e => ({\n type: ENTITY_STATUS_CATALOG_PROCESSING_TYPE,\n level: 'error',\n message: `${e.name}: ${e.message}`,\n error: e,\n }));\n }\n }\n // We opt to do this check here as we otherwise can't guarantee that it will be run after all processors\n for (const annotation of [ANNOTATION_VIEW_URL, ANNOTATION_EDIT_URL]) {\n const value = entity.metadata.annotations?.[annotation];\n if (typeof value === 'string' && scriptProtocolPattern.test(value)) {\n entity.metadata.annotations![annotation] =\n 'https://backstage.io/annotation-rejected-for-security-reasons';\n }\n }\n\n // TODO: entityRef is lower case and should be uppercase in the final\n // result\n entity.relations = relationsResult\n .filter(row => row.relationType /* exclude null row, if relevant */)\n .map<EntityRelation>(row => ({\n type: row.relationType!,\n targetRef: row.relationTarget!,\n }));\n if (statusItems.length) {\n entity.status = {\n ...entity.status,\n items: [...(entity.status?.items ?? []), ...statusItems],\n };\n }\n\n // If the output entity was actually not changed, just abort\n const hash = generateStableHash(entity);\n if (hash === previousHash) {\n logger.debug(`Skipped stitching of ${entityRef}, no changes`);\n return 'unchanged';\n }\n\n entity.metadata.uid = entityId;\n if (!entity.metadata.etag) {\n // If the original data source did not have its own etag handling,\n // use the hash as a good-quality etag\n entity.metadata.etag = hash;\n }\n\n // This may throw if the entity is invalid, so we call it before\n // the final_entities write, even though we may end up not needing\n // to write the search index.\n const searchEntries = buildEntitySearch(entityId, entity);\n\n const amountOfRowsChanged = await knex<DbFinalEntitiesRow>('final_entities')\n .update({\n final_entity: JSON.stringify(entity),\n hash,\n last_updated_at: knex.fn.now(),\n entity_ref: entityRef,\n })\n .where('entity_id', entityId)\n .where('stitch_ticket', stitchTicket)\n .onConflict('entity_id')\n .merge(['final_entity', 'hash', 'last_updated_at']);\n\n const markDeferred = async () => {\n if (options.strategy.mode !== 'deferred') {\n return;\n }\n await markDeferredStitchCompleted({\n knex: knex,\n entityRef,\n stitchTicket,\n });\n };\n\n if (amountOfRowsChanged === 0) {\n logger.debug(`Entity ${entityRef} is already stitched, skipping write.`);\n await markDeferred();\n return 'abandoned';\n }\n\n await knex.transaction(async trx => {\n await trx<DbSearchRow>('search').where({ entity_id: entityId }).delete();\n await trx.batchInsert('search', searchEntries, BATCH_SIZE);\n });\n\n await markDeferred();\n return 'changed';\n}\n"],"names":["uuid","ENTITY_STATUS_CATALOG_PROCESSING_TYPE","ANNOTATION_VIEW_URL","ANNOTATION_EDIT_URL","generateStableHash","buildEntitySearch","markDeferredStitchCompleted","BATCH_SIZE"],"mappings":"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"}
1
+ {"version":3,"file":"performStitching.cjs.js","sources":["../../../../src/database/operations/stitcher/performStitching.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ENTITY_STATUS_CATALOG_PROCESSING_TYPE } from '@backstage/catalog-client';\nimport {\n ANNOTATION_EDIT_URL,\n ANNOTATION_VIEW_URL,\n EntityRelation,\n} from '@backstage/catalog-model';\nimport { AlphaEntity, EntityStatusItem } from '@backstage/catalog-model/alpha';\nimport { SerializedError } from '@backstage/errors';\nimport { Knex } from 'knex';\nimport { v4 as uuid } from 'uuid';\nimport { StitchingStrategy } from '../../../stitching/types';\nimport {\n DbFinalEntitiesRow,\n DbRefreshStateRow,\n DbSearchRow,\n} from '../../tables';\nimport { buildEntitySearch } from './buildEntitySearch';\nimport { markDeferredStitchCompleted } from './markDeferredStitchCompleted';\nimport { BATCH_SIZE, generateStableHash } from './util';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\n// See https://github.com/facebook/react/blob/f0cf832e1d0c8544c36aa8b310960885a11a847c/packages/react-dom-bindings/src/shared/sanitizeURL.js\nconst scriptProtocolPattern =\n // eslint-disable-next-line no-control-regex\n /^[\\u0000-\\u001F ]*j[\\r\\n\\t]*a[\\r\\n\\t]*v[\\r\\n\\t]*a[\\r\\n\\t]*s[\\r\\n\\t]*c[\\r\\n\\t]*r[\\r\\n\\t]*i[\\r\\n\\t]*p[\\r\\n\\t]*t[\\r\\n\\t]*\\:/i;\n\n/**\n * Performs the act of stitching - to take all of the various outputs from the\n * ingestion process, and stitching them together into the final entity JSON\n * shape.\n */\nexport async function performStitching(options: {\n knex: Knex | Knex.Transaction;\n logger: LoggerService;\n strategy: StitchingStrategy;\n entityRef: string;\n stitchTicket?: string;\n}): Promise<'changed' | 'unchanged' | 'abandoned'> {\n const { knex, logger, entityRef } = options;\n const stitchTicket = options.stitchTicket ?? uuid();\n\n // In deferred mode, the entity is removed from the stitch queue on ANY\n // completion, except when an exception is thrown. In the latter case, the\n // entity will be retried at a later time.\n let removeFromStitchQueueOnCompletion = options.strategy.mode === 'deferred';\n\n try {\n const entityResult = await knex<DbRefreshStateRow>('refresh_state')\n .where({ entity_ref: entityRef })\n .limit(1)\n .select('entity_id');\n if (!entityResult.length) {\n // Entity does no exist in refresh state table, no stitching required.\n return 'abandoned';\n }\n\n // Insert stitching ticket that will be compared before inserting the final entity.\n await knex<DbFinalEntitiesRow>('final_entities')\n .insert({\n entity_id: entityResult[0].entity_id,\n hash: '',\n entity_ref: entityRef,\n stitch_ticket: stitchTicket,\n })\n .onConflict('entity_id')\n .merge(['stitch_ticket']);\n\n // Selecting from refresh_state and final_entities should yield exactly\n // one row (except in abnormal cases where the stitch was invoked for\n // something that didn't exist at all, in which case it's zero rows).\n // The join with the temporary incoming_references still gives one row.\n const [processedResult, relationsResult] = await Promise.all([\n knex\n .with('incoming_references', function incomingReferences(builder) {\n return builder\n .from('refresh_state_references')\n .where({ target_entity_ref: entityRef })\n .count({ count: '*' });\n })\n .select({\n entityId: 'refresh_state.entity_id',\n processedEntity: 'refresh_state.processed_entity',\n errors: 'refresh_state.errors',\n incomingReferenceCount: 'incoming_references.count',\n previousHash: 'final_entities.hash',\n })\n .from('refresh_state')\n .where({ 'refresh_state.entity_ref': entityRef })\n .crossJoin(knex.raw('incoming_references'))\n .leftOuterJoin('final_entities', {\n 'final_entities.entity_id': 'refresh_state.entity_id',\n }),\n knex\n .distinct({\n relationType: 'type',\n relationTarget: 'target_entity_ref',\n })\n .from('relations')\n .where({ source_entity_ref: entityRef })\n .orderBy('relationType', 'asc')\n .orderBy('relationTarget', 'asc'),\n ]);\n\n // If there were no rows returned, it would mean that there was no\n // matching row even in the refresh_state. This can happen for example\n // if we emit a relation to something that hasn't been ingested yet.\n // It's safe to ignore this stitch attempt in that case.\n if (!processedResult.length) {\n logger.debug(\n `Unable to stitch ${entityRef}, item does not exist in refresh state table`,\n );\n return 'abandoned';\n }\n\n const {\n entityId,\n processedEntity,\n errors,\n incomingReferenceCount,\n previousHash,\n } = processedResult[0];\n\n // If there was no processed entity in place, the target hasn't been\n // through the processing steps yet. It's safe to ignore this stitch\n // attempt in that case, since another stitch will be triggered when\n // that processing has finished.\n if (!processedEntity) {\n logger.debug(\n `Unable to stitch ${entityRef}, the entity has not yet been processed`,\n );\n return 'abandoned';\n }\n\n // Grab the processed entity and stitch all of the relevant data into\n // it\n const entity = JSON.parse(processedEntity) as AlphaEntity;\n const isOrphan = Number(incomingReferenceCount) === 0;\n let statusItems: EntityStatusItem[] = [];\n\n if (isOrphan) {\n logger.debug(`${entityRef} is an orphan`);\n entity.metadata.annotations = {\n ...entity.metadata.annotations,\n ['backstage.io/orphan']: 'true',\n };\n }\n if (errors) {\n const parsedErrors = JSON.parse(errors) as SerializedError[];\n if (Array.isArray(parsedErrors) && parsedErrors.length) {\n statusItems = parsedErrors.map(e => ({\n type: ENTITY_STATUS_CATALOG_PROCESSING_TYPE,\n level: 'error',\n message: `${e.name}: ${e.message}`,\n error: e,\n }));\n }\n }\n // We opt to do this check here as we otherwise can't guarantee that it will be run after all processors\n for (const annotation of [ANNOTATION_VIEW_URL, ANNOTATION_EDIT_URL]) {\n const value = entity.metadata.annotations?.[annotation];\n if (typeof value === 'string' && scriptProtocolPattern.test(value)) {\n entity.metadata.annotations![annotation] =\n 'https://backstage.io/annotation-rejected-for-security-reasons';\n }\n }\n\n // TODO: entityRef is lower case and should be uppercase in the final\n // result\n entity.relations = relationsResult\n .filter(row => row.relationType /* exclude null row, if relevant */)\n .map<EntityRelation>(row => ({\n type: row.relationType!,\n targetRef: row.relationTarget!,\n }));\n if (statusItems.length) {\n entity.status = {\n ...entity.status,\n items: [...(entity.status?.items ?? []), ...statusItems],\n };\n }\n\n // If the output entity was actually not changed, just abort\n const hash = generateStableHash(entity);\n if (hash === previousHash) {\n logger.debug(`Skipped stitching of ${entityRef}, no changes`);\n return 'unchanged';\n }\n\n entity.metadata.uid = entityId;\n if (!entity.metadata.etag) {\n // If the original data source did not have its own etag handling,\n // use the hash as a good-quality etag\n entity.metadata.etag = hash;\n }\n\n // This may throw if the entity is invalid, so we call it before\n // the final_entities write, even though we may end up not needing\n // to write the search index.\n const searchEntries = buildEntitySearch(entityId, entity);\n\n const amountOfRowsChanged = await knex<DbFinalEntitiesRow>('final_entities')\n .update({\n final_entity: JSON.stringify(entity),\n hash,\n last_updated_at: knex.fn.now(),\n })\n .where('entity_id', entityId)\n .where('stitch_ticket', stitchTicket);\n\n if (amountOfRowsChanged === 0) {\n logger.debug(`Entity ${entityRef} is already stitched, skipping write.`);\n return 'abandoned';\n }\n\n await knex.transaction(async trx => {\n await trx<DbSearchRow>('search').where({ entity_id: entityId }).delete();\n await trx.batchInsert('search', searchEntries, BATCH_SIZE);\n });\n\n return 'changed';\n } catch (error) {\n removeFromStitchQueueOnCompletion = false;\n throw error;\n } finally {\n if (removeFromStitchQueueOnCompletion) {\n await markDeferredStitchCompleted({\n knex: knex,\n entityRef,\n stitchTicket,\n });\n }\n }\n}\n"],"names":["uuid","ENTITY_STATUS_CATALOG_PROCESSING_TYPE","ANNOTATION_VIEW_URL","ANNOTATION_EDIT_URL","generateStableHash","buildEntitySearch","BATCH_SIZE","markDeferredStitchCompleted"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -9,7 +9,7 @@ import { CatalogProcessor as CatalogProcessor$1, CatalogProcessorEmit as Catalog
9
9
  import { Config } from '@backstage/config';
10
10
  import { PluginEndpointDiscovery, TokenManager } from '@backstage/backend-common';
11
11
  import { GetEntitiesRequest, CatalogApi } from '@backstage/catalog-client';
12
- import { Permission, PermissionAuthorizer, PermissionRuleParams } from '@backstage/plugin-permission-common';
12
+ import { Permission, PermissionRuleParams, PermissionAuthorizer } from '@backstage/plugin-permission-common';
13
13
  import { Router } from 'express';
14
14
  import { PermissionRule } from '@backstage/plugin-permission-node';
15
15
  import { EventBroker, EventsService } from '@backstage/plugin-events-node';
@@ -28,7 +28,7 @@ function progressTracker(knex, logger) {
28
28
  { description: "Number of entities currently in the stitching queue" }
29
29
  );
30
30
  stitchingQueueCount.addCallback(async (result) => {
31
- const total = await knex("refresh_state").count({ count: "*" }).whereNotNull("next_stitch_at").where("next_stitch_at", "<=", knex.fn.now());
31
+ const total = await knex("refresh_state").count({ count: "*" }).whereNotNull("next_stitch_at");
32
32
  result.observe(Number(total[0].count));
33
33
  });
34
34
  const stitchingQueueDelay = meter.createHistogram(
@@ -1 +1 @@
1
- {"version":3,"file":"progressTracker.cjs.js","sources":["../../src/stitching/progressTracker.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { stringifyError } from '@backstage/errors';\nimport { metrics } from '@opentelemetry/api';\nimport { Knex } from 'knex';\nimport { DateTime } from 'luxon';\nimport { DbRefreshStateRow } from '../database/tables';\nimport { createCounterMetric } from '../util/metrics';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\n// Helps wrap the timing and logging behaviors\nexport function progressTracker(knex: Knex, logger: LoggerService) {\n // prom-client metrics are deprecated in favour of OpenTelemetry metrics.\n const promStitchedEntities = createCounterMetric({\n name: 'catalog_stitched_entities_count',\n help: 'Amount of entities stitched. DEPRECATED, use OpenTelemetry metrics instead',\n });\n\n const meter = metrics.getMeter('default');\n\n const stitchedEntities = meter.createCounter(\n 'catalog.stitched.entities.count',\n {\n description: 'Amount of entities stitched',\n },\n );\n\n const stitchingDuration = meter.createHistogram(\n 'catalog.stitching.duration',\n {\n description: 'Time spent executing the full stitching flow',\n unit: 'seconds',\n },\n );\n\n const stitchingQueueCount = meter.createObservableGauge(\n 'catalog.stitching.queue.length',\n { description: 'Number of entities currently in the stitching queue' },\n );\n stitchingQueueCount.addCallback(async result => {\n const total = await knex<DbRefreshStateRow>('refresh_state')\n .count({ count: '*' })\n .whereNotNull('next_stitch_at')\n .where('next_stitch_at', '<=', knex.fn.now());\n result.observe(Number(total[0].count));\n });\n\n const stitchingQueueDelay = meter.createHistogram(\n 'catalog.stitching.queue.delay',\n {\n description:\n 'The amount of delay between being scheduled for stitching, and the start of actually being stitched',\n unit: 'seconds',\n },\n );\n\n function stitchStart(item: {\n entityRef: string;\n stitchRequestedAt?: DateTime;\n }) {\n logger.debug(`Stitching ${item.entityRef}`);\n\n const startTime = process.hrtime();\n if (item.stitchRequestedAt) {\n stitchingQueueDelay.record(\n -item.stitchRequestedAt.diffNow().as('seconds'),\n );\n }\n\n function endTime() {\n const delta = process.hrtime(startTime);\n return delta[0] + delta[1] / 1e9;\n }\n\n function markComplete(result: string) {\n promStitchedEntities.inc(1);\n stitchedEntities.add(1, { result });\n stitchingDuration.record(endTime(), { result });\n }\n\n function markFailed(error: Error) {\n promStitchedEntities.inc(1);\n stitchedEntities.add(1, { result: 'error' });\n stitchingDuration.record(endTime(), { result: 'error' });\n logger.error(\n `Failed to stitch ${item.entityRef}, ${stringifyError(error)}`,\n );\n }\n\n return {\n markComplete,\n markFailed,\n };\n }\n\n return { stitchStart };\n}\n"],"names":["createCounterMetric","metrics","stringifyError"],"mappings":"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"}
1
+ {"version":3,"file":"progressTracker.cjs.js","sources":["../../src/stitching/progressTracker.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { stringifyError } from '@backstage/errors';\nimport { metrics } from '@opentelemetry/api';\nimport { Knex } from 'knex';\nimport { DateTime } from 'luxon';\nimport { DbRefreshStateRow } from '../database/tables';\nimport { createCounterMetric } from '../util/metrics';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\n// Helps wrap the timing and logging behaviors\nexport function progressTracker(knex: Knex, logger: LoggerService) {\n // prom-client metrics are deprecated in favour of OpenTelemetry metrics.\n const promStitchedEntities = createCounterMetric({\n name: 'catalog_stitched_entities_count',\n help: 'Amount of entities stitched. DEPRECATED, use OpenTelemetry metrics instead',\n });\n\n const meter = metrics.getMeter('default');\n\n const stitchedEntities = meter.createCounter(\n 'catalog.stitched.entities.count',\n {\n description: 'Amount of entities stitched',\n },\n );\n\n const stitchingDuration = meter.createHistogram(\n 'catalog.stitching.duration',\n {\n description: 'Time spent executing the full stitching flow',\n unit: 'seconds',\n },\n );\n\n const stitchingQueueCount = meter.createObservableGauge(\n 'catalog.stitching.queue.length',\n { description: 'Number of entities currently in the stitching queue' },\n );\n stitchingQueueCount.addCallback(async result => {\n const total = await knex<DbRefreshStateRow>('refresh_state')\n .count({ count: '*' })\n .whereNotNull('next_stitch_at');\n result.observe(Number(total[0].count));\n });\n\n const stitchingQueueDelay = meter.createHistogram(\n 'catalog.stitching.queue.delay',\n {\n description:\n 'The amount of delay between being scheduled for stitching, and the start of actually being stitched',\n unit: 'seconds',\n },\n );\n\n function stitchStart(item: {\n entityRef: string;\n stitchRequestedAt?: DateTime;\n }) {\n logger.debug(`Stitching ${item.entityRef}`);\n\n const startTime = process.hrtime();\n if (item.stitchRequestedAt) {\n stitchingQueueDelay.record(\n -item.stitchRequestedAt.diffNow().as('seconds'),\n );\n }\n\n function endTime() {\n const delta = process.hrtime(startTime);\n return delta[0] + delta[1] / 1e9;\n }\n\n function markComplete(result: string) {\n promStitchedEntities.inc(1);\n stitchedEntities.add(1, { result });\n stitchingDuration.record(endTime(), { result });\n }\n\n function markFailed(error: Error) {\n promStitchedEntities.inc(1);\n stitchedEntities.add(1, { result: 'error' });\n stitchingDuration.record(endTime(), { result: 'error' });\n logger.error(\n `Failed to stitch ${item.entityRef}, ${stringifyError(error)}`,\n );\n }\n\n return {\n markComplete,\n markFailed,\n };\n }\n\n return { stitchStart };\n}\n"],"names":["createCounterMetric","metrics","stringifyError"],"mappings":";;;;;;AAyBgB,SAAA,eAAA,CAAgB,MAAY,MAAuB,EAAA;AAEjE,EAAA,MAAM,uBAAuBA,2BAAoB,CAAA;AAAA,IAC/C,IAAM,EAAA,iCAAA;AAAA,IACN,IAAM,EAAA;AAAA,GACP,CAAA;AAED,EAAM,MAAA,KAAA,GAAQC,WAAQ,CAAA,QAAA,CAAS,SAAS,CAAA;AAExC,EAAA,MAAM,mBAAmB,KAAM,CAAA,aAAA;AAAA,IAC7B,iCAAA;AAAA,IACA;AAAA,MACE,WAAa,EAAA;AAAA;AACf,GACF;AAEA,EAAA,MAAM,oBAAoB,KAAM,CAAA,eAAA;AAAA,IAC9B,4BAAA;AAAA,IACA;AAAA,MACE,WAAa,EAAA,8CAAA;AAAA,MACb,IAAM,EAAA;AAAA;AACR,GACF;AAEA,EAAA,MAAM,sBAAsB,KAAM,CAAA,qBAAA;AAAA,IAChC,gCAAA;AAAA,IACA,EAAE,aAAa,qDAAsD;AAAA,GACvE;AACA,EAAoB,mBAAA,CAAA,WAAA,CAAY,OAAM,MAAU,KAAA;AAC9C,IAAA,MAAM,KAAQ,GAAA,MAAM,IAAwB,CAAA,eAAe,CACxD,CAAA,KAAA,CAAM,EAAE,KAAA,EAAO,GAAI,EAAC,CACpB,CAAA,YAAA,CAAa,gBAAgB,CAAA;AAChC,IAAA,MAAA,CAAO,QAAQ,MAAO,CAAA,KAAA,CAAM,CAAC,CAAA,CAAE,KAAK,CAAC,CAAA;AAAA,GACtC,CAAA;AAED,EAAA,MAAM,sBAAsB,KAAM,CAAA,eAAA;AAAA,IAChC,+BAAA;AAAA,IACA;AAAA,MACE,WACE,EAAA,qGAAA;AAAA,MACF,IAAM,EAAA;AAAA;AACR,GACF;AAEA,EAAA,SAAS,YAAY,IAGlB,EAAA;AACD,IAAA,MAAA,CAAO,KAAM,CAAA,CAAA,UAAA,EAAa,IAAK,CAAA,SAAS,CAAE,CAAA,CAAA;AAE1C,IAAM,MAAA,SAAA,GAAY,QAAQ,MAAO,EAAA;AACjC,IAAA,IAAI,KAAK,iBAAmB,EAAA;AAC1B,MAAoB,mBAAA,CAAA,MAAA;AAAA,QAClB,CAAC,IAAK,CAAA,iBAAA,CAAkB,OAAQ,EAAA,CAAE,GAAG,SAAS;AAAA,OAChD;AAAA;AAGF,IAAA,SAAS,OAAU,GAAA;AACjB,MAAM,MAAA,KAAA,GAAQ,OAAQ,CAAA,MAAA,CAAO,SAAS,CAAA;AACtC,MAAA,OAAO,KAAM,CAAA,CAAC,CAAI,GAAA,KAAA,CAAM,CAAC,CAAI,GAAA,GAAA;AAAA;AAG/B,IAAA,SAAS,aAAa,MAAgB,EAAA;AACpC,MAAA,oBAAA,CAAqB,IAAI,CAAC,CAAA;AAC1B,MAAA,gBAAA,CAAiB,GAAI,CAAA,CAAA,EAAG,EAAE,MAAA,EAAQ,CAAA;AAClC,MAAA,iBAAA,CAAkB,MAAO,CAAA,OAAA,EAAW,EAAA,EAAE,QAAQ,CAAA;AAAA;AAGhD,IAAA,SAAS,WAAW,KAAc,EAAA;AAChC,MAAA,oBAAA,CAAqB,IAAI,CAAC,CAAA;AAC1B,MAAA,gBAAA,CAAiB,GAAI,CAAA,CAAA,EAAG,EAAE,MAAA,EAAQ,SAAS,CAAA;AAC3C,MAAA,iBAAA,CAAkB,OAAO,OAAQ,EAAA,EAAG,EAAE,MAAA,EAAQ,SAAS,CAAA;AACvD,MAAO,MAAA,CAAA,KAAA;AAAA,QACL,oBAAoB,IAAK,CAAA,SAAS,CAAK,EAAA,EAAAC,qBAAA,CAAe,KAAK,CAAC,CAAA;AAAA,OAC9D;AAAA;AAGF,IAAO,OAAA;AAAA,MACL,YAAA;AAAA,MACA;AAAA,KACF;AAAA;AAGF,EAAA,OAAO,EAAE,WAAY,EAAA;AACvB;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-catalog-backend",
3
- "version": "1.28.0",
3
+ "version": "1.28.1-next.0",
4
4
  "description": "The Backstage backend plugin that provides the Backstage catalog",
5
5
  "backstage": {
6
6
  "role": "backend-plugin",
@@ -72,20 +72,20 @@
72
72
  },
73
73
  "dependencies": {
74
74
  "@backstage/backend-common": "^0.25.0",
75
- "@backstage/backend-openapi-utils": "^0.3.0",
76
- "@backstage/backend-plugin-api": "^1.0.2",
77
- "@backstage/catalog-client": "^1.8.0",
78
- "@backstage/catalog-model": "^1.7.1",
79
- "@backstage/config": "^1.3.0",
80
- "@backstage/errors": "^1.2.5",
81
- "@backstage/integration": "^1.15.2",
82
- "@backstage/plugin-catalog-common": "^1.1.1",
83
- "@backstage/plugin-catalog-node": "^1.14.0",
84
- "@backstage/plugin-events-node": "^0.4.5",
85
- "@backstage/plugin-permission-common": "^0.8.2",
86
- "@backstage/plugin-permission-node": "^0.8.5",
87
- "@backstage/plugin-search-backend-module-catalog": "^0.2.5",
88
- "@backstage/types": "^1.2.0",
75
+ "@backstage/backend-openapi-utils": "0.3.1-next.0",
76
+ "@backstage/backend-plugin-api": "1.0.3-next.0",
77
+ "@backstage/catalog-client": "1.8.1-next.0",
78
+ "@backstage/catalog-model": "1.7.1",
79
+ "@backstage/config": "1.3.0",
80
+ "@backstage/errors": "1.2.5",
81
+ "@backstage/integration": "1.16.0-next.0",
82
+ "@backstage/plugin-catalog-common": "1.1.1",
83
+ "@backstage/plugin-catalog-node": "1.14.1-next.0",
84
+ "@backstage/plugin-events-node": "0.4.6-next.0",
85
+ "@backstage/plugin-permission-common": "0.8.2",
86
+ "@backstage/plugin-permission-node": "0.8.6-next.0",
87
+ "@backstage/plugin-search-backend-module-catalog": "0.2.6-next.0",
88
+ "@backstage/types": "1.2.0",
89
89
  "@opentelemetry/api": "^1.3.0",
90
90
  "@types/express": "^4.17.6",
91
91
  "codeowners-utils": "^1.0.2",
@@ -108,11 +108,11 @@
108
108
  "zod": "^3.22.4"
109
109
  },
110
110
  "devDependencies": {
111
- "@backstage/backend-defaults": "^0.5.3",
112
- "@backstage/backend-test-utils": "^1.1.0",
113
- "@backstage/cli": "^0.29.0",
114
- "@backstage/plugin-permission-common": "^0.8.2",
115
- "@backstage/repo-tools": "^0.11.0",
111
+ "@backstage/backend-defaults": "0.6.0-next.0",
112
+ "@backstage/backend-test-utils": "1.2.0-next.0",
113
+ "@backstage/cli": "0.29.3-next.0",
114
+ "@backstage/plugin-permission-common": "0.8.2",
115
+ "@backstage/repo-tools": "0.12.0-next.0",
116
116
  "@types/core-js": "^2.5.4",
117
117
  "@types/git-url-parse": "^9.0.0",
118
118
  "@types/glob": "^8.0.0",