@backstage/plugin-catalog-backend 1.6.0-next.1 → 1.6.0-next.2

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,33 @@
1
1
  # @backstage/plugin-catalog-backend
2
2
 
3
+ ## 1.6.0-next.2
4
+
5
+ ### Minor Changes
6
+
7
+ - 3072ebfdd7: The search table also holds the original entity value now and the facets endpoint fetches the filtered entity data from the search table.
8
+
9
+ ### Patch Changes
10
+
11
+ - c507aee8a2: Ensured typescript type checks in migration files.
12
+ - 884d749b14: Refactored to use `coreServices` from `@backstage/backend-plugin-api`.
13
+ - eacc8e2b55: Make it possible for entity providers to supply only entity refs, instead of full entities, in `delta` mutation deletions.
14
+ - 20a5161f04: Adds MySQL support for the catalog-backend
15
+ - Updated dependencies
16
+ - @backstage/plugin-catalog-node@1.3.0-next.2
17
+ - @backstage/backend-common@0.17.0-next.2
18
+ - @backstage/backend-plugin-api@0.2.0-next.2
19
+ - @backstage/plugin-search-common@1.2.0-next.2
20
+ - @backstage/plugin-permission-node@0.7.2-next.2
21
+ - @backstage/catalog-client@1.2.0-next.1
22
+ - @backstage/catalog-model@1.1.4-next.1
23
+ - @backstage/config@1.0.5-next.1
24
+ - @backstage/errors@1.1.4-next.1
25
+ - @backstage/integration@1.4.1-next.1
26
+ - @backstage/types@1.0.2-next.1
27
+ - @backstage/plugin-catalog-common@1.0.9-next.2
28
+ - @backstage/plugin-permission-common@0.7.2-next.1
29
+ - @backstage/plugin-scaffolder-common@1.2.3-next.1
30
+
3
31
  ## 1.6.0-next.1
4
32
 
5
33
  ### Minor Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-catalog-backend",
3
- "version": "1.6.0-next.1",
3
+ "version": "1.6.0-next.2",
4
4
  "main": "../dist/index.cjs.js",
5
5
  "types": "../dist/index.alpha.d.ts"
6
6
  }
package/dist/index.cjs.js CHANGED
@@ -1517,6 +1517,7 @@ class DefaultProcessingDatabase {
1517
1517
  refreshKeys,
1518
1518
  locationKey
1519
1519
  } = options;
1520
+ const configClient = tx.client.config.client;
1520
1521
  const refreshResult = await tx("refresh_state").update({
1521
1522
  processed_entity: JSON.stringify(processedEntity),
1522
1523
  result_hash: resultHash,
@@ -1539,7 +1540,7 @@ class DefaultProcessingDatabase {
1539
1540
  sourceEntityRef
1540
1541
  });
1541
1542
  let previousRelationRows;
1542
- if (tx.client.config.client.includes("sqlite3") || tx.client.config.client.includes("mysql")) {
1543
+ if (configClient.includes("sqlite3") || configClient.includes("mysql")) {
1543
1544
  previousRelationRows = await tx("relations").select("*").where({ originating_entity_id: id });
1544
1545
  await tx("relations").where({ originating_entity_id: id }).delete();
1545
1546
  } else {
@@ -1868,16 +1869,20 @@ class DefaultProcessingDatabase {
1868
1869
  next_update_at: tx.fn.now(),
1869
1870
  last_discovery_at: tx.fn.now()
1870
1871
  });
1871
- if (!tx.client.config.client.includes("sqlite3")) {
1872
+ if (tx.client.config.client.includes("pg")) {
1872
1873
  query = query.onConflict("entity_ref").ignore();
1873
1874
  }
1874
1875
  const result = await query;
1875
1876
  return result.rowCount === 1 || result.length === 1;
1876
1877
  } catch (error) {
1877
- if (errors.isError(error) && error.message.includes("UNIQUE constraint failed")) {
1878
+ if (!backendCommon.isDatabaseConflictError(error)) {
1879
+ throw error;
1880
+ } else {
1881
+ this.options.logger.debug(
1882
+ `Unable to insert a new refresh state row, ${error}`
1883
+ );
1878
1884
  return false;
1879
1885
  }
1880
- throw error;
1881
1886
  }
1882
1887
  }
1883
1888
  async checkLocationKeyConflict(tx, entityRef, locationKey) {
@@ -1906,7 +1911,7 @@ class DefaultProcessingDatabase {
1906
1911
  deferred: e,
1907
1912
  hash: generateStableHash$1(e.entity)
1908
1913
  })),
1909
- toRemove: options.removed.map((e) => catalogModel.stringifyEntityRef(e.entity))
1914
+ toRemove: options.removed.map((e) => e.entityRef)
1910
1915
  };
1911
1916
  }
1912
1917
  const oldRefs = await tx(
@@ -2425,8 +2430,8 @@ function stringifyPagination(input) {
2425
2430
  const base64 = Buffer.from(json, "utf8").toString("base64");
2426
2431
  return base64;
2427
2432
  }
2428
- function addCondition(queryBuilder, db, filter, negate = false) {
2429
- const matchQuery = db("search").select("entity_id").where({ key: filter.key.toLowerCase() }).andWhere(function keyFilter() {
2433
+ function addCondition(queryBuilder, db, filter, negate = false, entityIdField = "entity_id") {
2434
+ const matchQuery = db("search").select(entityIdField).where({ key: filter.key.toLowerCase() }).andWhere(function keyFilter() {
2430
2435
  if (filter.values) {
2431
2436
  if (filter.values.length === 1) {
2432
2437
  this.where({ value: filter.values[0].toLowerCase() });
@@ -2439,7 +2444,7 @@ function addCondition(queryBuilder, db, filter, negate = false) {
2439
2444
  }
2440
2445
  }
2441
2446
  });
2442
- queryBuilder.andWhere("entity_id", negate ? "not in" : "in", matchQuery);
2447
+ queryBuilder.andWhere(entityIdField, negate ? "not in" : "in", matchQuery);
2443
2448
  }
2444
2449
  function isEntitiesSearchFilter(filter) {
2445
2450
  return filter.hasOwnProperty("key");
@@ -2450,24 +2455,28 @@ function isOrEntityFilter(filter) {
2450
2455
  function isNegationEntityFilter(filter) {
2451
2456
  return filter.hasOwnProperty("not");
2452
2457
  }
2453
- function parseFilter(filter, query, db, negate = false) {
2458
+ function parseFilter(filter, query, db, negate = false, entityIdField = "entity_id") {
2454
2459
  if (isEntitiesSearchFilter(filter)) {
2455
2460
  return query.andWhere(function filterFunction() {
2456
- addCondition(this, db, filter, negate);
2461
+ addCondition(this, db, filter, negate, entityIdField);
2457
2462
  });
2458
2463
  }
2459
2464
  if (isNegationEntityFilter(filter)) {
2460
- return parseFilter(filter.not, query, db, !negate);
2465
+ return parseFilter(filter.not, query, db, !negate, entityIdField);
2461
2466
  }
2462
2467
  return query[negate ? "andWhereNot" : "andWhere"](function filterFunction() {
2463
2468
  var _a, _b;
2464
2469
  if (isOrEntityFilter(filter)) {
2465
2470
  for (const subFilter of (_a = filter.anyOf) != null ? _a : []) {
2466
- this.orWhere((subQuery) => parseFilter(subFilter, subQuery, db));
2471
+ this.orWhere(
2472
+ (subQuery) => parseFilter(subFilter, subQuery, db, false, entityIdField)
2473
+ );
2467
2474
  }
2468
2475
  } else {
2469
2476
  for (const subFilter of (_b = filter.allOf) != null ? _b : []) {
2470
- this.andWhere((subQuery) => parseFilter(subFilter, subQuery, db));
2477
+ this.andWhere(
2478
+ (subQuery) => parseFilter(subFilter, subQuery, db, false, entityIdField)
2479
+ );
2471
2480
  }
2472
2481
  }
2473
2482
  });
@@ -2551,14 +2560,36 @@ class DefaultEntitiesCatalog {
2551
2560
  return { items };
2552
2561
  }
2553
2562
  async removeEntityByUid(uid) {
2554
- await this.database("refresh_state").update({
2555
- result_hash: "child-was-deleted",
2556
- next_update_at: this.database.fn.now()
2557
- }).whereIn("entity_ref", function parents(builder) {
2558
- return builder.from("refresh_state").innerJoin("refresh_state_references", {
2559
- "refresh_state_references.target_entity_ref": "refresh_state.entity_ref"
2560
- }).where("refresh_state.entity_id", "=", uid).select("refresh_state_references.source_entity_ref");
2561
- });
2563
+ const dbConfig = this.database.client.config;
2564
+ if (dbConfig.client.includes("mysql")) {
2565
+ const results = await this.database("refresh_state").select("entity_id").whereIn("entity_ref", function parents(builder) {
2566
+ return builder.from("refresh_state").innerJoin(
2567
+ "refresh_state_references",
2568
+ {
2569
+ "refresh_state_references.target_entity_ref": "refresh_state.entity_ref"
2570
+ }
2571
+ ).where("refresh_state.entity_id", "=", uid).select("refresh_state_references.source_entity_ref");
2572
+ });
2573
+ await this.database("refresh_state").update({
2574
+ result_hash: "child-was-deleted",
2575
+ next_update_at: this.database.fn.now()
2576
+ }).whereIn(
2577
+ "entity_id",
2578
+ results.map((key) => key.entity_id)
2579
+ );
2580
+ } else {
2581
+ await this.database("refresh_state").update({
2582
+ result_hash: "child-was-deleted",
2583
+ next_update_at: this.database.fn.now()
2584
+ }).whereIn("entity_ref", function parents(builder) {
2585
+ return builder.from("refresh_state").innerJoin(
2586
+ "refresh_state_references",
2587
+ {
2588
+ "refresh_state_references.target_entity_ref": "refresh_state.entity_ref"
2589
+ }
2590
+ ).where("refresh_state.entity_id", "=", uid).select("refresh_state_references.source_entity_ref");
2591
+ });
2592
+ }
2562
2593
  const relationPeers = await this.database.from("relations").innerJoin("refresh_state", {
2563
2594
  "refresh_state.entity_ref": "relations.target_entity_ref"
2564
2595
  }).where("relations.originating_entity_id", "=", uid).andWhere("refresh_state.entity_id", "!=", uid).select({ ref: "relations.target_entity_ref" }).union(
@@ -2614,32 +2645,17 @@ class DefaultEntitiesCatalog {
2614
2645
  };
2615
2646
  }
2616
2647
  async facets(request) {
2617
- const { entities } = await this.entities({
2618
- filter: request.filter,
2619
- authorizationToken: request.authorizationToken
2620
- });
2621
2648
  const facets = {};
2649
+ const db = this.database;
2622
2650
  for (const facet of request.facets) {
2623
- const values = entities.map((entity) => {
2624
- var _a, _b;
2625
- if (facet.startsWith("metadata.annotations.")) {
2626
- return (_a = entity.metadata.annotations) == null ? void 0 : _a[facet.substring("metadata.annotations.".length)];
2627
- } else if (facet.startsWith("metadata.labels.")) {
2628
- return (_b = entity.metadata.labels) == null ? void 0 : _b[facet.substring("metadata.labels.".length)];
2629
- }
2630
- return lodash__default["default"].get(entity, facet);
2631
- }).flatMap((field) => {
2632
- if (typeof field === "string") {
2633
- return [field];
2634
- } else if (Array.isArray(field)) {
2635
- return field.filter((i) => typeof i === "string");
2636
- }
2637
- return [];
2638
- }).sort();
2639
- const counts = lodash__default["default"].countBy(values, lodash__default["default"].identity);
2640
- facets[facet] = Object.entries(counts).map(([value, count]) => ({
2641
- value,
2642
- count
2651
+ const dbQuery = db("search").join("final_entities", "search.entity_id", "final_entities.entity_id").where("search.key", facet.toLocaleLowerCase("en-US")).count("search.entity_id as count").select({ value: "search.original_value" }).groupBy("search.original_value");
2652
+ if (request == null ? void 0 : request.filter) {
2653
+ parseFilter(request.filter, dbQuery, db, false, "search.entity_id");
2654
+ }
2655
+ const result = await dbQuery;
2656
+ facets[facet] = result.map((data) => ({
2657
+ value: String(data.value),
2658
+ count: Number(data.count)
2643
2659
  }));
2644
2660
  }
2645
2661
  return { facets };
@@ -3082,15 +3098,30 @@ function mapToRows(input, entityId) {
3082
3098
  for (const { key: rawKey, value: rawValue } of input) {
3083
3099
  const key = rawKey.toLocaleLowerCase("en-US");
3084
3100
  if (rawValue === void 0 || rawValue === null) {
3085
- result.push({ entity_id: entityId, key, value: null });
3101
+ result.push({
3102
+ entity_id: entityId,
3103
+ key,
3104
+ original_value: null,
3105
+ value: null
3106
+ });
3086
3107
  } else {
3087
3108
  const value = String(rawValue).toLocaleLowerCase("en-US");
3088
3109
  if (key.length <= MAX_KEY_LENGTH) {
3089
- result.push({
3090
- entity_id: entityId,
3091
- key,
3092
- value: value.length <= MAX_VALUE_LENGTH ? value : null
3093
- });
3110
+ if (value.length <= MAX_VALUE_LENGTH) {
3111
+ result.push({
3112
+ entity_id: entityId,
3113
+ key,
3114
+ original_value: String(rawValue),
3115
+ value
3116
+ });
3117
+ } else {
3118
+ result.push({
3119
+ entity_id: entityId,
3120
+ key,
3121
+ original_value: null,
3122
+ value: null
3123
+ });
3124
+ }
3094
3125
  }
3095
3126
  }
3096
3127
  }
@@ -3808,13 +3839,20 @@ class Connection {
3808
3839
  });
3809
3840
  } else if (mutation.type === "delta") {
3810
3841
  this.check(mutation.added.map((e) => e.entity));
3811
- this.check(mutation.removed.map((e) => e.entity));
3842
+ this.check(
3843
+ mutation.removed.map((e) => "entity" in e ? e.entity : void 0).filter((e) => Boolean(e))
3844
+ );
3812
3845
  await db.transaction(async (tx) => {
3813
3846
  await db.replaceUnprocessedEntities(tx, {
3814
3847
  sourceKey: this.config.id,
3815
3848
  type: "delta",
3816
3849
  added: mutation.added,
3817
- removed: mutation.removed
3850
+ removed: mutation.removed.map(
3851
+ (r) => "entityRef" in r ? r : {
3852
+ entityRef: catalogModel.stringifyEntityRef(r.entity),
3853
+ locationKey: r.locationKey
3854
+ }
3855
+ )
3818
3856
  });
3819
3857
  });
3820
3858
  }
@@ -4444,13 +4482,13 @@ const catalogPlugin = backendPluginApi.createBackendPlugin({
4444
4482
  );
4445
4483
  env.registerInit({
4446
4484
  deps: {
4447
- logger: backendPluginApi.loggerServiceRef,
4448
- config: backendPluginApi.configServiceRef,
4449
- reader: backendPluginApi.urlReaderServiceRef,
4450
- permissions: backendPluginApi.permissionsServiceRef,
4451
- database: backendPluginApi.databaseServiceRef,
4452
- httpRouter: backendPluginApi.httpRouterServiceRef,
4453
- lifecycle: backendPluginApi.lifecycleServiceRef
4485
+ logger: backendPluginApi.coreServices.logger,
4486
+ config: backendPluginApi.coreServices.config,
4487
+ reader: backendPluginApi.coreServices.urlReader,
4488
+ permissions: backendPluginApi.coreServices.permissions,
4489
+ database: backendPluginApi.coreServices.database,
4490
+ httpRouter: backendPluginApi.coreServices.httpRouter,
4491
+ lifecycle: backendPluginApi.coreServices.lifecycle
4454
4492
  },
4455
4493
  async init({
4456
4494
  logger,