Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
183 lines
6.1 KiB
TypeScript
Executable File
183 lines
6.1 KiB
TypeScript
Executable File
#!/usr/bin/env ts-node
|
|
/**
|
|
* Script to verify tag migration integrity
|
|
* Run this after migration to ensure data consistency
|
|
*/
|
|
|
|
import { createTenantKnex } from '../src/lib/db';
|
|
|
|
async function verifyTagMigration() {
|
|
const { knex } = await createTenantKnex();
|
|
|
|
try {
|
|
console.log('Starting tag migration verification...\n');
|
|
|
|
// 1. Count comparison
|
|
console.log('1. Verifying record counts...');
|
|
const [{ old_count }] = await knex('tags').count('* as old_count');
|
|
const [{ new_count }] = await knex('tag_mappings').count('* as new_count');
|
|
const [{ def_count }] = await knex('tag_definitions').count('* as def_count');
|
|
|
|
console.log(` Old tags table: ${old_count} records`);
|
|
console.log(` New mappings table: ${new_count} records`);
|
|
console.log(` Tag definitions: ${def_count} unique tags`);
|
|
|
|
if (old_count !== new_count) {
|
|
console.error(` ❌ Count mismatch! Expected ${old_count} mappings, found ${new_count}`);
|
|
} else {
|
|
console.log(` ✓ Record counts match`);
|
|
}
|
|
|
|
// 2. Check for orphaned mappings
|
|
console.log('\n2. Checking for orphaned mappings...');
|
|
const orphanedMappings = await knex.raw(`
|
|
SELECT COUNT(*) as count
|
|
FROM tag_mappings tm
|
|
LEFT JOIN tag_definitions td ON tm.tenant = td.tenant AND tm.tag_id = td.tag_id
|
|
WHERE td.tag_id IS NULL
|
|
`);
|
|
|
|
if (orphanedMappings.rows[0].count > 0) {
|
|
console.error(` ❌ Found ${orphanedMappings.rows[0].count} orphaned mappings`);
|
|
} else {
|
|
console.log(` ✓ No orphaned mappings found`);
|
|
}
|
|
|
|
// 3. Verify unique constraints
|
|
console.log('\n3. Verifying unique constraints...');
|
|
const duplicateDefinitions = await knex.raw(`
|
|
SELECT tenant, tag_text, tagged_type, COUNT(*) as count
|
|
FROM tag_definitions
|
|
GROUP BY tenant, tag_text, tagged_type
|
|
HAVING COUNT(*) > 1
|
|
`);
|
|
|
|
if (duplicateDefinitions.rows.length > 0) {
|
|
console.error(` ❌ Found ${duplicateDefinitions.rows.length} duplicate tag definitions`);
|
|
console.error(' Duplicates:', duplicateDefinitions.rows);
|
|
} else {
|
|
console.log(` ✓ No duplicate tag definitions`);
|
|
}
|
|
|
|
// 4. Sample data verification
|
|
console.log('\n4. Verifying sample data integrity...');
|
|
const sampleOldTags = await knex('tags')
|
|
.orderBy(knex.raw('RANDOM()'))
|
|
.limit(10);
|
|
|
|
let dataIntegrityPassed = true;
|
|
for (const oldTag of sampleOldTags) {
|
|
// Find corresponding mapping
|
|
const mapping = await knex('tag_mappings')
|
|
.where('mapping_id', oldTag.tag_id)
|
|
.first();
|
|
|
|
if (!mapping) {
|
|
console.error(` ❌ No mapping found for old tag ${oldTag.tag_id}`);
|
|
dataIntegrityPassed = false;
|
|
continue;
|
|
}
|
|
|
|
// Find corresponding definition
|
|
const definition = await knex('tag_definitions')
|
|
.where('tag_id', mapping.tag_id)
|
|
.where('tenant', mapping.tenant)
|
|
.first();
|
|
|
|
if (!definition) {
|
|
console.error(` ❌ No definition found for mapping ${mapping.mapping_id}`);
|
|
dataIntegrityPassed = false;
|
|
continue;
|
|
}
|
|
|
|
// Verify data matches
|
|
if (definition.tag_text.toLowerCase() !== oldTag.tag_text.toLowerCase()) {
|
|
console.error(` ❌ Text mismatch: "${oldTag.tag_text}" vs "${definition.tag_text}"`);
|
|
dataIntegrityPassed = false;
|
|
}
|
|
|
|
if (mapping.tagged_id !== oldTag.tagged_id) {
|
|
console.error(` ❌ Tagged ID mismatch for tag ${oldTag.tag_id}`);
|
|
dataIntegrityPassed = false;
|
|
}
|
|
}
|
|
|
|
if (dataIntegrityPassed) {
|
|
console.log(` ✓ Sample data integrity verified`);
|
|
}
|
|
|
|
// 5. Performance comparison
|
|
console.log('\n5. Performance comparison...');
|
|
|
|
// Old system query
|
|
const oldStart = Date.now();
|
|
await knex('tags')
|
|
.where('tenant', sampleOldTags[0]?.tenant || '')
|
|
.where('tagged_type', 'ticket')
|
|
.select('*');
|
|
const oldTime = Date.now() - oldStart;
|
|
|
|
// New system query
|
|
const newStart = Date.now();
|
|
await knex('tag_mappings as tm')
|
|
.join('tag_definitions as td', function() {
|
|
this.on('tm.tenant', '=', 'td.tenant')
|
|
.andOn('tm.tag_id', '=', 'td.tag_id');
|
|
})
|
|
.where('tm.tenant', sampleOldTags[0]?.tenant || '')
|
|
.where('tm.tagged_type', 'ticket')
|
|
.select('*');
|
|
const newTime = Date.now() - newStart;
|
|
|
|
console.log(` Old system query time: ${oldTime}ms`);
|
|
console.log(` New system query time: ${newTime}ms`);
|
|
console.log(` Performance difference: ${((newTime - oldTime) / oldTime * 100).toFixed(1)}%`);
|
|
|
|
// 6. Storage analysis
|
|
console.log('\n6. Storage analysis...');
|
|
const storageAnalysis = await knex.raw(`
|
|
SELECT
|
|
'tags' as table_name,
|
|
pg_size_pretty(pg_total_relation_size('tags')) as total_size,
|
|
pg_size_pretty(pg_relation_size('tags')) as table_size,
|
|
pg_size_pretty(pg_indexes_size('tags')) as indexes_size
|
|
UNION ALL
|
|
SELECT
|
|
'tag_definitions + tag_mappings' as table_name,
|
|
pg_size_pretty(
|
|
pg_total_relation_size('tag_definitions') +
|
|
pg_total_relation_size('tag_mappings')
|
|
) as total_size,
|
|
pg_size_pretty(
|
|
pg_relation_size('tag_definitions') +
|
|
pg_relation_size('tag_mappings')
|
|
) as table_size,
|
|
pg_size_pretty(
|
|
pg_indexes_size('tag_definitions') +
|
|
pg_indexes_size('tag_mappings')
|
|
) as indexes_size
|
|
`);
|
|
|
|
console.log(' Storage comparison:');
|
|
storageAnalysis.rows.forEach((row: any) => {
|
|
console.log(` ${row.table_name}:`);
|
|
console.log(` Total: ${row.total_size}`);
|
|
console.log(` Table: ${row.table_size}`);
|
|
console.log(` Indexes: ${row.indexes_size}`);
|
|
});
|
|
|
|
console.log('\n✅ Tag migration verification complete!');
|
|
|
|
} catch (error) {
|
|
console.error('Error during verification:', error);
|
|
} finally {
|
|
await knex.destroy();
|
|
}
|
|
}
|
|
|
|
// Run if called directly
|
|
if (require.main === module) {
|
|
verifyTagMigration().catch(console.error);
|
|
}
|
|
|
|
export { verifyTagMigration }; |