From 9eba1f4847c5d38a2f72f211976fd6c8c8078fd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uro=C5=A1=20Marolt?= Date: Wed, 22 Apr 2026 15:07:07 +0200 Subject: [PATCH 1/3] chore: handle logo url and description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Uroš Marolt --- .../src/consumer/pccProjectConsumer.ts | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/services/apps/pcc_sync_worker/src/consumer/pccProjectConsumer.ts b/services/apps/pcc_sync_worker/src/consumer/pccProjectConsumer.ts index 15961ac436..32ebcc6256 100644 --- a/services/apps/pcc_sync_worker/src/consumer/pccProjectConsumer.ts +++ b/services/apps/pcc_sync_worker/src/consumer/pccProjectConsumer.ts @@ -560,19 +560,23 @@ async function upsertInsightsProject( // Slug is intentionally not updated — it is a stable identifier referenced by FK from // securityInsightsEvaluations and related tables. - // logoUrl won't be updated in InsightsProject until we confirm that the format is - // compatible with the Insights Squared standard. Do NOT reintroduce it as a - // `--`-commented SQL line: pg-promise scans placeholders textually and would still - // require the `logoUrl` param, triggering "Property 'logoUrl' doesn't exist". + // description: COALESCE keeps existing when PCC sends null (CM-1131). + // logoUrl: COALESCE("logoUrl", …) never overrides an existing logo; only fills missing ones (CM-1131). try { await db.none( `UPDATE "insightsProjects" SET name = $(name), - description = $(description), + description = COALESCE($(description), description), + "logoUrl" = COALESCE("logoUrl", $(logoUrl)), "updatedAt" = NOW() WHERE "segmentId" = $(segmentId) AND "deletedAt" IS NULL`, - { segmentId, name: project.name, description: project.description }, + { + segmentId, + name: project.name, + description: project.description, + logoUrl: project.logoUrl, + }, ) } catch (err) { if (isDuplicateKeyError(err)) return true @@ -609,12 +613,11 @@ async function upsertInsightsProject( ) if (conflicting) return true - // logoUrl intentionally omitted from the INSERT column list — see note above. try { await db.none( - `INSERT INTO "insightsProjects" (name, slug, description, "segmentId", "isLF") - VALUES ($(name), generate_slug('insightsProjects', $(name)), $(description), $(segmentId), TRUE)`, - { name: project.name, description: project.description, segmentId }, + `INSERT INTO "insightsProjects" (name, slug, description, "logoUrl", "segmentId", "isLF") + VALUES ($(name), generate_slug('insightsProjects', $(name)), $(description), $(logoUrl), $(segmentId), TRUE)`, + { name: project.name, description: project.description, logoUrl: project.logoUrl, segmentId }, ) } catch (err) { if (isDuplicateKeyError(err)) return true From 9d94701b401d6d3e3eed04f19048d2ac7c8864cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uro=C5=A1=20Marolt?= Date: Wed, 22 Apr 2026 15:09:58 +0200 Subject: [PATCH 2/3] fix: coalesce desc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Uroš Marolt --- .../apps/pcc_sync_worker/src/consumer/pccProjectConsumer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/apps/pcc_sync_worker/src/consumer/pccProjectConsumer.ts b/services/apps/pcc_sync_worker/src/consumer/pccProjectConsumer.ts index 32ebcc6256..bd4d0e351a 100644 --- a/services/apps/pcc_sync_worker/src/consumer/pccProjectConsumer.ts +++ b/services/apps/pcc_sync_worker/src/consumer/pccProjectConsumer.ts @@ -513,7 +513,7 @@ async function upsertSegment( SET name = $(name), status = COALESCE($(status)::"segmentsStatus_type", status), maturity = $(maturity), - description = $(description), + description = COALESCE($(description), description), "updatedAt" = NOW() WHERE "sourceId" = $(sourceId) AND "tenantId" = $(tenantId)`, { From b42569309f56a93d56163e7c95fa5b51d6d40777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uro=C5=A1=20Marolt?= Date: Wed, 22 Apr 2026 20:59:48 +0200 Subject: [PATCH 3/3] fix: comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Uroš Marolt --- .../src/consumer/pccProjectConsumer.ts | 65 ++++++++++++------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/services/apps/pcc_sync_worker/src/consumer/pccProjectConsumer.ts b/services/apps/pcc_sync_worker/src/consumer/pccProjectConsumer.ts index bd4d0e351a..4462582bed 100644 --- a/services/apps/pcc_sync_worker/src/consumer/pccProjectConsumer.ts +++ b/services/apps/pcc_sync_worker/src/consumer/pccProjectConsumer.ts @@ -298,7 +298,7 @@ export class PccProjectConsumer { ) return { action: 'SKIPPED' } } - segment = fallback as SegmentRow | null + segment = fallback } // Step 3: no match → SKIP (Phase 1: project doesn't exist in CDP yet) @@ -562,22 +562,29 @@ async function upsertInsightsProject( // securityInsightsEvaluations and related tables. // description: COALESCE keeps existing when PCC sends null (CM-1131). // logoUrl: COALESCE("logoUrl", …) never overrides an existing logo; only fills missing ones (CM-1131). + // + // Wrapped in db.tx() so that when called inside an outer transaction (ITask), pg-promise + // creates a SAVEPOINT. A 23505 failure rolls back only the savepoint, leaving the outer + // transaction intact. Without this, a caught PG error still leaves the transaction in + // an aborted state and all subsequent queries on the same tx would fail. try { - await db.none( - `UPDATE "insightsProjects" - SET name = $(name), - description = COALESCE($(description), description), - "logoUrl" = COALESCE("logoUrl", $(logoUrl)), - "updatedAt" = NOW() - WHERE "segmentId" = $(segmentId) - AND "deletedAt" IS NULL`, - { - segmentId, - name: project.name, - description: project.description, - logoUrl: project.logoUrl, - }, - ) + await db.tx(async (t) => { + await t.none( + `UPDATE "insightsProjects" + SET name = $(name), + description = COALESCE($(description), description), + "logoUrl" = COALESCE("logoUrl", $(logoUrl)), + "updatedAt" = NOW() + WHERE "segmentId" = $(segmentId) + AND "deletedAt" IS NULL`, + { + segmentId, + name: project.name, + description: project.description, + logoUrl: project.logoUrl, + }, + ) + }) } catch (err) { if (isDuplicateKeyError(err)) return true throw err @@ -613,14 +620,28 @@ async function upsertInsightsProject( ) if (conflicting) return true + // Same savepoint rationale as the UPDATE path above. try { - await db.none( - `INSERT INTO "insightsProjects" (name, slug, description, "logoUrl", "segmentId", "isLF") - VALUES ($(name), generate_slug('insightsProjects', $(name)), $(description), $(logoUrl), $(segmentId), TRUE)`, - { name: project.name, description: project.description, logoUrl: project.logoUrl, segmentId }, - ) + await db.tx(async (t) => { + await t.none( + `INSERT INTO "insightsProjects" (name, slug, description, "logoUrl", "segmentId", "isLF") + VALUES ($(name), generate_slug('insightsProjects', $(name)), $(description), $(logoUrl), $(segmentId), TRUE)`, + { + name: project.name, + description: project.description, + logoUrl: project.logoUrl, + segmentId, + }, + ) + }) } catch (err) { - if (isDuplicateKeyError(err)) return true + if (isDuplicateKeyError(err)) { + // unique_project_segmentId: another worker already inserted a row for this segment + // concurrently — treat as "already represented", no conflict to record. + const constraintName = (err as { constraint?: string }).constraint + if (constraintName === 'unique_project_segmentId') return false + return true + } throw err } return false