ToolJet/server/data-migrations/1774589474535-BackfillFontAndHoverStylesForButtonWidgets.ts
Kavin Venkatachalam 2a7b1a8272
Perf: Optimizes component backfill migration (#15713)
* Added Hover Background option for button, button group, modal trigger btn, popover menu button

* Added FontSize style property to Button, Button group, Popover menu, Modal Trigger btn. Adjusted line height and icon size accordingly

* Added FontWeight to Button, Button Group, Popover Menu, Modal Trigger Button

* Added Content Alignment for Button, Popover menu and Modal Trigger btn

* Added Hover Toggle for Button , Button Group, Popover menu and Modal Trigger Btn

* Reverted changes to button group legacy

* Added the properties to the new Button Group

* Resolved copilot comments

* Removed Content Alignment from Button group and changed the accordian for hover bg

* Created the migration and the import-export service

* perf: Replace placeholder text color migration with optimized version using cursor pagination

* fix: use result[1] for affected row count in batch DELETE

entityManager.query() returns [rows, rowCount] for DELETE/UPDATE
statements, not a QueryResult object. result.rowCount is undefined,
causing the cleanup loop to exit after the first batch.

* update: bump subproject commits for frontend and server components

* fix: include NULL styles rows in placeholder text color backfill

NOT (styles::jsonb ? 'key') yields NULL when styles IS NULL, so those
rows were silently skipped. Added explicit OR styles IS NULL to catch them.

* chore: use @helpers path alias for migration.helper import

* fix: implement two-pass delete for app history to avoid FK violations

* Revert "Reverted placeholder color changes (#15659)"

This reverts commit 8a458cfcbd.

* refactor: remove unused migration for backfilling placeholder text color

* refactor: button style migration and added the progess

---------

Co-authored-by: Rahul <rahulrnc03@gmail.com>
Co-authored-by: Shaurya Sharma <shaurya064@gmail.com>
Co-authored-by: Johnson Cherian <johnsonc.dev@gmail.com>
2026-03-27 15:54:50 +05:30

110 lines
4.1 KiB
TypeScript

import { MigrationInterface, QueryRunner } from 'typeorm';
import { deleteAppHistoryForStructuralMigration } from '@helpers/migration.helper';
const MIGRATION_NAME = 'BackfillFontAndHoverStylesForButtonWidgets1774589474535';
const BATCH_SIZE = 2000;
const COMPONENT_TYPES = ['Button', 'ButtonGroupV2', 'ModalV2', 'PopoverMenu'];
// Patch applied to Button and PopoverMenu (5 props)
const BUTTON_POPOVER_PATCH = JSON.stringify({
textSize: { value: '{{14}}' },
fontWeight: { value: 'normal' },
contentAlignment: { value: 'center' },
hoverBackgroundMode: { value: 'auto' },
hoverBackgroundColor: { value: 'var(--cc-primary-brand)' },
});
// Patch applied to ButtonGroupV2 (4 props — no contentAlignment)
const BUTTON_GROUP_PATCH = JSON.stringify({
textSize: { value: '{{14}}' },
fontWeight: { value: 'normal' },
hoverBackgroundMode: { value: 'auto' },
hoverBackgroundColor: { value: 'var(--cc-primary-brand)' },
});
// Patch applied to ModalV2 (5 props with triggerButton prefix)
const MODAL_V2_PATCH = JSON.stringify({
triggerButtonTextSize: { value: '{{14}}' },
triggerButtonFontWeight: { value: 'normal' },
triggerButtonContentAlignment: { value: 'center' },
triggerButtonHoverBackgroundMode: { value: 'auto' },
triggerButtonHoverBackgroundColor: { value: 'var(--cc-primary-brand)' },
});
export class BackfillFontAndHoverStylesForButtonWidgets1774589474535 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
const [{ count }] = await queryRunner.query(
`SELECT COUNT(*) FROM components
WHERE type = ANY($1)
AND (
(type IN ('Button', 'ButtonGroupV2', 'PopoverMenu') AND (styles IS NULL OR NOT (styles::jsonb ? 'textSize')))
OR (type = 'ModalV2' AND (styles IS NULL OR NOT (styles::jsonb ? 'triggerButtonTextSize')))
)`,
[COMPONENT_TYPES]
);
const total = parseInt(count, 10);
console.log(`${MIGRATION_NAME}: [START] Backfill font and hover styles | Total: ${total}`);
let lastId = '00000000-0000-0000-0000-000000000000';
let totalUpdated = 0;
while (true) {
const rows: { id: string }[] = await queryRunner.query(
`SELECT id FROM components
WHERE type = ANY($1)
AND id > $2
AND (
(type IN ('Button', 'ButtonGroupV2', 'PopoverMenu') AND (styles IS NULL OR NOT (styles::jsonb ? 'textSize')))
OR (type = 'ModalV2' AND (styles IS NULL OR NOT (styles::jsonb ? 'triggerButtonTextSize')))
)
ORDER BY id ASC
LIMIT $3`,
[COMPONENT_TYPES, lastId, BATCH_SIZE]
);
if (rows.length === 0) break;
lastId = rows[rows.length - 1].id;
const ids = rows.map((r) => r.id);
await queryRunner.query(
`UPDATE components
SET styles = (COALESCE(styles, '{}')::jsonb ||
CASE
WHEN type = 'ButtonGroupV2' THEN $1::jsonb
WHEN type = 'ModalV2' THEN $2::jsonb
ELSE $3::jsonb
END
)::json
WHERE id = ANY($4::uuid[])`,
[BUTTON_GROUP_PATCH, MODAL_V2_PATCH, BUTTON_POPOVER_PATCH, ids]
);
totalUpdated += rows.length;
const percentage = total > 0 ? ((totalUpdated / total) * 100).toFixed(1) : '0.0';
console.log(`${MIGRATION_NAME}: [PROGRESS] ${totalUpdated}/${total} (${percentage}%)`);
}
console.log(`${MIGRATION_NAME}: [SUCCESS] Backfill font and hover styles finished.`);
if (totalUpdated > 0) {
const appVersionRows: { app_version_id: string }[] = await queryRunner.query(
`SELECT DISTINCT p.app_version_id
FROM components c
INNER JOIN pages p ON c.page_id = p.id
WHERE c.type = ANY($1)`,
[COMPONENT_TYPES]
);
await deleteAppHistoryForStructuralMigration(
queryRunner.manager,
{ appVersionIds: appVersionRows.map((r) => r.app_version_id) },
MIGRATION_NAME
);
}
}
public async down(_queryRunner: QueryRunner): Promise<void> {
// Intentional no-op — structural migrations are not reversed
}
}