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 @@
1+ALTER TABLE "integration" ALTER COLUMN "description" DROP NOT NULL;--> statement-breakpoint
12 ALTER TABLE "connection" ADD COLUMN "description" text;--> statement-breakpoint
23 ALTER TABLE "connection" ADD COLUMN "tools_synced_at" bigint;--> statement-breakpoint
34 ALTER TABLE "integration" ADD COLUMN "name" text;--> statement-breakpoint
4−ALTER TABLE "integration" ADD COLUMN "config_revised_at" bigint;
5+ALTER 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.
9+UPDATE "integration" SET "name" = "description" WHERE "name" IS NULL;--> statement-breakpoint
10+UPDATE "integration" SET "description" = NULL;
apps/cloud/drizzle/meta/0005_snapshot.json+2−2
@@ -1,5 +1,5 @@
11 {
2− "id": "ab75bf05-6332-4d53-8db4-f131b67773ef",
2+ "id": "e105c61d-2fde-4d2e-a785-a74fafd0f4ea",
33 "prevId": "1187b7aa-fdd6-412a-bcfb-20d6ea8f8644",
44 "version": "7",
55 "dialect": "postgresql",
@@ -517,7 +517,7 @@ · 511 unmodified lines
517517 "name": "description",
518518 "type": "text",
519519 "primaryKey": false,
520− "notNull": true
520+ "notNull": false
521521 },
522522 "config_revised_at": {
523523 "name": "config_revised_at",
apps/cloud/drizzle/meta/_journal.json+1−1
@@ -40,7 +40,7 @@ · 39 unmodified lines
4040 {
4141 "idx": 5,
4242 "version": "7",
43− "when": 1781485927974,
43+ "when": 1781491100009,
4444 "tag": "0005_integration_descriptions",
4545 "breakpoints": true
4646 }
apps/cloud/src/db/executor-schema.ts+1−1
@@ -16,7 +16,7 @@ export const integration = pgTable( · 15 unmodified lines
1616 slug: varchar("slug", { length: 255 }).notNull(),
1717 plugin_id: text("plugin_id").notNull(),
1818 name: text("name"),
19− description: text("description").notNull(),
19+ description: text("description"),
2020 config_revised_at: bigint("config_revised_at", { mode: "bigint" }),
2121 config: json("config"),
2222 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
120120 {
121121 slug: keyColumn("slug"),
122122 plugin_id: textColumn("plugin_id"),
123− // Display name. Nullable for pre-split rows, where `description` held
124− // the name; readers fall back (see rowToIntegration).
123+ // Display name. The pre-split field: `description` used to hold the
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.
125129 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"),
127134 config: nullableJsonColumn("config"),
128135 // Epoch ms of the last tool-affecting config change (spec update, auth
129136 // 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
449449 slug: IntegrationSlug.make(row.slug),
450450 // Pre-split rows have no `name`; their description WAS the display name.
451451 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 ?? "",
453455 kind: row.plugin_id,
454456 canRemove: Boolean(row.can_remove),
455457 canRefresh: Boolean(row.can_refresh),