Improve seeds (#12675)

- Add seeds for notes/tasks
- Adds account manager to companies
- A companies and phone numbers to people
- Add many more opportunities

TODO: add timeline activities

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
Félix Malfait
2025-06-17 15:25:05 +02:00
committed by GitHub
parent f3a8b849aa
commit a47a6be4a8
11 changed files with 8617 additions and 3681 deletions

View File

@ -1,18 +1,18 @@
{ {
"install": "yarn install && echo 'Setting up Docker Compose environment...' && cd packages/twenty-docker && cp -n docker-compose.yml docker-compose.dev.yml || true", "install": "yarn install && echo 'Setting up Docker Compose environment...' && cd packages/twenty-docker && cp -n docker-compose.yml docker-compose.dev.yml || true && echo 'Dependencies installed and docker-compose prepared'",
"start": "sudo service docker start && cd packages/twenty-docker && echo 'Installing yq...' && sudo apt-get update && sudo apt-get install -y wget && wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 && sudo chmod +x /usr/local/bin/yq && echo 'Patching docker-compose for local development...' && yq eval 'del(.services.server.image)' -i docker-compose.dev.yml && yq eval '.services.server.build.context = \"../../\"' -i docker-compose.dev.yml && yq eval '.services.server.build.dockerfile = \"./packages/twenty-docker/twenty/Dockerfile\"' -i docker-compose.dev.yml && yq eval 'del(.services.worker.image)' -i docker-compose.dev.yml && yq eval '.services.worker.build.context = \"../../\"' -i docker-compose.dev.yml && yq eval '.services.worker.build.dockerfile = \"./packages/twenty-docker/twenty/Dockerfile\"' -i docker-compose.dev.yml && echo 'Setting up .env file...' && echo 'SERVER_URL=http://localhost:3000' > .env && echo 'APP_SECRET='$(openssl rand -base64 32) >> .env && echo 'PG_DATABASE_PASSWORD='$(openssl rand -hex 16) >> .env && echo 'SIGN_IN_PREFILLED=true' >> .env && echo 'Building and starting services...' && docker-compose -f docker-compose.dev.yml up -d --build", "start": "sudo service docker start && echo 'Docker service started' && cd packages/twenty-docker && echo 'Installing yq for YAML processing...' && sudo apt-get update -qq && sudo apt-get install -y wget && wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 && sudo chmod +x /usr/local/bin/yq && echo 'Patching docker-compose for local development...' && yq eval 'del(.services.server.image)' -i docker-compose.dev.yml && yq eval '.services.server.build.context = \"../../\"' -i docker-compose.dev.yml && yq eval '.services.server.build.dockerfile = \"./packages/twenty-docker/twenty/Dockerfile\"' -i docker-compose.dev.yml && yq eval 'del(.services.worker.image)' -i docker-compose.dev.yml && yq eval '.services.worker.build.context = \"../../\"' -i docker-compose.dev.yml && yq eval '.services.worker.build.dockerfile = \"./packages/twenty-docker/twenty/Dockerfile\"' -i docker-compose.dev.yml && echo 'Setting up .env file with database configuration...' && echo 'SERVER_URL=http://localhost:3000' > .env && echo 'APP_SECRET='$(openssl rand -base64 32) >> .env && echo 'PG_DATABASE_PASSWORD='$(openssl rand -hex 16) >> .env && echo 'PG_DATABASE_URL=postgres://postgres:password@localhost:5432/postgres' >> .env && echo 'SIGN_IN_PREFILLED=true' >> .env && echo 'Building and starting services...' && docker-compose -f docker-compose.dev.yml up -d --build && echo 'Waiting for services to initialize...' && sleep 30 && echo 'Checking service health...' && docker-compose -f docker-compose.dev.yml ps && echo 'Environment setup complete!'",
"terminals": [ "terminals": [
{ {
"name": "Docker Compose Logs", "name": "Database Setup & Seed",
"command": "sleep 30 && cd packages/twenty-docker && docker-compose -f docker-compose.dev.yml logs -f" "command": "sleep 40 && cd packages/twenty-docker && echo 'Waiting for PostgreSQL to be ready...' && until docker-compose -f docker-compose.dev.yml exec -T db pg_isready -U postgres; do echo 'Waiting for PostgreSQL...'; sleep 5; done && echo 'PostgreSQL is ready!' && echo 'Waiting for Twenty server to be healthy...' && until docker-compose -f docker-compose.dev.yml exec -T server curl --fail http://localhost:3000/healthz 2>/dev/null; do echo 'Waiting for server...'; sleep 5; done && echo 'Server is healthy!' && echo 'Running database setup and seeding...' && docker-compose -f docker-compose.dev.yml exec -T server npx nx database:reset twenty-server && echo 'Database seeded successfully!' && bash"
}, },
{ {
"name": "Database Seed", "name": "Application Logs",
"command": "sleep 60 && cd packages/twenty-docker && echo 'Waiting for services to be ready...' && until docker-compose -f docker-compose.dev.yml exec -T server curl --fail http://localhost:3000/healthz 2>/dev/null; do echo 'Waiting for server...'; sleep 5; done && echo 'Seeding database...' && docker-compose -f docker-compose.dev.yml exec -T server yarn command:prod -- workspace:seed:dev" "command": "sleep 35 && cd packages/twenty-docker && echo 'Following application logs...' && docker-compose -f docker-compose.dev.yml logs -f server worker"
}, },
{ {
"name": "Service Status", "name": "Service Monitor",
"command": "sleep 10 && cd packages/twenty-docker && while true; do clear; echo '=== Service Status ===' && docker-compose -f docker-compose.dev.yml ps && echo '\\n=== Health Status ===' && docker-compose -f docker-compose.dev.yml exec -T server curl -s http://localhost:3000/healthz 2>/dev/null && echo ' - Server: Healthy' || echo ' - Server: Starting...' && sleep 30; done" "command": "sleep 15 && cd packages/twenty-docker && echo '=== Service Status Monitor ===' && while true; do clear; echo '=== Service Status at $(date) ===' && docker-compose -f docker-compose.dev.yml ps && echo '\\n=== Health Status ===' && (docker-compose -f docker-compose.dev.yml exec -T server curl -s http://localhost:3000/healthz 2>/dev/null && echo '✅ Twenty Server: Healthy') || echo '❌ Twenty Server: Not Ready' && (docker-compose -f docker-compose.dev.yml exec -T db pg_isready -U postgres 2>/dev/null && echo '✅ PostgreSQL: Ready') || echo '❌ PostgreSQL: Not Ready' && echo '\\n=== Database Connection Test ===' && docker-compose -f docker-compose.dev.yml exec -T server node -e \"const { Client } = require('pg'); const client = new Client({connectionString: process.env.PG_DATABASE_URL}); client.connect().then(() => {console.log('✅ Database Connection: OK'); client.end();}).catch(e => console.log('❌ Database Connection: Failed -', e.message));\" || echo 'Connection test failed' && sleep 45; done"
} }
] ]
} }

View File

@ -1,14 +1,18 @@
{ {
"install": "yarn install", "install": "yarn install && echo 'Installing dependencies complete'",
"start": "sudo service docker start && echo 'Waiting for Docker to start...' && sleep 5 && echo 'Starting PostgreSQL and Redis containers...' && make postgres-on-docker && make redis-on-docker && sleep 15 && echo 'Containers started successfully! Checking status...' && docker ps --filter name=twenty_", "start": "sudo service docker start && echo 'Docker service started' && sleep 3 && echo 'Starting PostgreSQL and Redis containers...' && make postgres-on-docker && make redis-on-docker && echo 'Waiting for containers to initialize...' && sleep 20 && echo 'Checking container status...' && docker ps --filter name=twenty_ && echo 'Waiting for PostgreSQL to be ready...' && until docker exec twenty_pg pg_isready -U postgres -h localhost; do echo 'PostgreSQL not ready yet, waiting...'; sleep 3; done && echo 'PostgreSQL is ready!' && echo 'Setting up database...' && cd packages/twenty-server && npm run database:init || echo 'Database already initialized' && echo 'Environment setup complete!'",
"terminals": [ "terminals": [
{ {
"name": "Development Server", "name": "Development Server",
"command": "sleep 20 && echo 'Starting Twenty development server...' && export SERVER_URL=http://localhost:3000 && yarn start" "command": "echo 'Waiting for database to be fully ready...' && sleep 30 && until docker exec twenty_pg pg_isready -U postgres -h localhost; do echo 'Waiting for PostgreSQL...'; sleep 2; done && echo 'Starting Twenty development server...' && export SERVER_URL=http://localhost:3000 && export PG_DATABASE_URL=postgres://postgres:password@localhost:5432/postgres && yarn start"
}, },
{ {
"name": "Logs & Status", "name": "Database Management",
"command": "sleep 10 && echo '=== Waiting for containers to be ready ===' && until docker exec twenty_pg pg_isready -U postgres 2>/dev/null; do echo 'Waiting for PostgreSQL...'; sleep 2; done && echo 'PostgreSQL is ready!' && echo '=== Container Status ===' && docker ps --filter name=twenty_ --format 'table {{.Names}}\\t{{.Status}}\\t{{.Ports}}' && echo '\\n=== Following PostgreSQL logs ===' && docker logs -f twenty_pg" "command": "sleep 25 && echo 'Database management terminal ready' && echo 'Waiting for PostgreSQL to be available...' && until docker exec twenty_pg pg_isready -U postgres -h localhost; do echo 'Waiting for PostgreSQL...'; sleep 2; done && echo 'PostgreSQL is ready for database operations!' && echo 'You can now run database commands like:' && echo ' npx nx database:reset twenty-server' && echo ' npx nx database:migrate twenty-server' && bash"
},
{
"name": "Container Logs & Status",
"command": "sleep 10 && echo '=== Container Status Monitor ===' && while true; do echo '\\n=== Container Status at $(date) ===' && docker ps --filter name=twenty_ --format 'table {{.Names}}\\t{{.Status}}\\t{{.Ports}}' && echo '\\n=== PostgreSQL Status ===' && (docker exec twenty_pg pg_isready -U postgres -h localhost && echo 'PostgreSQL: ✅ Ready') || echo 'PostgreSQL: ❌ Not Ready' && echo '\\n=== Redis Status ===' && (docker exec twenty_redis redis-cli ping && echo 'Redis: ✅ Ready') || echo 'Redis: ❌ Not Ready' && sleep 30; done"
} }
] ]
} }

View File

@ -0,0 +1,204 @@
import { WORKSPACE_MEMBER_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/workspace-member-data-seeds.constant';
type NoteDataSeed = {
id: string;
position: number;
title: string;
body: string | null;
bodyV2Blocknote: string | null;
bodyV2Markdown: string | null;
createdBySource: string;
createdByWorkspaceMemberId: string;
createdByName: string;
createdByContext: string | null;
};
export const NOTE_DATA_SEED_COLUMNS: (keyof NoteDataSeed)[] = [
'id',
'position',
'title',
'body',
'bodyV2Blocknote',
'bodyV2Markdown',
'createdBySource',
'createdByWorkspaceMemberId',
'createdByName',
'createdByContext',
];
const GENERATE_NOTE_IDS = (): Record<string, string> => {
const NOTE_IDS: Record<string, string> = {};
for (let INDEX = 1; INDEX <= 1200; INDEX++) {
const HEX_INDEX = INDEX.toString(16).padStart(4, '0');
NOTE_IDS[`ID_${INDEX}`] = `20202020-${HEX_INDEX}-4e7c-8001-123456789abc`;
}
for (let INDEX = 1201; INDEX <= 1800; INDEX++) {
const HEX_INDEX = INDEX.toString(16).padStart(4, '0');
NOTE_IDS[`ID_${INDEX}`] = `20202020-${HEX_INDEX}-4e7c-9001-123456789abc`;
}
return NOTE_IDS;
};
export const NOTE_DATA_SEED_IDS = GENERATE_NOTE_IDS();
const PERSON_NOTE_TEMPLATES = [
{
title: 'Meeting Follow-up',
content:
'Great conversation today about potential collaboration opportunities. Next steps discussed include proposal review and timeline planning.',
},
{
title: 'Project Update Discussion',
content:
'Reviewed current project status and identified key deliverables for the upcoming quarter. Timeline adjustments may be needed.',
},
{
title: 'Skills & Experience Review',
content:
'Impressive background in technology and leadership. Strong potential for senior roles in upcoming projects.',
},
{
title: 'Networking Connection',
content:
'Made connection at industry conference. Shared interests in digital transformation and innovation strategies.',
},
{
title: 'Interview Notes',
content:
'Strong candidate with relevant experience. Technical skills align well with team requirements. Positive cultural fit assessment.',
},
{
title: 'Performance Check-in',
content:
'Quarterly review completed. Exceeded targets in key areas. Discussed career development opportunities and growth plans.',
},
{
title: 'Training & Development',
content:
'Completed certification program successfully. Ready to take on expanded responsibilities in the next phase.',
},
{
title: 'Client Relationship Notes',
content:
'Long-standing professional relationship. Reliable partner for complex initiatives. High satisfaction ratings.',
},
];
const COMPANY_NOTE_TEMPLATES = [
{
title: 'Partnership Opportunity',
content:
'Promising partnership potential identified. Complementary strengths in market reach and technical capabilities.',
},
{
title: 'Vendor Assessment',
content:
'Comprehensive evaluation completed. Strong financial position and excellent service track record. Recommended for preferred vendor status.',
},
{
title: 'Market Analysis',
content:
'Significant player in the industry with growing market share. Innovation-focused approach aligns with our strategic objectives.',
},
{
title: 'Contract Negotiation',
content:
'Initial terms discussed. Competitive pricing structure proposed. Legal review scheduled for next phase.',
},
{
title: 'Technology Integration',
content:
'Advanced technology stack compatible with our systems. Implementation timeline estimated at 6-8 weeks.',
},
{
title: 'Due Diligence Report',
content:
'Financial health indicators positive. Strong leadership team and sustainable business model. Low risk assessment.',
},
{
title: 'Customer Success Story',
content:
'Excellent case study of successful digital transformation. Results exceeded expectations with 40% efficiency improvement.',
},
{
title: 'Industry Insights',
content:
'Valuable perspective on market trends and future opportunities. Thought leadership in emerging technologies.',
},
];
// Generate note data seeds
const GENERATE_NOTE_SEEDS = (): NoteDataSeed[] => {
const NOTE_SEEDS: NoteDataSeed[] = [];
// Person notes (ID_1 to ID_1200)
for (let INDEX = 1; INDEX <= 1200; INDEX++) {
const TEMPLATE_INDEX = (INDEX - 1) % PERSON_NOTE_TEMPLATES.length;
const TEMPLATE = PERSON_NOTE_TEMPLATES[TEMPLATE_INDEX];
NOTE_SEEDS.push({
id: NOTE_DATA_SEED_IDS[`ID_${INDEX}`],
position: INDEX,
title: TEMPLATE.title,
body: null,
bodyV2Blocknote: JSON.stringify([
{
id: `block-${INDEX}`,
type: 'paragraph',
props: {
textColor: 'default',
backgroundColor: 'default',
textAlignment: 'left',
},
content: [{ type: 'text', text: TEMPLATE.content, styles: {} }],
children: [],
},
]),
bodyV2Markdown: TEMPLATE.content,
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
createdByName: 'Tim A',
createdByContext: null,
});
}
// Company notes (ID_1201 to ID_1800)
for (let INDEX = 1201; INDEX <= 1800; INDEX++) {
const TEMPLATE_INDEX = (INDEX - 1201) % COMPANY_NOTE_TEMPLATES.length;
const TEMPLATE = COMPANY_NOTE_TEMPLATES[TEMPLATE_INDEX];
NOTE_SEEDS.push({
id: NOTE_DATA_SEED_IDS[`ID_${INDEX}`],
position: INDEX,
title: TEMPLATE.title,
body: null,
bodyV2Blocknote: JSON.stringify([
{
id: `block-${INDEX}`,
type: 'paragraph',
props: {
textColor: 'default',
backgroundColor: 'default',
textAlignment: 'left',
},
content: [{ type: 'text', text: TEMPLATE.content, styles: {} }],
children: [],
},
]),
bodyV2Markdown: TEMPLATE.content,
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
createdByName: 'Tim A',
createdByContext: null,
});
}
return NOTE_SEEDS;
};
export const NOTE_DATA_SEEDS = GENERATE_NOTE_SEEDS();

View File

@ -0,0 +1,77 @@
import { COMPANY_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/company-data-seeds.constant';
import { NOTE_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/note-data-seeds.constant';
import { PERSON_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/person-data-seeds.constant';
type NoteTargetDataSeed = {
id: string;
noteId: string | null;
personId: string | null;
companyId: string | null;
opportunityId: string | null;
};
export const NOTE_TARGET_DATA_SEED_COLUMNS: (keyof NoteTargetDataSeed)[] = [
'id',
'noteId',
'personId',
'companyId',
'opportunityId',
];
const GENERATE_NOTE_TARGET_IDS = (): Record<string, string> => {
const NOTE_TARGET_IDS: Record<string, string> = {};
for (let INDEX = 1; INDEX <= 1200; INDEX++) {
const HEX_INDEX = INDEX.toString(16).padStart(4, '0');
NOTE_TARGET_IDS[`ID_${INDEX}`] =
`20202020-${HEX_INDEX}-4e7c-8001-123456789def`;
}
for (let INDEX = 1201; INDEX <= 1800; INDEX++) {
const HEX_INDEX = INDEX.toString(16).padStart(4, '0');
NOTE_TARGET_IDS[`ID_${INDEX}`] =
`20202020-${HEX_INDEX}-4e7c-9001-123456789def`;
}
return NOTE_TARGET_IDS;
};
const NOTE_TARGET_DATA_SEED_IDS = GENERATE_NOTE_TARGET_IDS();
const GENERATE_NOTE_TARGET_SEEDS = (): NoteTargetDataSeed[] => {
const NOTE_TARGET_SEEDS: NoteTargetDataSeed[] = [];
for (let INDEX = 1; INDEX <= 1200; INDEX++) {
NOTE_TARGET_SEEDS.push({
id: NOTE_TARGET_DATA_SEED_IDS[`ID_${INDEX}`],
noteId: NOTE_DATA_SEED_IDS[`ID_${INDEX}`],
personId:
PERSON_DATA_SEED_IDS[
`ID_${INDEX}` as keyof typeof PERSON_DATA_SEED_IDS
],
companyId: null,
opportunityId: null,
});
}
for (let INDEX = 1201; INDEX <= 1800; INDEX++) {
const COMPANY_INDEX = INDEX - 1200;
NOTE_TARGET_SEEDS.push({
id: NOTE_TARGET_DATA_SEED_IDS[`ID_${INDEX}`],
noteId: NOTE_DATA_SEED_IDS[`ID_${INDEX}`],
personId: null,
companyId:
COMPANY_DATA_SEED_IDS[
`ID_${COMPANY_INDEX}` as keyof typeof COMPANY_DATA_SEED_IDS
],
opportunityId: null,
});
}
return NOTE_TARGET_SEEDS;
};
export const NOTE_TARGET_DATA_SEEDS = GENERATE_NOTE_TARGET_SEEDS();

View File

@ -32,68 +32,166 @@ export const OPPORTUNITY_DATA_SEED_COLUMNS: (keyof OpportunityDataSeed)[] = [
'createdByName', 'createdByName',
]; ];
export const OPPORTUNITY_DATA_SEED_IDS = { const GENERATE_OPPORTUNITY_IDS = (): Record<string, string> => {
ID_1: '20202020-be10-422b-a663-16bd3c2228e1', const OPPORTUNITY_IDS: Record<string, string> = {};
ID_2: '20202020-0543-4cc2-9f96-95cc699960f2',
ID_3: '20202020-2f89-406f-90ea-180f433b2445', for (let INDEX = 1; INDEX <= 50; INDEX++) {
ID_4: '20202020-35b1-4045-9cde-42f715148954', const HEX_INDEX = INDEX.toString(16).padStart(4, '0');
OPPORTUNITY_IDS[`ID_${INDEX}`] =
`50505050-${HEX_INDEX}-4e7c-8001-123456789abc`;
}
return OPPORTUNITY_IDS;
}; };
export const OPPORTUNITY_DATA_SEEDS: OpportunityDataSeed[] = [ export const OPPORTUNITY_DATA_SEED_IDS = GENERATE_OPPORTUNITY_IDS();
// Credible opportunity names for Apple selling to various companies
const OPPORTUNITY_TEMPLATES = [
{ name: 'Enterprise iPad Deployment', amount: 2500000, stage: 'PROPOSAL' },
{ name: 'MacBook Pro Fleet Upgrade', amount: 1800000, stage: 'MEETING' },
{ name: 'iPhone Corporate Program', amount: 3200000, stage: 'NEW' },
{ name: 'Apple TV+ Enterprise License', amount: 450000, stage: 'SCREENING' },
{ name: 'Mac Studio Creative Suite', amount: 890000, stage: 'PROPOSAL' },
{ name: 'iPad Pro Design Team Setup', amount: 670000, stage: 'MEETING' },
{ name: 'Apple Watch Corporate Wellness', amount: 320000, stage: 'NEW' },
{ {
id: OPPORTUNITY_DATA_SEED_IDS.ID_1, name: 'iMac Office Workstation Refresh',
name: 'Opportunity 1', amount: 1200000,
amountAmountMicros: 100000, stage: 'CUSTOMER',
amountCurrencyCode: 'USD', },
closeDate: new Date(), { name: 'Apple One Business Bundle', amount: 180000, stage: 'PROPOSAL' },
{ name: 'MacBook Air Remote Work Package', amount: 950000, stage: 'MEETING' },
{ name: 'Apple Pencil Educational License', amount: 85000, stage: 'NEW' },
{
name: 'Mac Pro Video Production Setup',
amount: 2100000,
stage: 'SCREENING',
},
{ name: 'iPhone SE Frontline Workers', amount: 780000, stage: 'PROPOSAL' },
{ name: 'Apple CarPlay Integration', amount: 1500000, stage: 'MEETING' },
{ name: 'iPad Air Retail Deployment', amount: 620000, stage: 'NEW' },
{ name: 'Apple Music Business License', amount: 95000, stage: 'CUSTOMER' },
{ name: 'Mac mini Server Infrastructure', amount: 430000, stage: 'PROPOSAL' },
{ name: 'Apple Arcade Enterprise Gaming', amount: 75000, stage: 'SCREENING' },
{ name: 'iPhone 15 Pro Executive Program', amount: 540000, stage: 'MEETING' },
{ name: 'Apple Fitness+ Corporate Wellness', amount: 125000, stage: 'NEW' },
{ name: 'iPad Mini Field Operations', amount: 380000, stage: 'PROPOSAL' },
{
name: 'Apple News+ Business Subscription',
amount: 45000,
stage: 'CUSTOMER',
},
{ name: 'MacBook Pro M3 Developer Team', amount: 1600000, stage: 'MEETING' },
{
name: 'Apple Vision Pro Prototype Lab',
amount: 850000,
stage: 'SCREENING',
},
{ name: 'iPhone Photography Workshop', amount: 65000, stage: 'NEW' },
{ name: 'Apple Store Corporate Training', amount: 155000, stage: 'PROPOSAL' },
{ name: 'iPad Kiosk Solution Deployment', amount: 290000, stage: 'MEETING' },
{
name: 'Apple Pay Enterprise Integration',
amount: 720000,
stage: 'CUSTOMER',
},
{ name: 'Mac Studio Animation Pipeline', amount: 1350000, stage: 'PROPOSAL' },
{ name: 'Apple Configurator MDM Setup', amount: 210000, stage: 'SCREENING' },
{
name: 'iPhone Accessibility Features Training',
amount: 85000,
stage: 'NEW', stage: 'NEW',
position: 1,
pointOfContactId: PERSON_DATA_SEED_IDS.ID_1,
companyId: COMPANY_DATA_SEED_IDS.ID_1,
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
createdByName: 'Tim Cook',
}, },
{ name: 'Apple Business Manager License', amount: 180000, stage: 'MEETING' },
{ name: 'iPad Pro AR Development Kit', amount: 490000, stage: 'PROPOSAL' },
{ name: 'Apple School Manager Education', amount: 320000, stage: 'CUSTOMER' },
{ name: 'MacBook Air Student Program', amount: 750000, stage: 'MEETING' },
{ name: 'Apple Watch Health Monitoring', amount: 280000, stage: 'SCREENING' },
{ name: 'iPhone Security Audit Services', amount: 195000, stage: 'NEW' },
{ {
id: OPPORTUNITY_DATA_SEED_IDS.ID_2, name: 'Apple TV Digital Signage Solution',
name: 'Opportunity 2', amount: 340000,
amountAmountMicros: 2000000, stage: 'PROPOSAL',
amountCurrencyCode: 'USD', },
closeDate: new Date(), { name: 'Mac Pro Rendering Farm Setup', amount: 2800000, stage: 'MEETING' },
{
name: 'Apple Pencil Digital Art License',
amount: 120000,
stage: 'CUSTOMER',
},
{ name: 'iPad Point of Sale Integration', amount: 580000, stage: 'PROPOSAL' },
{
name: 'Apple Maps Business Integration',
amount: 165000,
stage: 'SCREENING',
},
{ name: 'iPhone App Development Workshop', amount: 95000, stage: 'NEW' },
{
name: 'Apple Silicon Migration Consulting',
amount: 420000,
stage: 'MEETING', stage: 'MEETING',
position: 2,
pointOfContactId: PERSON_DATA_SEED_IDS.ID_2,
companyId: COMPANY_DATA_SEED_IDS.ID_2,
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
createdByName: 'Tim Cook',
}, },
{ {
id: OPPORTUNITY_DATA_SEED_IDS.ID_3, name: 'iPad Inventory Management System',
name: 'Opportunity 3', amount: 350000,
amountAmountMicros: 300000,
amountCurrencyCode: 'USD',
closeDate: new Date(),
stage: 'PROPOSAL', stage: 'PROPOSAL',
position: 3,
pointOfContactId: PERSON_DATA_SEED_IDS.ID_3,
companyId: COMPANY_DATA_SEED_IDS.ID_3,
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
createdByName: 'Tim Cook',
}, },
{ {
id: OPPORTUNITY_DATA_SEED_IDS.ID_4, name: 'Apple Podcast Enterprise Hosting',
name: 'Opportunity 4', amount: 75000,
amountAmountMicros: 4000000, stage: 'CUSTOMER',
amountCurrencyCode: 'USD', },
closeDate: new Date(), {
name: 'MacBook Pro Creative Cloud Bundle',
amount: 1100000,
stage: 'MEETING',
},
{ name: 'Apple ID Enterprise SSO Setup', amount: 240000, stage: 'SCREENING' },
{ name: 'iPhone Field Service Optimization', amount: 680000, stage: 'NEW' },
{
name: 'Apple Retail Partnership Program',
amount: 1950000,
stage: 'PROPOSAL', stage: 'PROPOSAL',
position: 4,
pointOfContactId: PERSON_DATA_SEED_IDS.ID_4,
companyId: COMPANY_DATA_SEED_IDS.ID_4,
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
createdByName: '',
}, },
]; ];
const GENERATE_OPPORTUNITY_SEEDS = (): OpportunityDataSeed[] => {
const OPPORTUNITY_SEEDS: OpportunityDataSeed[] = [];
for (let INDEX = 1; INDEX <= 50; INDEX++) {
const TEMPLATE_INDEX = (INDEX - 1) % OPPORTUNITY_TEMPLATES.length;
const TEMPLATE = OPPORTUNITY_TEMPLATES[TEMPLATE_INDEX];
const DAYS_AHEAD = Math.floor(Math.random() * 90) + 1;
const CLOSE_DATE = new Date();
CLOSE_DATE.setDate(CLOSE_DATE.getDate() + DAYS_AHEAD);
OPPORTUNITY_SEEDS.push({
id: OPPORTUNITY_DATA_SEED_IDS[`ID_${INDEX}`],
name: TEMPLATE.name,
amountAmountMicros: TEMPLATE.amount * 1000000,
amountCurrencyCode: 'USD',
closeDate: CLOSE_DATE,
stage: TEMPLATE.stage,
position: INDEX,
pointOfContactId:
PERSON_DATA_SEED_IDS[
`ID_${INDEX}` as keyof typeof PERSON_DATA_SEED_IDS
] || PERSON_DATA_SEED_IDS.ID_1,
companyId:
COMPANY_DATA_SEED_IDS[
`ID_${Math.ceil(INDEX / 2)}` as keyof typeof COMPANY_DATA_SEED_IDS
] || COMPANY_DATA_SEED_IDS.ID_1,
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
createdByName: 'Tim Cook',
});
}
return OPPORTUNITY_SEEDS;
};
export const OPPORTUNITY_DATA_SEEDS = GENERATE_OPPORTUNITY_SEEDS();

View File

@ -0,0 +1,223 @@
import { WORKSPACE_MEMBER_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/workspace-member-data-seeds.constant';
type TaskDataSeed = {
id: string;
position: number;
title: string;
body: string | null;
status: string;
dueAt: string | null;
assigneeId: string;
createdBySource: string;
createdByWorkspaceMemberId: string;
createdByName: string;
};
export const TASK_DATA_SEED_COLUMNS: (keyof TaskDataSeed)[] = [
'id',
'position',
'title',
'body',
'status',
'dueAt',
'assigneeId',
'createdBySource',
'createdByWorkspaceMemberId',
'createdByName',
];
// Generate all task IDs
const GENERATE_TASK_IDS = (): Record<string, string> => {
const TASK_IDS: Record<string, string> = {};
// Person tasks (ID_1 to ID_1200)
for (let INDEX = 1; INDEX <= 1200; INDEX++) {
const HEX_INDEX = INDEX.toString(16).padStart(4, '0');
TASK_IDS[`ID_${INDEX}`] = `20202020-${HEX_INDEX}-4e7c-8001-123456789def`;
}
// Company tasks (ID_1201 to ID_1800)
for (let INDEX = 1201; INDEX <= 1800; INDEX++) {
const HEX_INDEX = INDEX.toString(16).padStart(4, '0');
TASK_IDS[`ID_${INDEX}`] = `20202020-${HEX_INDEX}-4e7c-9001-123456789def`;
}
return TASK_IDS;
};
export const TASK_DATA_SEED_IDS = GENERATE_TASK_IDS();
// Sample credible task titles and contents for person-related tasks
const PERSON_TASK_TEMPLATES = [
{
title: 'Schedule follow-up call',
body: 'Arrange a follow-up call to discuss project details and next steps.',
status: 'TODO',
daysFromNow: 3,
},
{
title: 'Send project proposal',
body: 'Prepare and send the project proposal document with timeline and deliverables.',
status: 'IN_PROGRESS',
daysFromNow: 5,
},
{
title: 'Review contract terms',
body: 'Review the contract terms and conditions before final approval.',
status: 'TODO',
daysFromNow: 7,
},
{
title: 'Prepare meeting agenda',
body: 'Create detailed agenda for upcoming strategy meeting.',
status: 'TODO',
daysFromNow: 2,
},
{
title: 'Update contact information',
body: 'Verify and update contact details in the system.',
status: 'DONE',
daysFromNow: null,
},
{
title: 'Conduct reference check',
body: 'Complete reference verification for background check process.',
status: 'IN_PROGRESS',
daysFromNow: 4,
},
{
title: 'Share portfolio samples',
body: 'Send portfolio examples and case studies for review.',
status: 'TODO',
daysFromNow: 6,
},
{
title: 'Set up onboarding process',
body: 'Prepare onboarding materials and schedule orientation session.',
status: 'TODO',
daysFromNow: 8,
},
];
// Sample credible task titles and contents for company-related tasks
const COMPANY_TASK_TEMPLATES = [
{
title: 'Conduct vendor evaluation',
body: 'Complete comprehensive evaluation of vendor capabilities and pricing.',
status: 'IN_PROGRESS',
daysFromNow: 10,
},
{
title: 'Negotiate contract terms',
body: 'Review and negotiate contract terms for upcoming partnership.',
status: 'TODO',
daysFromNow: 14,
},
{
title: 'Schedule demo presentation',
body: 'Arrange product demonstration for stakeholder review.',
status: 'TODO',
daysFromNow: 7,
},
{
title: 'Prepare RFP response',
body: 'Draft comprehensive response to Request for Proposal.',
status: 'IN_PROGRESS',
daysFromNow: 12,
},
{
title: 'Update compliance documentation',
body: 'Review and update all compliance and certification documents.',
status: 'TODO',
daysFromNow: 21,
},
{
title: 'Analyze market research',
body: 'Review market analysis report and competitive landscape.',
status: 'DONE',
daysFromNow: null,
},
{
title: 'Plan integration strategy',
body: 'Develop technical integration plan and implementation timeline.',
status: 'TODO',
daysFromNow: 15,
},
{
title: 'Review financial statements',
body: 'Analyze latest financial reports and performance metrics.',
status: 'IN_PROGRESS',
daysFromNow: 5,
},
];
// Helper function to get random workspace member
const GET_RANDOM_ASSIGNEE = (): string => {
const MEMBERS = [
WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
WORKSPACE_MEMBER_DATA_SEED_IDS.JONY,
WORKSPACE_MEMBER_DATA_SEED_IDS.PHIL,
];
return MEMBERS[Math.floor(Math.random() * MEMBERS.length)];
};
// Helper function to format due date
const FORMAT_DUE_DATE = (daysFromNow: number | null): string | null => {
if (daysFromNow === null) return null;
const DATE = new Date();
DATE.setDate(DATE.getDate() + daysFromNow);
return DATE.toISOString();
};
// Generate task data seeds
const GENERATE_TASK_SEEDS = (): TaskDataSeed[] => {
const TASK_SEEDS: TaskDataSeed[] = [];
// Person tasks (ID_1 to ID_1200)
for (let INDEX = 1; INDEX <= 1200; INDEX++) {
const TEMPLATE_INDEX = (INDEX - 1) % PERSON_TASK_TEMPLATES.length;
const TEMPLATE = PERSON_TASK_TEMPLATES[TEMPLATE_INDEX];
TASK_SEEDS.push({
id: TASK_DATA_SEED_IDS[`ID_${INDEX}`],
position: INDEX,
title: TEMPLATE.title,
body: TEMPLATE.body,
status: TEMPLATE.status,
dueAt: FORMAT_DUE_DATE(TEMPLATE.daysFromNow),
assigneeId: GET_RANDOM_ASSIGNEE(),
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
createdByName: 'Tim A',
});
}
// Company tasks (ID_1201 to ID_1800)
for (let INDEX = 1201; INDEX <= 1800; INDEX++) {
const TEMPLATE_INDEX = (INDEX - 1201) % COMPANY_TASK_TEMPLATES.length;
const TEMPLATE = COMPANY_TASK_TEMPLATES[TEMPLATE_INDEX];
TASK_SEEDS.push({
id: TASK_DATA_SEED_IDS[`ID_${INDEX}`],
position: INDEX,
title: TEMPLATE.title,
body: TEMPLATE.body,
status: TEMPLATE.status,
dueAt: FORMAT_DUE_DATE(TEMPLATE.daysFromNow),
assigneeId: GET_RANDOM_ASSIGNEE(),
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
createdByName: 'Tim A',
});
}
return TASK_SEEDS;
};
export const TASK_DATA_SEEDS = GENERATE_TASK_SEEDS();

View File

@ -0,0 +1,83 @@
import { COMPANY_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/company-data-seeds.constant';
import { PERSON_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/person-data-seeds.constant';
import { TASK_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/task-data-seeds.constant';
type TaskTargetDataSeed = {
id: string;
taskId: string | null;
personId: string | null;
companyId: string | null;
opportunityId: string | null;
};
export const TASK_TARGET_DATA_SEED_COLUMNS: (keyof TaskTargetDataSeed)[] = [
'id',
'taskId',
'personId',
'companyId',
'opportunityId',
];
// Generate all task target IDs
const GENERATE_TASK_TARGET_IDS = (): Record<string, string> => {
const TASK_TARGET_IDS: Record<string, string> = {};
// Person task targets (ID_1 to ID_1200)
for (let INDEX = 1; INDEX <= 1200; INDEX++) {
const HEX_INDEX = INDEX.toString(16).padStart(4, '0');
TASK_TARGET_IDS[`ID_${INDEX}`] =
`60606060-${HEX_INDEX}-4e7c-8001-123456789def`;
}
// Company task targets (ID_1201 to ID_1800)
for (let INDEX = 1201; INDEX <= 1800; INDEX++) {
const HEX_INDEX = INDEX.toString(16).padStart(4, '0');
TASK_TARGET_IDS[`ID_${INDEX}`] =
`60606060-${HEX_INDEX}-4e7c-9001-123456789def`;
}
return TASK_TARGET_IDS;
};
const TASK_TARGET_DATA_SEED_IDS = GENERATE_TASK_TARGET_IDS();
// Generate task target data seeds
const GENERATE_TASK_TARGET_SEEDS = (): TaskTargetDataSeed[] => {
const TASK_TARGET_SEEDS: TaskTargetDataSeed[] = [];
// Person task targets (link each person task to its corresponding person)
for (let INDEX = 1; INDEX <= 1200; INDEX++) {
TASK_TARGET_SEEDS.push({
id: TASK_TARGET_DATA_SEED_IDS[`ID_${INDEX}`],
taskId: TASK_DATA_SEED_IDS[`ID_${INDEX}`],
personId:
PERSON_DATA_SEED_IDS[
`ID_${INDEX}` as keyof typeof PERSON_DATA_SEED_IDS
],
companyId: null,
opportunityId: null,
});
}
// Company task targets (link each company task to its corresponding company)
for (let INDEX = 1201; INDEX <= 1800; INDEX++) {
const COMPANY_INDEX = INDEX - 1200;
TASK_TARGET_SEEDS.push({
id: TASK_TARGET_DATA_SEED_IDS[`ID_${INDEX}`],
taskId: TASK_DATA_SEED_IDS[`ID_${INDEX}`],
personId: null,
companyId:
COMPANY_DATA_SEED_IDS[
`ID_${COMPANY_INDEX}` as keyof typeof COMPANY_DATA_SEED_IDS
],
opportunityId: null,
});
}
return TASK_TARGET_SEEDS;
};
export const TASK_TARGET_DATA_SEEDS = GENERATE_TASK_TARGET_SEEDS();

View File

@ -52,6 +52,14 @@ import {
MESSAGE_THREAD_DATA_SEED_COLUMNS, MESSAGE_THREAD_DATA_SEED_COLUMNS,
MESSAGE_THREAD_DATA_SEEDS, MESSAGE_THREAD_DATA_SEEDS,
} from 'src/engine/workspace-manager/dev-seeder/data/constants/message-thread-data-seeds.constant'; } from 'src/engine/workspace-manager/dev-seeder/data/constants/message-thread-data-seeds.constant';
import {
NOTE_DATA_SEED_COLUMNS,
NOTE_DATA_SEEDS,
} from 'src/engine/workspace-manager/dev-seeder/data/constants/note-data-seeds.constant';
import {
NOTE_TARGET_DATA_SEED_COLUMNS,
NOTE_TARGET_DATA_SEEDS,
} from 'src/engine/workspace-manager/dev-seeder/data/constants/note-target-data-seeds.constant';
import { import {
OPPORTUNITY_DATA_SEED_COLUMNS, OPPORTUNITY_DATA_SEED_COLUMNS,
OPPORTUNITY_DATA_SEEDS, OPPORTUNITY_DATA_SEEDS,
@ -69,17 +77,25 @@ import {
SURVEY_RESULT_DATA_SEEDS, SURVEY_RESULT_DATA_SEEDS,
} from 'src/engine/workspace-manager/dev-seeder/data/constants/survey-result-data-seeds.constant'; } from 'src/engine/workspace-manager/dev-seeder/data/constants/survey-result-data-seeds.constant';
import { import {
WORKSPACE_MEMBER_DATA_SEED_COLUMNS, TASK_DATA_SEED_COLUMNS,
WORKSPACE_MEMBER_DATA_SEEDS, TASK_DATA_SEEDS,
} from 'src/engine/workspace-manager/dev-seeder/data/constants/workspace-member-data-seeds.constant'; } from 'src/engine/workspace-manager/dev-seeder/data/constants/task-data-seeds.constant';
import { prefillViews } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-views'; import {
import { prefillWorkspaceFavorites } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workspace-favorites'; TASK_TARGET_DATA_SEED_COLUMNS,
TASK_TARGET_DATA_SEEDS,
} from 'src/engine/workspace-manager/dev-seeder/data/constants/task-target-data-seeds.constant';
import { import {
WORKFLOW_DATA_SEED_COLUMNS, WORKFLOW_DATA_SEED_COLUMNS,
WORKFLOW_DATA_SEEDS, WORKFLOW_DATA_SEEDS,
WORKFLOW_VERSION_DATA_SEED_COLUMNS, WORKFLOW_VERSION_DATA_SEED_COLUMNS,
WORKFLOW_VERSION_DATA_SEEDS, WORKFLOW_VERSION_DATA_SEEDS,
} from 'src/engine/workspace-manager/dev-seeder/data/constants/workflow-data-seeds.constants'; } from 'src/engine/workspace-manager/dev-seeder/data/constants/workflow-data-seeds.constants';
import {
WORKSPACE_MEMBER_DATA_SEED_COLUMNS,
WORKSPACE_MEMBER_DATA_SEEDS,
} from 'src/engine/workspace-manager/dev-seeder/data/constants/workspace-member-data-seeds.constant';
import { prefillViews } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-views';
import { prefillWorkspaceFavorites } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workspace-favorites';
const RECORD_SEEDS_CONFIGS = [ const RECORD_SEEDS_CONFIGS = [
{ {
@ -97,6 +113,16 @@ const RECORD_SEEDS_CONFIGS = [
pgColumns: PERSON_DATA_SEED_COLUMNS, pgColumns: PERSON_DATA_SEED_COLUMNS,
recordSeeds: PERSON_DATA_SEEDS, recordSeeds: PERSON_DATA_SEEDS,
}, },
{
tableName: 'note',
pgColumns: NOTE_DATA_SEED_COLUMNS,
recordSeeds: NOTE_DATA_SEEDS,
},
{
tableName: 'noteTarget',
pgColumns: NOTE_TARGET_DATA_SEED_COLUMNS,
recordSeeds: NOTE_TARGET_DATA_SEEDS,
},
{ {
tableName: 'opportunity', tableName: 'opportunity',
pgColumns: OPPORTUNITY_DATA_SEED_COLUMNS, pgColumns: OPPORTUNITY_DATA_SEED_COLUMNS,
@ -177,6 +203,16 @@ const RECORD_SEEDS_CONFIGS = [
pgColumns: SURVEY_RESULT_DATA_SEED_COLUMNS, pgColumns: SURVEY_RESULT_DATA_SEED_COLUMNS,
recordSeeds: SURVEY_RESULT_DATA_SEEDS, recordSeeds: SURVEY_RESULT_DATA_SEEDS,
}, },
{
tableName: 'task',
pgColumns: TASK_DATA_SEED_COLUMNS,
recordSeeds: TASK_DATA_SEEDS,
},
{
tableName: 'taskTarget',
pgColumns: TASK_TARGET_DATA_SEED_COLUMNS,
recordSeeds: TASK_TARGET_DATA_SEEDS,
},
]; ];
@Injectable() @Injectable()

View File

@ -1,28 +1,28 @@
import { OBJECT_MODEL_COMMON_FIELDS } from 'test/integration/constants/object-model-common-fields'; import { OBJECT_MODEL_COMMON_FIELDS } from 'test/integration/constants/object-model-common-fields';
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants'; import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants';
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util'; import { TEST_API_KEY_1_ID } from 'test/integration/constants/test-api-key-ids.constant';
import { performCreateManyOperation } from 'test/integration/graphql/utils/perform-create-many-operation.utils';
import { searchFactory } from 'test/integration/graphql/utils/search-factory.util';
import { EachTestingContext } from 'twenty-shared/testing';
import { import {
TEST_PERSON_1_ID, TEST_PERSON_1_ID,
TEST_PERSON_2_ID, TEST_PERSON_2_ID,
TEST_PERSON_3_ID, TEST_PERSON_3_ID,
} from 'test/integration/constants/test-person-ids.constants'; } from 'test/integration/constants/test-person-ids.constants';
import { TEST_API_KEY_1_ID } from 'test/integration/constants/test-api-key-ids.constant';
import { import {
TEST_PET_ID_1, TEST_PET_ID_1,
TEST_PET_ID_2, TEST_PET_ID_2,
} from 'test/integration/constants/test-pet-ids.constants'; } from 'test/integration/constants/test-pet-ids.constants';
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
import { performCreateManyOperation } from 'test/integration/graphql/utils/perform-create-many-operation.utils';
import { searchFactory } from 'test/integration/graphql/utils/search-factory.util';
import { deleteAllRecords } from 'test/integration/utils/delete-all-records'; import { deleteAllRecords } from 'test/integration/utils/delete-all-records';
import { EachTestingContext } from 'twenty-shared/testing';
import { SearchResultEdgeDTO } from 'src/engine/core-modules/search/dtos/search-result-edge.dto';
import { import {
decodeCursor, decodeCursor,
encodeCursorData, encodeCursorData,
} from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util'; } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util';
import { SearchCursor } from 'src/engine/core-modules/search/services/search.service';
import { SearchArgs } from 'src/engine/core-modules/search/dtos/search-args'; import { SearchArgs } from 'src/engine/core-modules/search/dtos/search-args';
import { SearchResultEdgeDTO } from 'src/engine/core-modules/search/dtos/search-result-edge.dto';
import { SearchCursor } from 'src/engine/core-modules/search/services/search.service';
describe('SearchResolver', () => { describe('SearchResolver', () => {
const [firstPerson, secondPerson, thirdPerson] = [ const [firstPerson, secondPerson, thirdPerson] = [
@ -48,6 +48,10 @@ describe('SearchResolver', () => {
await deleteAllRecords('person'); await deleteAllRecords('person');
await deleteAllRecords('company'); await deleteAllRecords('company');
await deleteAllRecords('opportunity'); await deleteAllRecords('opportunity');
await deleteAllRecords('note');
await deleteAllRecords('task');
await deleteAllRecords('noteTarget');
await deleteAllRecords('taskTarget');
await deleteAllRecords('_pet'); await deleteAllRecords('_pet');
await deleteAllRecords('_surveyResult'); await deleteAllRecords('_surveyResult');