History

Split integration name from description; backfill cloud data clean

Production data confirms `integration.description` was universally the display NAME (367 rows: avg 13 chars, ~0 real sentences), never a description. - description is now nullable — an actual, optional description distinct from the name. - ONE cloud migration 0005: add name + config_revised_at (+ connection description/tools_synced_at), DROP NOT NULL on description, then backfill name = description (367/367 get a real name, verified none empty) and clear description to NULL so no row carries a duplicated title. name stays column-nullable on purpose: SQLite boot-ensure hosts (self-host, Cloudflare, local) cannot add a NOT NULL column to an existing table, so the read path keeps the name ?? description ?? slug fallback.

Rhys Sullivan committed 4 months ago

Commit 827b06c1 parent 1da1a886 files · +24 −9Browse files

Unified · Split

6 files
  1. apps/cloud/drizzle/0005_integration_descriptions.sql +7 −1
  2. apps/cloud/drizzle/meta/0005_snapshot.json +2 −2
  3. apps/cloud/drizzle/meta/_journal.json +1 −1
  4. apps/cloud/src/db/executor-schema.ts +1 −1
  5. packages/core/sdk/src/core-schema.ts +10 −3
  6. packages/core/sdk/src/executor.ts +3 −1
apps/cloud/drizzle/0005_integration_descriptions.sql+7−1
@@ -1,4 +1,10 @@
1ALTER TABLE "integration" ALTER COLUMN "description" DROP NOT NULL;--> statement-breakpoint
1ALTER TABLE "connection" ADD COLUMN "description" text;--> statement-breakpoint2ALTER TABLE "connection" ADD COLUMN "description" text;--> statement-breakpoint
2ALTER TABLE "connection" ADD COLUMN "tools_synced_at" bigint;--> statement-breakpoint3ALTER TABLE "connection" ADD COLUMN "tools_synced_at" bigint;--> statement-breakpoint
3ALTER TABLE "integration" ADD COLUMN "name" text;--> statement-breakpoint4ALTER TABLE "integration" ADD COLUMN "name" text;--> statement-breakpoint
4ALTER TABLE "integration" ADD COLUMN "config_revised_at" bigint;5ALTER TABLE "integration" ADD COLUMN "config_revised_at" bigint;--> statement-breakpoint
6-- Pre-split rows kept the display name in `description`. Move it to `name`
7-- (its proper home), then clear `description` so it no longer carries a
8-- duplicated title — it now means an actual, optional description.
9UPDATE "integration" SET "name" = "description" WHERE "name" IS NULL;--> statement-breakpoint
10UPDATE "integration" SET "description" = NULL;
apps/cloud/drizzle/meta/0005_snapshot.json+2−2
@@ -1,5 +1,5 @@
1{1{
2 "id": "ab75bf05-6332-4d53-8db4-f131b67773ef",2 "id": "e105c61d-2fde-4d2e-a785-a74fafd0f4ea",
3 "prevId": "1187b7aa-fdd6-412a-bcfb-20d6ea8f8644",3 "prevId": "1187b7aa-fdd6-412a-bcfb-20d6ea8f8644",
4 "version": "7",4 "version": "7",
5 "dialect": "postgresql",5 "dialect": "postgresql",
@@ -517,7 +517,7 @@ · 511 unmodified lines
517 "name": "description",517 "name": "description",
518 "type": "text",518 "type": "text",
519 "primaryKey": false,519 "primaryKey": false,
520 "notNull": true520 "notNull": false
521 },521 },
522 "config_revised_at": {522 "config_revised_at": {
523 "name": "config_revised_at",523 "name": "config_revised_at",
apps/cloud/drizzle/meta/_journal.json+1−1
@@ -40,7 +40,7 @@ · 39 unmodified lines
40 {40 {
41 "idx": 5,41 "idx": 5,
42 "version": "7",42 "version": "7",
43 "when": 1781485927974,43 "when": 1781491100009,
44 "tag": "0005_integration_descriptions",44 "tag": "0005_integration_descriptions",
45 "breakpoints": true45 "breakpoints": true
46 }46 }
apps/cloud/src/db/executor-schema.ts+1−1
@@ -16,7 +16,7 @@ export const integration = pgTable( · 15 unmodified lines
16 slug: varchar("slug", { length: 255 }).notNull(),16 slug: varchar("slug", { length: 255 }).notNull(),
17 plugin_id: text("plugin_id").notNull(),17 plugin_id: text("plugin_id").notNull(),
18 name: text("name"),18 name: text("name"),
19 description: text("description").notNull(),19 description: text("description"),
20 config_revised_at: bigint("config_revised_at", { mode: "bigint" }),20 config_revised_at: bigint("config_revised_at", { mode: "bigint" }),
21 config: json("config"),21 config: json("config"),
22 can_remove: boolean("can_remove").notNull().default(true),22 can_remove: boolean("can_remove").notNull().default(true),
packages/core/sdk/src/core-schema.ts+10−3
@@ -120,10 +120,17 @@ export const coreTables = defineTables({ · 119 unmodified lines
120 {120 {
121 slug: keyColumn("slug"),121 slug: keyColumn("slug"),
122 plugin_id: textColumn("plugin_id"),122 plugin_id: textColumn("plugin_id"),
123 // Display name. Nullable for pre-split rows, where `description` held123 // Display name. The pre-split field: `description` used to hold the
124 // the name; readers fall back (see rowToIntegration).124 // name, so cloud backfills `name` from it (migration 0006) and other
125 // hosts fall back at read time (see rowToIntegration). Nullable because
126 // SQLite boot-ensure hosts cannot add a NOT NULL column to existing
127 // tables, so the column stays nullable even though it is always present
128 // in practice.
125 name: nullableTextColumn("name"),129 name: nullableTextColumn("name"),
126 description: textColumn("description"),130 // Actual prose description, now distinct from the name. Nullable: absent
131 // until a user/spec supplies one (cloud clears the old duplicated title
132 // to NULL in 0006).
133 description: nullableTextColumn("description"),
127 config: nullableJsonColumn("config"),134 config: nullableJsonColumn("config"),
128 // Epoch ms of the last tool-affecting config change (spec update, auth135 // Epoch ms of the last tool-affecting config change (spec update, auth
129 // template edit). Compared against each connection's `tools_synced_at`136 // template edit). Compared against each connection's `tools_synced_at`
packages/core/sdk/src/executor.ts+3−1
@@ -449,7 +449,9 @@ const rowToIntegration = ( · 448 unmodified lines
449 slug: IntegrationSlug.make(row.slug),449 slug: IntegrationSlug.make(row.slug),
450 // Pre-split rows have no `name`; their description WAS the display name.450 // Pre-split rows have no `name`; their description WAS the display name.
451 name: row.name ?? row.description ?? row.slug,451 name: row.name ?? row.description ?? row.slug,
452 description: row.description,452 // `description` is now nullable (cleared where it only held a duplicated
453 // title); present it as "" so the public Integration type stays a string.
454 description: row.description ?? "",
453 kind: row.plugin_id,455 kind: row.plugin_id,
454 canRemove: Boolean(row.can_remove),456 canRemove: Boolean(row.can_remove),
455 canRefresh: Boolean(row.can_refresh),457 canRefresh: Boolean(row.can_refresh),