Refactor: Approve test data generator tool use

This commit is contained in:
gpt-engineer-app[bot]
2025-10-10 16:44:08 +00:00
parent c8443e05a3
commit bc36583598
4 changed files with 177 additions and 20 deletions

View File

@@ -181,6 +181,39 @@ export function TestDataGenerator() {
</AlertDescription>
</Alert>
)}
{/* Dependency warnings */}
{entityTypes.rides && stats.parks === 0 && !entityTypes.parks && (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>
<strong>Missing Dependency:</strong> You've selected "rides" but no parks exist.
Rides require parks to be created first. Either enable "parks" or generate parks separately first.
</AlertDescription>
</Alert>
)}
{entityTypes.ride_models && stats.manufacturers === 0 && !entityTypes.manufacturers && (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>
<strong>Missing Dependency:</strong> 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.
</AlertDescription>
</Alert>
)}
{(entityTypes.parks || entityTypes.rides) &&
stats.operators === 0 && stats.property_owners === 0 &&
!entityTypes.operators && !entityTypes.property_owners && (
<Alert>
<AlertTriangle className="h-4 w-4" />
<AlertDescription>
<strong>Optional Dependencies:</strong> 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.
</AlertDescription>
</Alert>
)}
</div>
)}

View File

@@ -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: {

View File

@@ -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<Array<{ slug: string; entity_id: string }>> {
): Promise<Array<{ slug: string; entity_id: string; submission_item_id: string }>> {
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<Array<{ id: string; item_data: any }>> {
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<string, string[]> = {
operator: [],
property_owner: [],
manufacturer: [],
designer: [],
park: [],
ride_model: []
};
const createdParks: string[] = [];
const createdCompanies: Record<string, string[]> = { 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++;

View File

@@ -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