From bc36583598718a741a66de434565723dbefc2b14 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 10 Oct 2025 16:44:08 +0000 Subject: [PATCH] Refactor: Approve test data generator tool use --- src/components/admin/TestDataGenerator.tsx | 33 ++++ src/integrations/supabase/types.ts | 13 +- supabase/functions/seed-test-data/index.ts | 141 +++++++++++++++--- ...5_dd0baeac-d6a3-449c-b42a-0a1e5ba48df3.sql | 10 ++ 4 files changed, 177 insertions(+), 20 deletions(-) create mode 100644 supabase/migrations/20251010164045_dd0baeac-d6a3-449c-b42a-0a1e5ba48df3.sql diff --git a/src/components/admin/TestDataGenerator.tsx b/src/components/admin/TestDataGenerator.tsx index d31587ec..1362376d 100644 --- a/src/components/admin/TestDataGenerator.tsx +++ b/src/components/admin/TestDataGenerator.tsx @@ -181,6 +181,39 @@ export function TestDataGenerator() { )} + + {/* Dependency warnings */} + {entityTypes.rides && stats.parks === 0 && !entityTypes.parks && ( + + + + Missing Dependency: You've selected "rides" but no parks exist. + Rides require parks to be created first. Either enable "parks" or generate parks separately first. + + + )} + + {entityTypes.ride_models && stats.manufacturers === 0 && !entityTypes.manufacturers && ( + + + + Missing Dependency: You've selected "ride models" but no manufacturers exist. + Ride models require manufacturers to be created first. Either enable "manufacturers" or generate manufacturers separately first. + + + )} + + {(entityTypes.parks || entityTypes.rides) && + stats.operators === 0 && stats.property_owners === 0 && + !entityTypes.operators && !entityTypes.property_owners && ( + + + + Optional Dependencies: Parks and rides can optionally link to operators and property owners. + Consider enabling "operators" and "property_owners" for more realistic test data with proper dependency chains. + + + )} )} diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts index 0aed41f9..9ffef2ae 100644 --- a/src/integrations/supabase/types.ts +++ b/src/integrations/supabase/types.ts @@ -2288,6 +2288,7 @@ export type Database = { entity_type: string id: string metadata: Json | null + submission_item_id: string | null test_session_id: string | null } Insert: { @@ -2297,6 +2298,7 @@ export type Database = { entity_type: string id?: string metadata?: Json | null + submission_item_id?: string | null test_session_id?: string | null } Update: { @@ -2306,9 +2308,18 @@ export type Database = { entity_type?: string id?: string metadata?: Json | null + submission_item_id?: string | null test_session_id?: string | null } - Relationships: [] + Relationships: [ + { + foreignKeyName: "test_data_registry_submission_item_id_fkey" + columns: ["submission_item_id"] + isOneToOne: false + referencedRelation: "submission_items" + referencedColumns: ["id"] + }, + ] } user_blocks: { Row: { diff --git a/supabase/functions/seed-test-data/index.ts b/supabase/functions/seed-test-data/index.ts index b9bed1fe..d471280f 100644 --- a/supabase/functions/seed-test-data/index.ts +++ b/supabase/functions/seed-test-data/index.ts @@ -76,12 +76,14 @@ async function registerTestEntity( entityType: string, slug: string, entityId: string, + submissionItemId: string, sessionId: string ) { const { error } = await supabase.from('test_data_registry').insert({ entity_type: entityType, entity_slug: slug, entity_id: entityId, + submission_item_id: submissionItemId, test_session_id: sessionId }); @@ -93,10 +95,10 @@ async function registerTestEntity( async function getExistingTestEntities( supabase: any, entityType: string -): Promise> { +): Promise> { const { data, error } = await supabase .from('test_data_registry') - .select('entity_slug, entity_id') + .select('entity_slug, entity_id, submission_item_id') .eq('entity_type', entityType); if (error) { @@ -107,6 +109,24 @@ async function getExistingTestEntities( return data || []; } +async function getPendingSubmissionItems( + supabase: any, + itemType: string +): Promise> { + const { data, error } = await supabase + .from('submission_items') + .select('id, item_data') + .eq('item_type', itemType) + .eq('status', 'pending'); + + if (error) { + console.error(`Error fetching pending ${itemType} items:`, error); + return []; + } + + return data || []; +} + Deno.serve(async (req) => { if (req.method === 'OPTIONS') { return new Response(null, { headers: corsHeaders }); @@ -165,6 +185,17 @@ Deno.serve(async (req) => { const startTime = Date.now(); const sessionId = crypto.randomUUID(); // Unique ID for this generation session const summary = { parks: 0, rides: 0, companies: 0, rideModels: 0, photos: 0, totalPhotoItems: 0, conflicts: 0, versionChains: 0 }; + + // Track submission item IDs for dependency resolution + const createdSubmissionItems: Record = { + operator: [], + property_owner: [], + manufacturer: [], + designer: [], + park: [], + ride_model: [] + }; + const createdParks: string[] = []; const createdCompanies: Record = { manufacturer: [], operator: [], designer: [], property_owner: [] }; const createdParkSlugs: string[] = []; @@ -181,20 +212,56 @@ Deno.serve(async (req) => { const existingParks = await getExistingTestEntities(supabase, 'park'); const existingRideModels = await getExistingTestEntities(supabase, 'ride_model'); - existingOperators.forEach(op => createdCompanies.operator.push(op.slug)); - existingOwners.forEach(own => createdCompanies.property_owner.push(own.slug)); - existingManufacturers.forEach(mfg => createdCompanies.manufacturer.push(mfg.slug)); - existingDesigners.forEach(des => createdCompanies.designer.push(des.slug)); + // Load pending submission items for dependency resolution + const pendingOperators = await getPendingSubmissionItems(supabase, 'operator'); + const pendingOwners = await getPendingSubmissionItems(supabase, 'property_owner'); + const pendingManufacturers = await getPendingSubmissionItems(supabase, 'manufacturer'); + const pendingDesigners = await getPendingSubmissionItems(supabase, 'designer'); + const pendingParks = await getPendingSubmissionItems(supabase, 'park'); + const pendingRideModels = await getPendingSubmissionItems(supabase, 'ride_model'); + + // Track both approved and pending items + existingOperators.forEach(op => { + createdCompanies.operator.push(op.slug); + if (op.submission_item_id) createdSubmissionItems.operator.push(op.submission_item_id); + }); + existingOwners.forEach(own => { + createdCompanies.property_owner.push(own.slug); + if (own.submission_item_id) createdSubmissionItems.property_owner.push(own.submission_item_id); + }); + existingManufacturers.forEach(mfg => { + createdCompanies.manufacturer.push(mfg.slug); + if (mfg.submission_item_id) createdSubmissionItems.manufacturer.push(mfg.submission_item_id); + }); + existingDesigners.forEach(des => { + createdCompanies.designer.push(des.slug); + if (des.submission_item_id) createdSubmissionItems.designer.push(des.submission_item_id); + }); existingParks.forEach(park => { createdParks.push(park.slug); createdParkSlugs.push(park.slug); + if (park.submission_item_id) createdSubmissionItems.park.push(park.submission_item_id); }); + // Add pending items to tracking + pendingOperators.forEach(item => createdSubmissionItems.operator.push(item.id)); + pendingOwners.forEach(item => createdSubmissionItems.property_owner.push(item.id)); + pendingManufacturers.forEach(item => createdSubmissionItems.manufacturer.push(item.id)); + pendingDesigners.forEach(item => createdSubmissionItems.designer.push(item.id)); + pendingParks.forEach(item => createdSubmissionItems.park.push(item.id)); + pendingRideModels.forEach(item => createdSubmissionItems.ride_model.push(item.id)); + console.log(`Loaded ${existingOperators.length} operators, ${existingOwners.length} owners, ${existingManufacturers.length} manufacturers, ${existingDesigners.length} designers, ${existingParks.length} parks`); + console.log(`Loaded ${pendingOperators.length} pending operators, ${pendingOwners.length} pending owners, ${pendingManufacturers.length} pending manufacturers, ${pendingDesigners.length} pending designers, ${pendingParks.length} pending parks`); } // Helper to create submission - async function createSubmission(userId: string, type: string, itemData: any, options: { escalated?: boolean; expiredLock?: boolean } = {}) { + async function createSubmission( + userId: string, + type: string, + itemData: any, + options: { escalated?: boolean; expiredLock?: boolean; dependsOn?: string } = {} + ) { const submissionId = crypto.randomUUID(); const itemId = crypto.randomUUID(); @@ -237,7 +304,8 @@ Deno.serve(async (req) => { item_type: type, item_data: itemData, status: 'pending', - order_index: 0 + order_index: 0, + depends_on: options.dependsOn || null }); if (itemError) throw itemError; @@ -260,10 +328,10 @@ Deno.serve(async (req) => { const { data: insertedData, error: typeError } = await supabase.from(table).insert(typeData).select('id').single(); if (typeError) throw typeError; - return { submissionId, typeId: insertedData?.id }; + return { submissionId, itemId, typeId: insertedData?.id }; } - return { submissionId, typeId: null }; + return { submissionId, itemId, typeId: null }; } // Create parks @@ -299,9 +367,16 @@ Deno.serve(async (req) => { parkData.phone = `+1-555-${randomInt(100, 999)}-${randomInt(1000, 9999)}`; } + let operatorItemId: string | undefined; + let ownerItemId: string | undefined; + if (level >= 3 && createdCompanies.operator.length > 0) { const { data: operatorData } = await supabase.from('companies').select('id').eq('slug', randomItem(createdCompanies.operator)).maybeSingle(); if (operatorData) parkData.operator_id = operatorData.id; + // Track operator dependency + if (createdSubmissionItems.operator.length > 0) { + operatorItemId = randomItem(createdSubmissionItems.operator); + } parkData.email = `info@test-park-${i + 1}.example.com`; parkData.card_image_id = `test-park-card-${i + 1}`; parkData.card_image_url = `https://imagedelivery.net/test/park-${i + 1}/card`; @@ -311,6 +386,10 @@ Deno.serve(async (req) => { if (createdCompanies.property_owner.length > 0) { const { data: ownerData } = await supabase.from('companies').select('id').eq('slug', randomItem(createdCompanies.property_owner)).maybeSingle(); if (ownerData) parkData.property_owner_id = ownerData.id; + // Track owner dependency (prefer operator if both exist) + if (createdSubmissionItems.property_owner.length > 0 && !operatorItemId) { + ownerItemId = randomItem(createdSubmissionItems.property_owner); + } } if (Math.random() > 0.9) { parkData.closing_date = randomDate(2000, 2024); @@ -322,10 +401,14 @@ Deno.serve(async (req) => { const options = { escalated: includeEscalated && Math.random() < 0.1, - expiredLock: includeExpiredLocks && Math.random() < 0.1 + expiredLock: includeExpiredLocks && Math.random() < 0.1, + dependsOn: operatorItemId || ownerItemId }; - await createSubmission(user.id, 'park', parkData, options); + const { itemId } = await createSubmission(user.id, 'park', parkData, options); + + // Track created submission item + createdSubmissionItems.park.push(itemId); // Register newly created park in the registry const { data: approvedPark } = await supabase @@ -335,7 +418,7 @@ Deno.serve(async (req) => { .maybeSingle(); if (approvedPark) { - await registerTestEntity(supabase, 'park', slug, approvedPark.id, sessionId); + await registerTestEntity(supabase, 'park', slug, approvedPark.id, itemId, sessionId); } createdParks.push(slug); @@ -382,7 +465,10 @@ Deno.serve(async (req) => { companyData.banner_image_url = `https://imagedelivery.net/test/${compType}-${i + 1}/banner`; } - await createSubmission(user.id, compType, companyData); + const { itemId } = await createSubmission(user.id, compType, companyData); + + // Track created submission item + createdSubmissionItems[compType].push(itemId); // Register newly created company in the registry const companySlug = `test-${compType}-${i + 1}`; @@ -393,7 +479,7 @@ Deno.serve(async (req) => { .maybeSingle(); if (approvedCompany) { - await registerTestEntity(supabase, compType, companySlug, approvedCompany.id, sessionId); + await registerTestEntity(supabase, compType, companySlug, approvedCompany.id, itemId, sessionId); } createdCompanies[compType].push(companySlug); @@ -420,6 +506,9 @@ Deno.serve(async (req) => { const parkSlug = randomItem(createdParks); const { data: parkData } = await supabase.from('parks').select('id').eq('slug', parkSlug).maybeSingle(); + + // Get park dependency for depends_on + const parkItemId = createdSubmissionItems.park.length > 0 ? randomItem(createdSubmissionItems.park) : undefined; const category = randomItem(['roller_coaster', 'flat_ride', 'water_ride', 'dark_ride']); const rideData: any = { @@ -470,7 +559,11 @@ Deno.serve(async (req) => { rideData.banner_image_url = `https://imagedelivery.net/test/ride-${i + 1}/banner`; } - const { submissionId, typeId } = await createSubmission(user.id, 'ride', rideData); + const options = { + dependsOn: parkItemId + }; + + const { submissionId, itemId, typeId } = await createSubmission(user.id, 'ride', rideData, options); // Add technical specs and stats for level 4 if (level >= 4 && typeId && category === 'roller_coaster') { @@ -516,7 +609,7 @@ Deno.serve(async (req) => { .maybeSingle(); if (approvedRide) { - await registerTestEntity(supabase, 'ride', slug, approvedRide.id, sessionId); + await registerTestEntity(supabase, 'ride', slug, approvedRide.id, itemId, sessionId); } if (!shouldConflict && !shouldVersionChain) { @@ -531,6 +624,9 @@ Deno.serve(async (req) => { for (let i = 0; i < plan.rideModels; i++) { const level = getPopulationLevel(fieldDensity, i); const { data: mfgData } = await supabase.from('companies').select('id').eq('slug', randomItem(createdCompanies.manufacturer)).maybeSingle(); + + // Get manufacturer dependency for depends_on + const mfgItemId = createdSubmissionItems.manufacturer.length > 0 ? randomItem(createdSubmissionItems.manufacturer) : undefined; const category = randomItem(['roller_coaster', 'flat_ride', 'water_ride']); const modelData: any = { @@ -555,7 +651,14 @@ Deno.serve(async (req) => { modelData.banner_image_url = `https://imagedelivery.net/test/model-${i + 1}/banner`; } - await createSubmission(user.id, 'ride_model', modelData); + const options = { + dependsOn: mfgItemId + }; + + const { itemId } = await createSubmission(user.id, 'ride_model', modelData, options); + + // Track created submission item + createdSubmissionItems.ride_model.push(itemId); // Register newly created ride model in the registry const modelSlug = `test-model-${i + 1}`; @@ -566,7 +669,7 @@ Deno.serve(async (req) => { .maybeSingle(); if (approvedModel) { - await registerTestEntity(supabase, 'ride_model', modelSlug, approvedModel.id, sessionId); + await registerTestEntity(supabase, 'ride_model', modelSlug, approvedModel.id, itemId, sessionId); } summary.rideModels++; diff --git a/supabase/migrations/20251010164045_dd0baeac-d6a3-449c-b42a-0a1e5ba48df3.sql b/supabase/migrations/20251010164045_dd0baeac-d6a3-449c-b42a-0a1e5ba48df3.sql new file mode 100644 index 00000000..fa26397e --- /dev/null +++ b/supabase/migrations/20251010164045_dd0baeac-d6a3-449c-b42a-0a1e5ba48df3.sql @@ -0,0 +1,10 @@ +-- Add submission_item_id to test_data_registry for proper dependency tracking +ALTER TABLE test_data_registry + ADD COLUMN IF NOT EXISTS submission_item_id UUID REFERENCES submission_items(id) ON DELETE CASCADE; + +-- Add index for fast lookups +CREATE INDEX IF NOT EXISTS idx_test_data_registry_item_id + ON test_data_registry(submission_item_id); + +-- Update existing records to have NULL submission_item_id (for backwards compatibility) +-- New records will populate this field \ No newline at end of file