Improve seeds 3 (#12740)
- Fix an issue where custom object were seeded with 2 views, and with the wrong icon - ACME becomes YCombinator - Allow 2 workspaces to have different metadata seeded - Add many seeds for messages - Add many seeds for calendar events - Randomize createdBy for person and companies --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
@ -3,8 +3,8 @@ import { Logger } from '@nestjs/common';
|
||||
import { Command, CommandRunner } from 'nest-commander';
|
||||
|
||||
import {
|
||||
SEED_ACME_WORKSPACE_ID,
|
||||
SEED_APPLE_WORKSPACE_ID,
|
||||
SEED_YCOMBINATOR_WORKSPACE_ID,
|
||||
} from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspaces.util';
|
||||
import { DevSeederService } from 'src/engine/workspace-manager/dev-seeder/services/dev-seeder.service';
|
||||
@Command({
|
||||
@ -13,7 +13,7 @@ import { DevSeederService } from 'src/engine/workspace-manager/dev-seeder/servic
|
||||
'Seed workspace with initial data. This command is intended for development only.',
|
||||
})
|
||||
export class DataSeedWorkspaceCommand extends CommandRunner {
|
||||
workspaceIds = [SEED_APPLE_WORKSPACE_ID, SEED_ACME_WORKSPACE_ID];
|
||||
workspaceIds = [SEED_APPLE_WORKSPACE_ID, SEED_YCOMBINATOR_WORKSPACE_ID];
|
||||
private readonly logger = new Logger(DataSeedWorkspaceCommand.name);
|
||||
|
||||
constructor(private readonly devSeederService: DevSeederService) {
|
||||
|
||||
@ -35,7 +35,7 @@ export class ObjectMetadataRelatedRecordsService {
|
||||
type: 'table',
|
||||
name: `All ${objectMetadata.labelPlural}`,
|
||||
key: 'INDEX',
|
||||
icon: objectMetadata.icon,
|
||||
icon: 'IconList',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -9,8 +9,8 @@ import { RoleService } from 'src/engine/metadata-modules/role/role.service';
|
||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||
import { USER_WORKSPACE_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-user-workspaces.util';
|
||||
import {
|
||||
SEED_ACME_WORKSPACE_ID,
|
||||
SEED_APPLE_WORKSPACE_ID,
|
||||
SEED_YCOMBINATOR_WORKSPACE_ID,
|
||||
} from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspaces.util';
|
||||
|
||||
@Injectable()
|
||||
@ -46,7 +46,7 @@ export class DevSeederPermissionsService {
|
||||
userWorkspaceId: USER_WORKSPACE_DATA_SEED_IDS.PHIL,
|
||||
roleId: guestRole.id,
|
||||
});
|
||||
} else if (workspaceId === SEED_ACME_WORKSPACE_ID) {
|
||||
} else if (workspaceId === SEED_YCOMBINATOR_WORKSPACE_ID) {
|
||||
adminUserWorkspaceId = USER_WORKSPACE_DATA_SEED_IDS.TIM_ACME;
|
||||
}
|
||||
|
||||
|
||||
@ -3,8 +3,8 @@ import { DataSource } from 'typeorm';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { USER_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-users.util';
|
||||
import {
|
||||
SEED_ACME_WORKSPACE_ID,
|
||||
SEED_APPLE_WORKSPACE_ID,
|
||||
SEED_YCOMBINATOR_WORKSPACE_ID,
|
||||
} from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspaces.util';
|
||||
|
||||
const tableName = 'userWorkspace';
|
||||
@ -46,7 +46,7 @@ export const seedUserWorkspaces = async (
|
||||
];
|
||||
}
|
||||
|
||||
if (workspaceId === SEED_ACME_WORKSPACE_ID) {
|
||||
if (workspaceId === SEED_YCOMBINATOR_WORKSPACE_ID) {
|
||||
userWorkspaces = [
|
||||
{
|
||||
id: USER_WORKSPACE_DATA_SEED_IDS.TIM_ACME,
|
||||
|
||||
@ -7,7 +7,8 @@ import { extractVersionMajorMinorPatch } from 'src/utils/version/extract-version
|
||||
const tableName = 'workspace';
|
||||
|
||||
export const SEED_APPLE_WORKSPACE_ID = '20202020-1c25-4d02-bf25-6aeccf7ea419';
|
||||
export const SEED_ACME_WORKSPACE_ID = '3b8e6458-5fc1-4e63-8563-008ccddaa6db';
|
||||
export const SEED_YCOMBINATOR_WORKSPACE_ID =
|
||||
'3b8e6458-5fc1-4e63-8563-008ccddaa6db';
|
||||
|
||||
export type SeedWorkspaceArgs = {
|
||||
dataSource: DataSource;
|
||||
@ -49,12 +50,12 @@ export const seedWorkspaces = async ({
|
||||
activationStatus: WorkspaceActivationStatus.PENDING_CREATION, // will be set to active after default role creation
|
||||
version: version,
|
||||
},
|
||||
[SEED_ACME_WORKSPACE_ID]: {
|
||||
id: SEED_ACME_WORKSPACE_ID,
|
||||
displayName: 'Acme',
|
||||
subdomain: 'acme',
|
||||
inviteHash: 'acme.dev-invite-hash',
|
||||
logo: 'https://logos-world.net/wp-content/uploads/2022/05/Acme-Logo-700x394.png',
|
||||
[SEED_YCOMBINATOR_WORKSPACE_ID]: {
|
||||
id: SEED_YCOMBINATOR_WORKSPACE_ID,
|
||||
displayName: 'YCombinator',
|
||||
subdomain: 'yc',
|
||||
inviteHash: 'yc.dev-invite-hash',
|
||||
logo: 'https://twentyhq.github.io/placeholder-images/workspaces/ycombinator-logo.png',
|
||||
activationStatus: WorkspaceActivationStatus.PENDING_CREATION, // will be set to active after default role creation
|
||||
version: version,
|
||||
},
|
||||
|
||||
@ -20,15 +20,57 @@ export const CALENDAR_CHANNEL_DATA_SEED_COLUMNS: (keyof CalendarChannelDataSeed)
|
||||
'isSyncEnabled',
|
||||
];
|
||||
|
||||
export const CALENDAR_CHANNEL_DATA_SEED_IDS = {
|
||||
TIM: '20202020-a40f-4faf-bb9f-c6f9945b8203',
|
||||
const GENERATE_CALENDAR_CHANNEL_IDS = (): Record<string, string> => {
|
||||
const CHANNEL_IDS: Record<string, string> = {};
|
||||
|
||||
CHANNEL_IDS['TIM'] = '20202020-a40f-4faf-bb9f-c6f9945b8203';
|
||||
CHANNEL_IDS['JONY'] = '20202020-a40f-4faf-bb9f-c6f9945b8204';
|
||||
CHANNEL_IDS['PHIL'] = '20202020-a40f-4faf-bb9f-c6f9945b8205';
|
||||
CHANNEL_IDS['COMPANY_MAIN'] = '20202020-a40f-4faf-bb9f-c6f9945b8206';
|
||||
CHANNEL_IDS['TEAM_CALENDAR'] = '20202020-a40f-4faf-bb9f-c6f9945b8207';
|
||||
|
||||
return CHANNEL_IDS;
|
||||
};
|
||||
|
||||
export const CALENDAR_CHANNEL_DATA_SEED_IDS = GENERATE_CALENDAR_CHANNEL_IDS();
|
||||
|
||||
export const CALENDAR_CHANNEL_DATA_SEEDS: CalendarChannelDataSeed[] = [
|
||||
{
|
||||
id: CALENDAR_CHANNEL_DATA_SEED_IDS.TIM,
|
||||
connectedAccountId: CONNECTED_ACCOUNT_DATA_SEED_IDS.TIM,
|
||||
handle: 'tim@apple.dev',
|
||||
visibility: CalendarChannelVisibility.METADATA,
|
||||
isContactAutoCreationEnabled: true,
|
||||
isSyncEnabled: true,
|
||||
},
|
||||
{
|
||||
id: CALENDAR_CHANNEL_DATA_SEED_IDS.JONY,
|
||||
connectedAccountId: CONNECTED_ACCOUNT_DATA_SEED_IDS.JONY,
|
||||
handle: 'jony@apple.dev',
|
||||
visibility: CalendarChannelVisibility.SHARE_EVERYTHING,
|
||||
isContactAutoCreationEnabled: true,
|
||||
isSyncEnabled: true,
|
||||
},
|
||||
{
|
||||
id: CALENDAR_CHANNEL_DATA_SEED_IDS.PHIL,
|
||||
connectedAccountId: CONNECTED_ACCOUNT_DATA_SEED_IDS.PHIL,
|
||||
handle: 'phil@apple.dev',
|
||||
visibility: CalendarChannelVisibility.METADATA,
|
||||
isContactAutoCreationEnabled: true,
|
||||
isSyncEnabled: true,
|
||||
},
|
||||
{
|
||||
id: CALENDAR_CHANNEL_DATA_SEED_IDS.COMPANY_MAIN,
|
||||
connectedAccountId: CONNECTED_ACCOUNT_DATA_SEED_IDS.TIM,
|
||||
handle: 'company-main@apple.dev',
|
||||
visibility: CalendarChannelVisibility.SHARE_EVERYTHING,
|
||||
isContactAutoCreationEnabled: true,
|
||||
isSyncEnabled: true,
|
||||
},
|
||||
{
|
||||
id: CALENDAR_CHANNEL_DATA_SEED_IDS.TEAM_CALENDAR,
|
||||
connectedAccountId: CONNECTED_ACCOUNT_DATA_SEED_IDS.TIM,
|
||||
handle: 'team-calendar@apple.dev',
|
||||
visibility: CalendarChannelVisibility.SHARE_EVERYTHING,
|
||||
isContactAutoCreationEnabled: true,
|
||||
isSyncEnabled: true,
|
||||
|
||||
@ -18,17 +18,82 @@ export const CALENDAR_CHANNEL_EVENT_ASSOCIATION_DATA_SEED_COLUMNS: (keyof Calend
|
||||
'recurringEventExternalId',
|
||||
];
|
||||
|
||||
export const CALENDAR_CHANNEL_EVENT_ASSOCIATION_DATA_SEED_IDS = {
|
||||
ID_1: '20202020-0687-4c41-b707-ed1bfca972a2',
|
||||
const GENERATE_CALENDAR_CHANNEL_EVENT_ASSOCIATION_IDS = (): Record<
|
||||
string,
|
||||
string
|
||||
> => {
|
||||
const ASSOCIATION_IDS: Record<string, string> = {};
|
||||
|
||||
for (let INDEX = 1; INDEX <= 800; INDEX++) {
|
||||
const HEX_INDEX = INDEX.toString(16).padStart(4, '0');
|
||||
|
||||
ASSOCIATION_IDS[`ID_${INDEX}`] =
|
||||
`20202020-${HEX_INDEX}-4e7c-8001-123456789abc`;
|
||||
}
|
||||
|
||||
return ASSOCIATION_IDS;
|
||||
};
|
||||
|
||||
export const CALENDAR_CHANNEL_EVENT_ASSOCIATION_DATA_SEEDS: CalendarChannelEventAssociationDataSeed[] =
|
||||
[
|
||||
{
|
||||
id: CALENDAR_CHANNEL_EVENT_ASSOCIATION_DATA_SEED_IDS.ID_1,
|
||||
calendarChannelId: CALENDAR_CHANNEL_DATA_SEED_IDS.TIM,
|
||||
calendarEventId: CALENDAR_EVENT_DATA_SEED_IDS.ID_1,
|
||||
eventExternalId: 'exampleExternalId',
|
||||
recurringEventExternalId: 'exampleRecurringExternalId',
|
||||
},
|
||||
];
|
||||
export const CALENDAR_CHANNEL_EVENT_ASSOCIATION_DATA_SEED_IDS =
|
||||
GENERATE_CALENDAR_CHANNEL_EVENT_ASSOCIATION_IDS();
|
||||
|
||||
const GENERATE_CALENDAR_CHANNEL_EVENT_ASSOCIATION_SEEDS =
|
||||
(): CalendarChannelEventAssociationDataSeed[] => {
|
||||
const ASSOCIATION_SEEDS: CalendarChannelEventAssociationDataSeed[] = [];
|
||||
|
||||
const EVENT_IDS = Object.keys(CALENDAR_EVENT_DATA_SEED_IDS).map(
|
||||
(key) =>
|
||||
CALENDAR_EVENT_DATA_SEED_IDS[
|
||||
key as keyof typeof CALENDAR_EVENT_DATA_SEED_IDS
|
||||
],
|
||||
);
|
||||
|
||||
const CHANNEL_IDS = [
|
||||
CALENDAR_CHANNEL_DATA_SEED_IDS.TIM,
|
||||
CALENDAR_CHANNEL_DATA_SEED_IDS.JONY,
|
||||
CALENDAR_CHANNEL_DATA_SEED_IDS.PHIL,
|
||||
CALENDAR_CHANNEL_DATA_SEED_IDS.COMPANY_MAIN,
|
||||
CALENDAR_CHANNEL_DATA_SEED_IDS.TEAM_CALENDAR,
|
||||
];
|
||||
|
||||
// Create associations for each event
|
||||
EVENT_IDS.forEach((eventId, index) => {
|
||||
// Distribute events across channels with weighted distribution
|
||||
let CHANNEL_ID: string;
|
||||
const CHANNEL_RAND = Math.random();
|
||||
|
||||
if (CHANNEL_RAND < 0.3) {
|
||||
// 30% - Tim's personal calendar
|
||||
CHANNEL_ID = CHANNEL_IDS[0]; // TIM
|
||||
} else if (CHANNEL_RAND < 0.45) {
|
||||
// 15% - Jony's personal calendar
|
||||
CHANNEL_ID = CHANNEL_IDS[1]; // JONY
|
||||
} else if (CHANNEL_RAND < 0.6) {
|
||||
// 15% - Phil's personal calendar
|
||||
CHANNEL_ID = CHANNEL_IDS[2]; // PHIL
|
||||
} else if (CHANNEL_RAND < 0.8) {
|
||||
// 20% - Company main calendar
|
||||
CHANNEL_ID = CHANNEL_IDS[3]; // COMPANY_MAIN
|
||||
} else {
|
||||
// 20% - Team calendar
|
||||
CHANNEL_ID = CHANNEL_IDS[4]; // TEAM_CALENDAR
|
||||
}
|
||||
|
||||
const ASSOCIATION_INDEX = index + 1;
|
||||
|
||||
ASSOCIATION_SEEDS.push({
|
||||
id: CALENDAR_CHANNEL_EVENT_ASSOCIATION_DATA_SEED_IDS[
|
||||
`ID_${ASSOCIATION_INDEX}`
|
||||
],
|
||||
calendarChannelId: CHANNEL_ID,
|
||||
calendarEventId: eventId,
|
||||
eventExternalId: `external_event_${ASSOCIATION_INDEX}@calendar.com`,
|
||||
recurringEventExternalId: `recurring_${ASSOCIATION_INDEX}@calendar.com`,
|
||||
});
|
||||
});
|
||||
|
||||
return ASSOCIATION_SEEDS;
|
||||
};
|
||||
|
||||
export const CALENDAR_CHANNEL_EVENT_ASSOCIATION_DATA_SEEDS =
|
||||
GENERATE_CALENDAR_CHANNEL_EVENT_ASSOCIATION_SEEDS();
|
||||
|
||||
@ -33,25 +33,198 @@ export const CALENDAR_EVENT_DATA_SEED_COLUMNS: (keyof CalendarEventDataSeed)[] =
|
||||
'conferenceLinkPrimaryLinkUrl',
|
||||
];
|
||||
|
||||
export const CALENDAR_EVENT_DATA_SEED_IDS = {
|
||||
ID_1: '20202020-1c0e-494c-a1b6-85b1c6fefaa5',
|
||||
const GENERATE_CALENDAR_EVENT_IDS = (): Record<string, string> => {
|
||||
const CALENDAR_EVENT_IDS: Record<string, string> = {};
|
||||
|
||||
for (let INDEX = 1; INDEX <= 800; INDEX++) {
|
||||
const HEX_INDEX = INDEX.toString(16).padStart(4, '0');
|
||||
|
||||
CALENDAR_EVENT_IDS[`ID_${INDEX}`] =
|
||||
`20202020-${HEX_INDEX}-4e7c-8001-123456789cde`;
|
||||
}
|
||||
|
||||
return CALENDAR_EVENT_IDS;
|
||||
};
|
||||
|
||||
export const CALENDAR_EVENT_DATA_SEEDS: CalendarEventDataSeed[] = [
|
||||
export const CALENDAR_EVENT_DATA_SEED_IDS = GENERATE_CALENDAR_EVENT_IDS();
|
||||
|
||||
const EVENT_TEMPLATES = [
|
||||
{
|
||||
id: CALENDAR_EVENT_DATA_SEED_IDS.ID_1,
|
||||
title: 'Meeting with Christoph',
|
||||
isCanceled: false,
|
||||
title: 'Team Standup',
|
||||
description:
|
||||
'Daily team synchronization meeting to discuss progress and blockers.',
|
||||
isFullDay: false,
|
||||
startsAt: new Date(new Date().setHours(10, 0)).toISOString(),
|
||||
endsAt: new Date(new Date().setHours(11, 0)).toISOString(),
|
||||
externalCreatedAt: new Date().toISOString(),
|
||||
externalUpdatedAt: new Date().toISOString(),
|
||||
description: 'Discuss project progress',
|
||||
location: 'Seattle',
|
||||
iCalUID: 'event1@calendar.com',
|
||||
conferenceSolution: 'Zoom',
|
||||
conferenceLinkPrimaryLinkLabel: 'https://zoom.us/j/1234567890',
|
||||
conferenceLinkPrimaryLinkUrl: 'https://zoom.us/j/1234567890',
|
||||
duration: 30, // minutes
|
||||
locations: ['Conference Room A', 'Zoom', 'Teams'],
|
||||
conferenceSolutions: ['Zoom', 'Teams', 'Google Meet'],
|
||||
},
|
||||
{
|
||||
title: 'Client Presentation',
|
||||
description:
|
||||
'Present project progress and next steps to client stakeholders.',
|
||||
isFullDay: false,
|
||||
duration: 60,
|
||||
locations: ['Client Office', 'Zoom', 'Conference Room B'],
|
||||
conferenceSolutions: ['Zoom', 'Teams', 'Google Meet'],
|
||||
},
|
||||
{
|
||||
title: 'Project Planning Session',
|
||||
description:
|
||||
'Strategic planning session for upcoming project milestones and deliverables.',
|
||||
isFullDay: false,
|
||||
duration: 90,
|
||||
locations: ['Conference Room C', 'Teams', 'Boardroom'],
|
||||
conferenceSolutions: ['Teams', 'Zoom', 'Google Meet'],
|
||||
},
|
||||
{
|
||||
title: 'One-on-One Meeting',
|
||||
description:
|
||||
'Regular one-on-one check-in to discuss performance and career development.',
|
||||
isFullDay: false,
|
||||
duration: 45,
|
||||
locations: ['Office', 'Zoom', 'Coffee Shop'],
|
||||
conferenceSolutions: ['Zoom', 'Teams', 'Google Meet'],
|
||||
},
|
||||
{
|
||||
title: 'Code Review Session',
|
||||
description: 'Collaborative code review and technical discussion.',
|
||||
isFullDay: false,
|
||||
duration: 60,
|
||||
locations: ['Dev Room', 'Zoom', 'Teams'],
|
||||
conferenceSolutions: ['Zoom', 'Teams', 'Google Meet'],
|
||||
},
|
||||
{
|
||||
title: 'Strategic Planning Workshop',
|
||||
description: 'Quarterly strategic planning and goal setting workshop.',
|
||||
isFullDay: true,
|
||||
duration: 480, // 8 hours
|
||||
locations: ['Offsite Location', 'Headquarters', 'Conference Center'],
|
||||
conferenceSolutions: ['Zoom', 'Teams', 'Google Meet'],
|
||||
},
|
||||
{
|
||||
title: 'Training Session',
|
||||
description: 'Professional development and skills training session.',
|
||||
isFullDay: false,
|
||||
duration: 120,
|
||||
locations: ['Training Room', 'Zoom', 'Teams'],
|
||||
conferenceSolutions: ['Zoom', 'Teams', 'Google Meet'],
|
||||
},
|
||||
{
|
||||
title: 'Customer Discovery Call',
|
||||
description: 'Customer interview to gather feedback and understand needs.',
|
||||
isFullDay: false,
|
||||
duration: 45,
|
||||
locations: ['Phone', 'Zoom', 'Teams'],
|
||||
conferenceSolutions: ['Zoom', 'Teams', 'Google Meet'],
|
||||
},
|
||||
{
|
||||
title: 'Budget Review Meeting',
|
||||
description: 'Quarterly budget review and financial planning session.',
|
||||
isFullDay: false,
|
||||
duration: 90,
|
||||
locations: ['Finance Office', 'Conference Room D', 'Zoom'],
|
||||
conferenceSolutions: ['Zoom', 'Teams', 'Google Meet'],
|
||||
},
|
||||
{
|
||||
title: 'Product Demo',
|
||||
description:
|
||||
'Product demonstration for potential customers and stakeholders.',
|
||||
isFullDay: false,
|
||||
duration: 60,
|
||||
locations: ['Demo Room', 'Zoom', 'Client Site'],
|
||||
conferenceSolutions: ['Zoom', 'Teams', 'Google Meet'],
|
||||
},
|
||||
{
|
||||
title: 'All Hands Meeting',
|
||||
description: 'Company-wide meeting for updates and announcements.',
|
||||
isFullDay: false,
|
||||
duration: 60,
|
||||
locations: ['Main Auditorium', 'Zoom', 'Teams'],
|
||||
conferenceSolutions: ['Zoom', 'Teams', 'Google Meet'],
|
||||
},
|
||||
{
|
||||
title: 'Sprint Retrospective',
|
||||
description:
|
||||
'Team retrospective to discuss what went well and areas for improvement.',
|
||||
isFullDay: false,
|
||||
duration: 75,
|
||||
locations: ['Conference Room E', 'Teams', 'Zoom'],
|
||||
conferenceSolutions: ['Teams', 'Zoom', 'Google Meet'],
|
||||
},
|
||||
];
|
||||
|
||||
const GENERATE_CALENDAR_EVENT_SEEDS = (): CalendarEventDataSeed[] => {
|
||||
const CALENDAR_EVENT_SEEDS: CalendarEventDataSeed[] = [];
|
||||
|
||||
for (let INDEX = 1; INDEX <= 800; INDEX++) {
|
||||
const TEMPLATE_INDEX = (INDEX - 1) % EVENT_TEMPLATES.length;
|
||||
const TEMPLATE = EVENT_TEMPLATES[TEMPLATE_INDEX];
|
||||
|
||||
// Random date within the last 6 months and next 6 months
|
||||
const NOW = new Date();
|
||||
const RANDOM_DAYS_OFFSET = Math.floor(Math.random() * 365) - 182; // -182 to +182 days
|
||||
const EVENT_DATE = new Date(
|
||||
NOW.getTime() + RANDOM_DAYS_OFFSET * 24 * 60 * 60 * 1000,
|
||||
);
|
||||
|
||||
// Random time between 9 AM and 6 PM
|
||||
const START_HOUR = 9 + Math.floor(Math.random() * 9);
|
||||
const START_MINUTE = Math.floor(Math.random() * 4) * 15; // 0, 15, 30, or 45 minutes
|
||||
|
||||
const START_TIME = new Date(EVENT_DATE);
|
||||
|
||||
START_TIME.setHours(START_HOUR, START_MINUTE, 0, 0);
|
||||
|
||||
const END_TIME = new Date(START_TIME);
|
||||
|
||||
if (TEMPLATE.isFullDay) {
|
||||
END_TIME.setDate(END_TIME.getDate() + 1);
|
||||
END_TIME.setHours(0, 0, 0, 0);
|
||||
} else {
|
||||
END_TIME.setMinutes(END_TIME.getMinutes() + TEMPLATE.duration);
|
||||
}
|
||||
|
||||
// Random location and conference solution
|
||||
const LOCATION =
|
||||
TEMPLATE.locations[Math.floor(Math.random() * TEMPLATE.locations.length)];
|
||||
const CONFERENCE_SOLUTION =
|
||||
TEMPLATE.conferenceSolutions[
|
||||
Math.floor(Math.random() * TEMPLATE.conferenceSolutions.length)
|
||||
];
|
||||
|
||||
// 5% chance of being cancelled
|
||||
const IS_CANCELLED = Math.random() < 0.05;
|
||||
|
||||
// Generate conference link if it's an online meeting
|
||||
const CONFERENCE_LINK = ['Zoom', 'Teams', 'Google Meet'].includes(
|
||||
CONFERENCE_SOLUTION,
|
||||
)
|
||||
? `https://${CONFERENCE_SOLUTION.toLowerCase().replace(' ', '')}.com/j/${Math.floor(Math.random() * 9000000000) + 1000000000}`
|
||||
: '';
|
||||
|
||||
CALENDAR_EVENT_SEEDS.push({
|
||||
id: CALENDAR_EVENT_DATA_SEED_IDS[`ID_${INDEX}`],
|
||||
title: TEMPLATE.title,
|
||||
isCanceled: IS_CANCELLED,
|
||||
isFullDay: TEMPLATE.isFullDay,
|
||||
startsAt: START_TIME.toISOString(),
|
||||
endsAt: END_TIME.toISOString(),
|
||||
externalCreatedAt: new Date(
|
||||
START_TIME.getTime() - Math.random() * 7 * 24 * 60 * 60 * 1000,
|
||||
).toISOString(),
|
||||
externalUpdatedAt: new Date(
|
||||
START_TIME.getTime() - Math.random() * 24 * 60 * 60 * 1000,
|
||||
).toISOString(),
|
||||
description: TEMPLATE.description,
|
||||
location: LOCATION,
|
||||
iCalUID: `event${INDEX}@calendar.twentycrm.com`,
|
||||
conferenceSolution: CONFERENCE_SOLUTION,
|
||||
conferenceLinkPrimaryLinkLabel: CONFERENCE_LINK,
|
||||
conferenceLinkPrimaryLinkUrl: CONFERENCE_LINK,
|
||||
});
|
||||
}
|
||||
|
||||
return CALENDAR_EVENT_SEEDS;
|
||||
};
|
||||
|
||||
export const CALENDAR_EVENT_DATA_SEEDS = GENERATE_CALENDAR_EVENT_SEEDS();
|
||||
|
||||
@ -25,31 +25,280 @@ export const CALENDAR_EVENT_PARTICIPANT_DATA_SEED_COLUMNS = [
|
||||
'workspaceMemberId',
|
||||
];
|
||||
|
||||
export const CALENDAR_EVENT_PARTICIPANT_DATA_SEED_IDS = {
|
||||
ONE: '20202020-fb8f-4f0d-a36e-950e185401d4',
|
||||
TWO: '20202020-0722-40d7-9e55-cb5d00cfb654',
|
||||
const GENERATE_CALENDAR_EVENT_PARTICIPANT_IDS = (): Record<string, string> => {
|
||||
const PARTICIPANT_IDS: Record<string, string> = {};
|
||||
|
||||
for (let INDEX = 1; INDEX <= 2000; INDEX++) {
|
||||
const HEX_INDEX = INDEX.toString(16).padStart(4, '0');
|
||||
|
||||
PARTICIPANT_IDS[`ID_${INDEX}`] =
|
||||
`20202020-${HEX_INDEX}-4e7c-8001-123456789def`;
|
||||
}
|
||||
|
||||
return PARTICIPANT_IDS;
|
||||
};
|
||||
|
||||
export const CALENDAR_EVENT_PARTICIPANT_DATA_SEEDS: CalendarEventParticipantDataSeed[] =
|
||||
[
|
||||
{
|
||||
id: CALENDAR_EVENT_PARTICIPANT_DATA_SEED_IDS.ONE,
|
||||
calendarEventId: CALENDAR_EVENT_DATA_SEED_IDS.ID_1,
|
||||
handle: 'christoph.calisto@linkedin.com',
|
||||
displayName: 'Christoph Calisto',
|
||||
isOrganizer: true,
|
||||
responseStatus: CalendarEventParticipantResponseStatus.ACCEPTED,
|
||||
personId: PERSON_DATA_SEED_IDS.ID_1,
|
||||
workspaceMemberId: null,
|
||||
},
|
||||
{
|
||||
id: CALENDAR_EVENT_PARTICIPANT_DATA_SEED_IDS.TWO,
|
||||
calendarEventId: CALENDAR_EVENT_DATA_SEED_IDS.ID_1,
|
||||
handle: 'tim@apple.com',
|
||||
displayName: 'Tim Apple',
|
||||
isOrganizer: false,
|
||||
responseStatus: CalendarEventParticipantResponseStatus.ACCEPTED,
|
||||
personId: null,
|
||||
workspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
|
||||
},
|
||||
export const CALENDAR_EVENT_PARTICIPANT_DATA_SEED_IDS =
|
||||
GENERATE_CALENDAR_EVENT_PARTICIPANT_IDS();
|
||||
|
||||
const FAKE_PARTICIPANTS = [
|
||||
{ name: 'Alex Johnson', email: 'alex.johnson@company.com' },
|
||||
{ name: 'Sarah Williams', email: 'sarah.williams@company.com' },
|
||||
{ name: 'Michael Chen', email: 'michael.chen@company.com' },
|
||||
{ name: 'Emily Davis', email: 'emily.davis@company.com' },
|
||||
{ name: 'David Rodriguez', email: 'david.rodriguez@company.com' },
|
||||
{ name: 'Lisa Anderson', email: 'lisa.anderson@company.com' },
|
||||
{ name: 'James Wilson', email: 'james.wilson@company.com' },
|
||||
{ name: 'Jennifer Martinez', email: 'jennifer.martinez@company.com' },
|
||||
{ name: 'Robert Taylor', email: 'robert.taylor@company.com' },
|
||||
{ name: 'Maria Garcia', email: 'maria.garcia@company.com' },
|
||||
];
|
||||
|
||||
type EventParticipantData = {
|
||||
handle: string;
|
||||
displayName: string;
|
||||
personId: string | null;
|
||||
workspaceMemberId: string | null;
|
||||
};
|
||||
|
||||
const GET_RANDOM_FAKE_PARTICIPANT = () => {
|
||||
return FAKE_PARTICIPANTS[
|
||||
Math.floor(Math.random() * FAKE_PARTICIPANTS.length)
|
||||
];
|
||||
};
|
||||
|
||||
const FIND_UNUSED_PERSON_ID = (
|
||||
personIds: string[],
|
||||
usedPersonIds: Set<string>,
|
||||
): string | null => {
|
||||
const AVAILABLE_IDS = personIds.filter((id) => !usedPersonIds.has(id));
|
||||
|
||||
if (AVAILABLE_IDS.length === 0) return null;
|
||||
|
||||
return AVAILABLE_IDS[Math.floor(Math.random() * AVAILABLE_IDS.length)];
|
||||
};
|
||||
|
||||
const FIND_UNUSED_WORKSPACE_MEMBER_ID = (
|
||||
workspaceMemberIds: string[],
|
||||
usedWorkspaceMemberIds: Set<string>,
|
||||
): string | null => {
|
||||
const AVAILABLE_IDS = workspaceMemberIds.filter(
|
||||
(id) => !usedWorkspaceMemberIds.has(id),
|
||||
);
|
||||
|
||||
if (AVAILABLE_IDS.length === 0) return null;
|
||||
|
||||
return AVAILABLE_IDS[Math.floor(Math.random() * AVAILABLE_IDS.length)];
|
||||
};
|
||||
|
||||
const CREATE_PERSON_EVENT_PARTICIPANT = (
|
||||
personIds: string[],
|
||||
usedPersonIds: Set<string>,
|
||||
): EventParticipantData | null => {
|
||||
const PERSON_ID = FIND_UNUSED_PERSON_ID(personIds, usedPersonIds);
|
||||
|
||||
if (!PERSON_ID) return null;
|
||||
|
||||
usedPersonIds.add(PERSON_ID);
|
||||
const PERSON_INDEX = personIds.indexOf(PERSON_ID) + 1;
|
||||
|
||||
return {
|
||||
handle: `person${PERSON_INDEX}@company.com`,
|
||||
displayName: `Person ${PERSON_INDEX}`,
|
||||
personId: PERSON_ID,
|
||||
workspaceMemberId: null,
|
||||
};
|
||||
};
|
||||
|
||||
const CREATE_WORKSPACE_MEMBER_EVENT_PARTICIPANT = (
|
||||
workspaceMemberIds: string[],
|
||||
usedWorkspaceMemberIds: Set<string>,
|
||||
): EventParticipantData | null => {
|
||||
const WORKSPACE_MEMBER_ID = FIND_UNUSED_WORKSPACE_MEMBER_ID(
|
||||
workspaceMemberIds,
|
||||
usedWorkspaceMemberIds,
|
||||
);
|
||||
|
||||
if (!WORKSPACE_MEMBER_ID) return null;
|
||||
|
||||
usedWorkspaceMemberIds.add(WORKSPACE_MEMBER_ID);
|
||||
|
||||
switch (WORKSPACE_MEMBER_ID) {
|
||||
case WORKSPACE_MEMBER_DATA_SEED_IDS.TIM:
|
||||
return {
|
||||
handle: 'tim@apple.com',
|
||||
displayName: 'Tim Apple',
|
||||
personId: null,
|
||||
workspaceMemberId: WORKSPACE_MEMBER_ID,
|
||||
};
|
||||
case WORKSPACE_MEMBER_DATA_SEED_IDS.JONY:
|
||||
return {
|
||||
handle: 'jony.ive@apple.com',
|
||||
displayName: 'Jony Ive',
|
||||
personId: null,
|
||||
workspaceMemberId: WORKSPACE_MEMBER_ID,
|
||||
};
|
||||
case WORKSPACE_MEMBER_DATA_SEED_IDS.PHIL:
|
||||
return {
|
||||
handle: 'phil.schiller@apple.com',
|
||||
displayName: 'Phil Schiller',
|
||||
personId: null,
|
||||
workspaceMemberId: WORKSPACE_MEMBER_ID,
|
||||
};
|
||||
default:
|
||||
return {
|
||||
handle: 'member@company.com',
|
||||
displayName: 'Workspace Member',
|
||||
personId: null,
|
||||
workspaceMemberId: WORKSPACE_MEMBER_ID,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const CREATE_FAKE_EVENT_PARTICIPANT = (): EventParticipantData => {
|
||||
const FAKE = GET_RANDOM_FAKE_PARTICIPANT();
|
||||
|
||||
return {
|
||||
handle: FAKE.email,
|
||||
displayName: FAKE.name,
|
||||
personId: null,
|
||||
workspaceMemberId: null,
|
||||
};
|
||||
};
|
||||
|
||||
const CREATE_EVENT_PARTICIPANT_DATA = (
|
||||
personIds: string[],
|
||||
workspaceMemberIds: string[],
|
||||
usedPersonIds: Set<string>,
|
||||
usedWorkspaceMemberIds: Set<string>,
|
||||
): EventParticipantData => {
|
||||
const PARTICIPANT_TYPE = Math.random();
|
||||
|
||||
// Try person participant (40% chance)
|
||||
if (PARTICIPANT_TYPE < 0.4) {
|
||||
const PERSON_PARTICIPANT = CREATE_PERSON_EVENT_PARTICIPANT(
|
||||
personIds,
|
||||
usedPersonIds,
|
||||
);
|
||||
|
||||
if (PERSON_PARTICIPANT) return PERSON_PARTICIPANT;
|
||||
}
|
||||
|
||||
// Try workspace member participant (20% chance, 0.4-0.6 range)
|
||||
if (PARTICIPANT_TYPE >= 0.4 && PARTICIPANT_TYPE < 0.6) {
|
||||
const WORKSPACE_PARTICIPANT = CREATE_WORKSPACE_MEMBER_EVENT_PARTICIPANT(
|
||||
workspaceMemberIds,
|
||||
usedWorkspaceMemberIds,
|
||||
);
|
||||
|
||||
if (WORKSPACE_PARTICIPANT) return WORKSPACE_PARTICIPANT;
|
||||
}
|
||||
|
||||
// Fallback to fake participant
|
||||
return CREATE_FAKE_EVENT_PARTICIPANT();
|
||||
};
|
||||
|
||||
const GET_PARTICIPANT_COUNT = (): number => {
|
||||
const RAND = Math.random();
|
||||
|
||||
if (RAND < 0.2) return 1; // 20% - single participant
|
||||
if (RAND < 0.5) return 2; // 30% - two participants
|
||||
if (RAND < 0.8) return 3; // 30% - three participants
|
||||
|
||||
return 4; // 20% - four participants
|
||||
};
|
||||
|
||||
const GET_RESPONSE_STATUS = (
|
||||
isOrganizer: boolean,
|
||||
): CalendarEventParticipantResponseStatus => {
|
||||
if (isOrganizer) {
|
||||
return CalendarEventParticipantResponseStatus.ACCEPTED;
|
||||
}
|
||||
|
||||
const RESPONSE_RAND = Math.random();
|
||||
|
||||
if (RESPONSE_RAND < 0.7)
|
||||
return CalendarEventParticipantResponseStatus.ACCEPTED;
|
||||
if (RESPONSE_RAND < 0.85)
|
||||
return CalendarEventParticipantResponseStatus.TENTATIVE;
|
||||
if (RESPONSE_RAND < 0.95)
|
||||
return CalendarEventParticipantResponseStatus.NEEDS_ACTION;
|
||||
|
||||
return CalendarEventParticipantResponseStatus.DECLINED;
|
||||
};
|
||||
|
||||
const CREATE_EVENT_PARTICIPANTS = (
|
||||
eventId: string,
|
||||
personIds: string[],
|
||||
workspaceMemberIds: string[],
|
||||
participantIndex: number,
|
||||
): { participants: CalendarEventParticipantDataSeed[]; nextIndex: number } => {
|
||||
const PARTICIPANTS: CalendarEventParticipantDataSeed[] = [];
|
||||
const PARTICIPANT_COUNT = GET_PARTICIPANT_COUNT();
|
||||
|
||||
const USED_PERSON_IDS = new Set<string>();
|
||||
const USED_WORKSPACE_MEMBER_IDS = new Set<string>();
|
||||
|
||||
for (let I = 0; I < PARTICIPANT_COUNT; I++) {
|
||||
const IS_ORGANIZER = I === 0;
|
||||
|
||||
const PARTICIPANT_DATA = CREATE_EVENT_PARTICIPANT_DATA(
|
||||
personIds,
|
||||
workspaceMemberIds,
|
||||
USED_PERSON_IDS,
|
||||
USED_WORKSPACE_MEMBER_IDS,
|
||||
);
|
||||
|
||||
const RESPONSE_STATUS = GET_RESPONSE_STATUS(IS_ORGANIZER);
|
||||
|
||||
PARTICIPANTS.push({
|
||||
id: CALENDAR_EVENT_PARTICIPANT_DATA_SEED_IDS[`ID_${participantIndex}`],
|
||||
calendarEventId: eventId,
|
||||
handle: PARTICIPANT_DATA.handle,
|
||||
displayName: PARTICIPANT_DATA.displayName,
|
||||
isOrganizer: IS_ORGANIZER,
|
||||
responseStatus: RESPONSE_STATUS,
|
||||
personId: PARTICIPANT_DATA.personId,
|
||||
workspaceMemberId: PARTICIPANT_DATA.workspaceMemberId,
|
||||
});
|
||||
|
||||
participantIndex++;
|
||||
}
|
||||
|
||||
return { participants: PARTICIPANTS, nextIndex: participantIndex };
|
||||
};
|
||||
|
||||
const GENERATE_CALENDAR_EVENT_PARTICIPANT_SEEDS =
|
||||
(): CalendarEventParticipantDataSeed[] => {
|
||||
const PARTICIPANT_SEEDS: CalendarEventParticipantDataSeed[] = [];
|
||||
let PARTICIPANT_INDEX = 1;
|
||||
|
||||
const EVENT_IDS = Object.keys(CALENDAR_EVENT_DATA_SEED_IDS).map(
|
||||
(key) =>
|
||||
CALENDAR_EVENT_DATA_SEED_IDS[
|
||||
key as keyof typeof CALENDAR_EVENT_DATA_SEED_IDS
|
||||
],
|
||||
);
|
||||
|
||||
const PERSON_IDS = Object.keys(PERSON_DATA_SEED_IDS).map(
|
||||
(key) => PERSON_DATA_SEED_IDS[key as keyof typeof PERSON_DATA_SEED_IDS],
|
||||
);
|
||||
const WORKSPACE_MEMBER_IDS = Object.values(WORKSPACE_MEMBER_DATA_SEED_IDS);
|
||||
|
||||
for (const EVENT_ID of EVENT_IDS) {
|
||||
const RESULT = CREATE_EVENT_PARTICIPANTS(
|
||||
EVENT_ID,
|
||||
PERSON_IDS,
|
||||
WORKSPACE_MEMBER_IDS,
|
||||
PARTICIPANT_INDEX,
|
||||
);
|
||||
|
||||
PARTICIPANT_SEEDS.push(...RESULT.participants);
|
||||
PARTICIPANT_INDEX = RESULT.nextIndex;
|
||||
}
|
||||
|
||||
return PARTICIPANT_SEEDS;
|
||||
};
|
||||
|
||||
export const CALENDAR_EVENT_PARTICIPANT_DATA_SEEDS =
|
||||
GENERATE_CALENDAR_EVENT_PARTICIPANT_SEEDS();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -33,12 +33,20 @@ export const MESSAGE_CHANNEL_DATA_SEED_COLUMNS: (keyof MessageChannelDataSeed)[]
|
||||
'syncStage',
|
||||
];
|
||||
|
||||
export const MESSAGE_CHANNEL_DATA_SEED_IDS = {
|
||||
TIM: '20202020-9b80-4c2c-a597-383db48de1d6',
|
||||
JONY: '20202020-5ffe-4b32-814a-983d5e4911cd',
|
||||
PHIL: '20202020-e2f1-49b5-85d2-5d3a3386990c',
|
||||
const GENERATE_MESSAGE_CHANNEL_IDS = (): Record<string, string> => {
|
||||
const CHANNEL_IDS: Record<string, string> = {};
|
||||
|
||||
CHANNEL_IDS['TIM'] = '20202020-9b80-4c2c-a597-383db48de1d6';
|
||||
CHANNEL_IDS['JONY'] = '20202020-5ffe-4b32-814a-983d5e4911cd';
|
||||
CHANNEL_IDS['PHIL'] = '20202020-e2f1-49b5-85d2-5d3a3386990c';
|
||||
CHANNEL_IDS['SUPPORT'] = '20202020-e2f1-49b5-85d2-5d3a3386990d';
|
||||
CHANNEL_IDS['SALES'] = '20202020-e2f1-49b5-85d2-5d3a3386990e';
|
||||
|
||||
return CHANNEL_IDS;
|
||||
};
|
||||
|
||||
export const MESSAGE_CHANNEL_DATA_SEED_IDS = GENERATE_MESSAGE_CHANNEL_IDS();
|
||||
|
||||
export const MESSAGE_CHANNEL_DATA_SEEDS: MessageChannelDataSeed[] = [
|
||||
{
|
||||
id: MESSAGE_CHANNEL_DATA_SEED_IDS.TIM,
|
||||
@ -79,4 +87,30 @@ export const MESSAGE_CHANNEL_DATA_SEEDS: MessageChannelDataSeed[] = [
|
||||
visibility: MessageChannelVisibility.SHARE_EVERYTHING,
|
||||
syncStage: MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING,
|
||||
},
|
||||
{
|
||||
id: MESSAGE_CHANNEL_DATA_SEED_IDS.SUPPORT,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
isContactAutoCreationEnabled: true,
|
||||
type: 'email',
|
||||
connectedAccountId: CONNECTED_ACCOUNT_DATA_SEED_IDS.TIM, // Use TIM's connected account for shared inbox
|
||||
handle: 'support@apple.dev',
|
||||
isSyncEnabled: false,
|
||||
visibility: MessageChannelVisibility.SHARE_EVERYTHING,
|
||||
syncStage: MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING,
|
||||
},
|
||||
{
|
||||
id: MESSAGE_CHANNEL_DATA_SEED_IDS.SALES,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
isContactAutoCreationEnabled: true,
|
||||
type: 'email',
|
||||
connectedAccountId: CONNECTED_ACCOUNT_DATA_SEED_IDS.TIM, // Use TIM's connected account for shared inbox
|
||||
handle: 'sales@apple.dev',
|
||||
isSyncEnabled: false,
|
||||
visibility: MessageChannelVisibility.SHARE_EVERYTHING,
|
||||
syncStage: MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING,
|
||||
},
|
||||
];
|
||||
|
||||
@ -7,10 +7,10 @@ type MessageChannelMessageAssociationDataSeed = {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
deletedAt: Date | null;
|
||||
messageThreadExternalId: string | null;
|
||||
messageExternalId: string | null;
|
||||
messageId: string;
|
||||
messageExternalId: string;
|
||||
messageThreadExternalId: string;
|
||||
messageChannelId: string;
|
||||
messageId: string;
|
||||
direction: MessageDirection;
|
||||
};
|
||||
|
||||
@ -20,52 +20,100 @@ export const MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_DATA_SEED_COLUMNS: (keyof Messa
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'deletedAt',
|
||||
'messageThreadExternalId',
|
||||
'messageExternalId',
|
||||
'messageId',
|
||||
'messageThreadExternalId',
|
||||
'messageChannelId',
|
||||
'messageId',
|
||||
'direction',
|
||||
];
|
||||
|
||||
export const MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_DATA_SEED_IDS = {
|
||||
ID_1: '20202020-cc69-44ef-a82c-600c0dbf39ba',
|
||||
ID_2: '20202020-d80e-4a13-b10b-72ba09082668',
|
||||
ID_3: '20202020-e6ec-4c8a-b431-0901eaf395a9',
|
||||
const GENERATE_MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_IDS = (): Record<
|
||||
string,
|
||||
string
|
||||
> => {
|
||||
const ASSOCIATION_IDS: Record<string, string> = {};
|
||||
|
||||
for (let INDEX = 1; INDEX <= 600; INDEX++) {
|
||||
const HEX_INDEX = INDEX.toString(16).padStart(4, '0');
|
||||
|
||||
ASSOCIATION_IDS[`ID_${INDEX}`] =
|
||||
`20202020-${HEX_INDEX}-4e7c-8001-123456789bcd`;
|
||||
}
|
||||
|
||||
return ASSOCIATION_IDS;
|
||||
};
|
||||
|
||||
export const MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_DATA_SEEDS: MessageChannelMessageAssociationDataSeed[] =
|
||||
[
|
||||
{
|
||||
id: MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_DATA_SEED_IDS.ID_1,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
messageThreadExternalId: null,
|
||||
messageExternalId: null,
|
||||
messageId: MESSAGE_DATA_SEED_IDS.ID_1,
|
||||
messageChannelId: MESSAGE_CHANNEL_DATA_SEED_IDS.TIM,
|
||||
direction: MessageDirection.OUTGOING,
|
||||
},
|
||||
{
|
||||
id: MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_DATA_SEED_IDS.ID_2,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
messageThreadExternalId: null,
|
||||
messageExternalId: null,
|
||||
messageId: MESSAGE_DATA_SEED_IDS.ID_2,
|
||||
messageChannelId: MESSAGE_CHANNEL_DATA_SEED_IDS.TIM,
|
||||
direction: MessageDirection.OUTGOING,
|
||||
},
|
||||
{
|
||||
id: MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_DATA_SEED_IDS.ID_3,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
messageThreadExternalId: null,
|
||||
messageExternalId: null,
|
||||
messageId: MESSAGE_DATA_SEED_IDS.ID_3,
|
||||
messageChannelId: MESSAGE_CHANNEL_DATA_SEED_IDS.TIM,
|
||||
direction: MessageDirection.INCOMING,
|
||||
},
|
||||
];
|
||||
export const MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_DATA_SEED_IDS =
|
||||
GENERATE_MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_IDS();
|
||||
|
||||
const GENERATE_MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_SEEDS =
|
||||
(): MessageChannelMessageAssociationDataSeed[] => {
|
||||
const ASSOCIATION_SEEDS: MessageChannelMessageAssociationDataSeed[] = [];
|
||||
|
||||
const MESSAGE_IDS = Object.keys(MESSAGE_DATA_SEED_IDS).map(
|
||||
(key) => MESSAGE_DATA_SEED_IDS[key as keyof typeof MESSAGE_DATA_SEED_IDS],
|
||||
);
|
||||
|
||||
MESSAGE_IDS.forEach((messageId, index) => {
|
||||
const ASSOCIATION_INDEX = index + 1;
|
||||
|
||||
const NOW = new Date();
|
||||
const RANDOM_DAYS_OFFSET = Math.floor(Math.random() * 90);
|
||||
const ASSOCIATION_DATE = new Date(
|
||||
NOW.getTime() - RANDOM_DAYS_OFFSET * 24 * 60 * 60 * 1000,
|
||||
);
|
||||
|
||||
const CHANNEL_WEIGHT = Math.random();
|
||||
let CHANNEL_ID: string;
|
||||
|
||||
// Tim's email (40% weight)
|
||||
if (CHANNEL_WEIGHT < 0.4) {
|
||||
CHANNEL_ID = MESSAGE_CHANNEL_DATA_SEED_IDS.TIM;
|
||||
}
|
||||
// Jony's email (20% weight)
|
||||
else if (CHANNEL_WEIGHT < 0.6) {
|
||||
CHANNEL_ID = MESSAGE_CHANNEL_DATA_SEED_IDS.JONY;
|
||||
}
|
||||
// Phil's email (15% weight)
|
||||
else if (CHANNEL_WEIGHT < 0.75) {
|
||||
CHANNEL_ID = MESSAGE_CHANNEL_DATA_SEED_IDS.PHIL;
|
||||
}
|
||||
// Support channel (15% weight)
|
||||
else if (CHANNEL_WEIGHT < 0.9) {
|
||||
CHANNEL_ID = MESSAGE_CHANNEL_DATA_SEED_IDS.SUPPORT;
|
||||
}
|
||||
// Sales channel (10% weight)
|
||||
else {
|
||||
CHANNEL_ID = MESSAGE_CHANNEL_DATA_SEED_IDS.SALES;
|
||||
}
|
||||
|
||||
// 50/50 split between incoming and outgoing messages
|
||||
const DIRECTION: MessageDirection =
|
||||
Math.random() < 0.5
|
||||
? MessageDirection.INCOMING
|
||||
: MessageDirection.OUTGOING;
|
||||
|
||||
// Generate unique external IDs for email sync
|
||||
const MESSAGE_EXTERNAL_ID = `msg-${ASSOCIATION_INDEX}-${Date.now()}`;
|
||||
const MESSAGE_THREAD_EXTERNAL_ID = `thread-${Math.floor(ASSOCIATION_INDEX / 2)}-${Date.now()}`;
|
||||
|
||||
ASSOCIATION_SEEDS.push({
|
||||
id: MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_DATA_SEED_IDS[
|
||||
`ID_${ASSOCIATION_INDEX}`
|
||||
],
|
||||
createdAt: ASSOCIATION_DATE,
|
||||
updatedAt: ASSOCIATION_DATE,
|
||||
deletedAt: null,
|
||||
messageExternalId: MESSAGE_EXTERNAL_ID,
|
||||
messageThreadExternalId: MESSAGE_THREAD_EXTERNAL_ID,
|
||||
messageChannelId: CHANNEL_ID,
|
||||
messageId: messageId,
|
||||
direction: DIRECTION,
|
||||
});
|
||||
});
|
||||
|
||||
return ASSOCIATION_SEEDS;
|
||||
};
|
||||
|
||||
export const MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_DATA_SEEDS =
|
||||
GENERATE_MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_SEEDS();
|
||||
|
||||
@ -24,44 +24,116 @@ export const MESSAGE_DATA_SEED_COLUMNS: (keyof MessageDataSeed)[] = [
|
||||
'headerMessageId',
|
||||
];
|
||||
|
||||
export const MESSAGE_DATA_SEED_IDS = {
|
||||
ID_1: '20202020-2b8a-405d-8f42-e820ca921421',
|
||||
ID_2: '20202020-04c8-4f24-93f2-764948e95014',
|
||||
ID_3: '20202020-ac6b-4f86-87a2-5f5f9d1b6481',
|
||||
const GENERATE_MESSAGE_IDS = (): Record<string, string> => {
|
||||
const MESSAGE_IDS: Record<string, string> = {};
|
||||
|
||||
for (let INDEX = 1; INDEX <= 600; INDEX++) {
|
||||
const HEX_INDEX = INDEX.toString(16).padStart(4, '0');
|
||||
|
||||
MESSAGE_IDS[`ID_${INDEX}`] = `20202020-${HEX_INDEX}-4e7c-8001-123456789bcd`;
|
||||
}
|
||||
|
||||
return MESSAGE_IDS;
|
||||
};
|
||||
|
||||
export const MESSAGE_DATA_SEEDS: MessageDataSeed[] = [
|
||||
export const MESSAGE_DATA_SEED_IDS = GENERATE_MESSAGE_IDS();
|
||||
|
||||
const EMAIL_TEMPLATES = [
|
||||
{
|
||||
id: MESSAGE_DATA_SEED_IDS.ID_1,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
receivedAt: new Date(),
|
||||
text: 'Hello, \n I hope this email finds you well. I am writing to request a meeting. I believe it would be beneficial for both parties to collaborate and explore potential opportunities. Would you be available for a meeting sometime next week? Please let me know your availability, and I will arrange a suitable time. \n Looking forward to your response.\n Best regards',
|
||||
subject: 'Meeting Request',
|
||||
messageThreadId: MESSAGE_THREAD_DATA_SEED_IDS.ID_1,
|
||||
headerMessageId: '99ef24a8-2b8a-405d-8f42-e820ca921421',
|
||||
text: 'Hello,\n\nI hope this email finds you well. I am writing to request a meeting. I believe it would be beneficial for both parties to collaborate and explore potential opportunities.\n\nWould you be available for a meeting sometime next week? Please let me know your availability, and I will arrange a suitable time.\n\nLooking forward to your response.\n\nBest regards',
|
||||
},
|
||||
{
|
||||
id: MESSAGE_DATA_SEED_IDS.ID_2,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
receivedAt: new Date(),
|
||||
text: 'Good Morning,\n I am writing to inquire about information. Could you please provide me with details regarding this topic? \n Your assistance in this matter would be greatly appreciated. Thank you in advance for your prompt response. \n Best regards,Tim',
|
||||
subject: 'Inquiry Regarding Topic',
|
||||
messageThreadId: MESSAGE_THREAD_DATA_SEED_IDS.ID_2,
|
||||
headerMessageId: '8f804a9a-04c8-4f24-93f2-764948e95014',
|
||||
subject: 'Project Update',
|
||||
text: 'Hi there,\n\nI wanted to provide you with a quick update on our current project status. We have made significant progress and are on track to meet our deadline.\n\nKey accomplishments this week:\n- Completed the initial design phase\n- Gathered stakeholder feedback\n- Finalized technical requirements\n\nNext steps will be shared in our upcoming meeting.\n\nBest regards',
|
||||
},
|
||||
{
|
||||
subject: 'Invoice for Services',
|
||||
text: "Dear Sir/Madam,\n\nPlease find attached the invoice for services rendered during the month of January. The total amount due is $2,500.\n\nPayment terms: Net 30 days\nPayment methods: Bank transfer or check\n\nIf you have any questions regarding this invoice, please don't hesitate to contact me.\n\nThank you for your business.\n\nBest regards",
|
||||
},
|
||||
{
|
||||
id: MESSAGE_DATA_SEED_IDS.ID_3,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
receivedAt: new Date(),
|
||||
text: 'Good Evening,\nI wanted to extend my sincere gratitude for taking the time to meet with me earlier today. It was a pleasure discussing with you, and I am excited about the potential opportunities for collaboration. \n Please feel free to reach out if you have any further questions or require additional information. I look forward to our continued communication. Best regards.',
|
||||
subject: 'Thank You for the Meeting',
|
||||
messageThreadId: MESSAGE_THREAD_DATA_SEED_IDS.ID_1,
|
||||
headerMessageId: '3939d68a-ac6b-4f86-87a2-5f5f9d1b6481',
|
||||
text: 'Good evening,\n\nI wanted to extend my sincere gratitude for taking the time to meet with me earlier today. It was a pleasure discussing our potential collaboration.\n\nI am excited about the opportunities we discussed and look forward to moving forward with our partnership.\n\nPlease feel free to reach out if you have any further questions or require additional information.\n\nBest regards',
|
||||
},
|
||||
{
|
||||
subject: 'Proposal Submission',
|
||||
text: 'Dear Team,\n\nI am pleased to submit our proposal for your consideration. We have carefully reviewed your requirements and believe we can deliver exceptional results.\n\nOur proposal includes:\n- Detailed project timeline\n- Resource allocation plan\n- Budget breakdown\n- Risk mitigation strategies\n\nWe would welcome the opportunity to discuss this proposal in detail.\n\nThank you for considering our services.\n\nBest regards',
|
||||
},
|
||||
{
|
||||
subject: 'Follow-up on Discussion',
|
||||
text: "Hi,\n\nI wanted to follow up on our conversation from last week regarding the new initiative. I've had some time to think about the points we discussed.\n\nI believe we should proceed with the plan we outlined, but I would like to suggest a few modifications to improve efficiency.\n\nCould we schedule a brief call to discuss these changes? I'm available most afternoons this week.\n\nThanks for your time.\n\nBest regards",
|
||||
},
|
||||
{
|
||||
subject: 'Customer Feedback',
|
||||
text: "Hello,\n\nI hope you're doing well. I wanted to share some positive feedback we received from our recent customer satisfaction survey.\n\nCustomers particularly appreciated:\n- Quick response times\n- Professional service delivery\n- Attention to detail\n- Clear communication\n\nThis reflects well on our team's dedication to excellence.\n\nKeep up the great work!\n\nBest regards",
|
||||
},
|
||||
{
|
||||
subject: 'Training Session Reminder',
|
||||
text: 'Dear Team,\n\nThis is a friendly reminder about the upcoming training session scheduled for [Date] at [Time].\n\nSession details:\n- Topic: [Training Topic]\n- Duration: 2 hours\n- Location: Conference Room A\n- Materials: Will be provided\n\nPlease confirm your attendance by replying to this email.\n\nLooking forward to seeing everyone there.\n\nBest regards',
|
||||
},
|
||||
{
|
||||
subject: 'Contract Renewal',
|
||||
text: 'Dear [Name],\n\nI hope this email finds you well. Your current contract with us is set to expire on [Date], and we would like to discuss renewal options.\n\nWe value our partnership and would be pleased to continue our collaboration. We have prepared several renewal packages that offer enhanced services and competitive pricing.\n\nWould you be available for a call next week to discuss the details?\n\nBest regards',
|
||||
},
|
||||
{
|
||||
subject: 'Quarterly Report',
|
||||
text: 'Good morning,\n\nPlease find attached our quarterly report covering the period from [Start Date] to [End Date].\n\nKey highlights:\n- Revenue growth of 15%\n- Customer satisfaction score of 4.8/5\n- Successful completion of 95% of projects\n- Team expansion by 20%\n\nWe are pleased with these results and look forward to continued success.\n\nIf you have any questions about the report, please let me know.\n\nBest regards',
|
||||
},
|
||||
{
|
||||
subject: 'Partnership Opportunity',
|
||||
text: 'Hello,\n\nI am reaching out to explore a potential partnership opportunity between our organizations. We believe there are synergies that could benefit both parties.\n\nOur company specializes in [Area of Expertise] and we have identified opportunities where our services could complement your offerings.\n\nWould you be interested in discussing this further? I would be happy to schedule a call at your convenience.\n\nBest regards',
|
||||
},
|
||||
{
|
||||
subject: 'Event Invitation',
|
||||
text: "Dear [Name],\n\nYou are cordially invited to attend our upcoming event:\n\nEvent: [Event Name]\nDate: [Date]\nTime: [Time]\nLocation: [Venue]\n\nThis event will feature industry experts discussing the latest trends and innovations in our field. It's an excellent opportunity for networking and learning.\n\nPlease RSVP by [RSVP Date] to secure your spot.\n\nWe look forward to seeing you there.\n\nBest regards",
|
||||
},
|
||||
];
|
||||
|
||||
const GENERATE_MESSAGE_SEEDS = (): MessageDataSeed[] => {
|
||||
const MESSAGE_SEEDS: MessageDataSeed[] = [];
|
||||
|
||||
const THREAD_IDS = Object.keys(MESSAGE_THREAD_DATA_SEED_IDS).map(
|
||||
(key) =>
|
||||
MESSAGE_THREAD_DATA_SEED_IDS[
|
||||
key as keyof typeof MESSAGE_THREAD_DATA_SEED_IDS
|
||||
],
|
||||
);
|
||||
|
||||
for (let INDEX = 1; INDEX <= 600; INDEX++) {
|
||||
const TEMPLATE_INDEX = (INDEX - 1) % EMAIL_TEMPLATES.length;
|
||||
const TEMPLATE = EMAIL_TEMPLATES[TEMPLATE_INDEX];
|
||||
|
||||
// Assign messages to threads (some threads will have multiple messages)
|
||||
const THREAD_INDEX = Math.floor((INDEX - 1) / 2); // 2 messages per thread on average
|
||||
const THREAD_ID = THREAD_IDS[THREAD_INDEX % THREAD_IDS.length];
|
||||
|
||||
const NOW = new Date();
|
||||
const RANDOM_DAYS_OFFSET = Math.floor(Math.random() * 90);
|
||||
const MESSAGE_DATE = new Date(
|
||||
NOW.getTime() - RANDOM_DAYS_OFFSET * 24 * 60 * 60 * 1000,
|
||||
);
|
||||
|
||||
MESSAGE_DATE.setHours(
|
||||
8 + Math.floor(Math.random() * 10), // 8 AM to 6 PM
|
||||
Math.floor(Math.random() * 60), // Random minutes
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
MESSAGE_SEEDS.push({
|
||||
id: MESSAGE_DATA_SEED_IDS[`ID_${INDEX}`],
|
||||
createdAt: MESSAGE_DATE,
|
||||
updatedAt: MESSAGE_DATE,
|
||||
deletedAt: null,
|
||||
receivedAt: MESSAGE_DATE,
|
||||
text: TEMPLATE.text,
|
||||
subject: TEMPLATE.subject,
|
||||
messageThreadId: THREAD_ID,
|
||||
headerMessageId: `${INDEX.toString(16).padStart(8, '0')}-${MESSAGE_DATE.getTime().toString(16)}`,
|
||||
});
|
||||
}
|
||||
|
||||
return MESSAGE_SEEDS;
|
||||
};
|
||||
|
||||
export const MESSAGE_DATA_SEEDS = GENERATE_MESSAGE_SEEDS();
|
||||
|
||||
@ -29,86 +29,263 @@ export const MESSAGE_PARTICIPANT_DATA_SEED_COLUMNS: (keyof MessageParticipantDat
|
||||
'messageId',
|
||||
];
|
||||
|
||||
export const MESSAGE_PARTICIPANT_DATA_SEED_IDS = {
|
||||
ID_1: '20202020-0f2a-49d8-8aa2-ec8786153a0b',
|
||||
ID_2: '20202020-4e83-41ec-93e2-fd70ff09f68c',
|
||||
ID_3: '20202020-e716-4dd5-ac61-3315bc559e2d',
|
||||
ID_4: '20202020-fc7d-4ad8-9aea-b78bcbf79cdd',
|
||||
ID_5: '20202020-564c-4a3c-abbf-e942e8c3f9c9',
|
||||
ID_6: '20202020-7e4a-489a-ba6b-1ae6b7d721ac',
|
||||
const GENERATE_MESSAGE_PARTICIPANT_IDS = (): Record<string, string> => {
|
||||
const PARTICIPANT_IDS: Record<string, string> = {};
|
||||
|
||||
for (let INDEX = 1; INDEX <= 1500; INDEX++) {
|
||||
const HEX_INDEX = INDEX.toString(16).padStart(4, '0');
|
||||
|
||||
PARTICIPANT_IDS[`ID_${INDEX}`] =
|
||||
`20202020-${HEX_INDEX}-4e7c-8001-123456789cde`;
|
||||
}
|
||||
|
||||
return PARTICIPANT_IDS;
|
||||
};
|
||||
|
||||
export const MESSAGE_PARTICIPANT_DATA_SEEDS: MessageParticipantDataSeed[] = [
|
||||
{
|
||||
id: MESSAGE_PARTICIPANT_DATA_SEED_IDS.ID_1,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
workspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
|
||||
personId: PERSON_DATA_SEED_IDS.ID_1,
|
||||
displayName: 'Christoph',
|
||||
handle: 'outgoing',
|
||||
role: 'from',
|
||||
messageId: MESSAGE_DATA_SEED_IDS.ID_1,
|
||||
},
|
||||
{
|
||||
id: MESSAGE_PARTICIPANT_DATA_SEED_IDS.ID_2,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
workspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
|
||||
personId: PERSON_DATA_SEED_IDS.ID_2,
|
||||
displayName: 'Sylvie',
|
||||
handle: 'incoming',
|
||||
role: 'to',
|
||||
messageId: MESSAGE_DATA_SEED_IDS.ID_1,
|
||||
},
|
||||
{
|
||||
id: MESSAGE_PARTICIPANT_DATA_SEED_IDS.ID_3,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
workspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
|
||||
personId: PERSON_DATA_SEED_IDS.ID_3,
|
||||
displayName: 'Christopher',
|
||||
handle: 'incoming',
|
||||
role: 'to',
|
||||
messageId: MESSAGE_DATA_SEED_IDS.ID_1,
|
||||
},
|
||||
{
|
||||
id: MESSAGE_PARTICIPANT_DATA_SEED_IDS.ID_4,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
workspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
|
||||
personId: PERSON_DATA_SEED_IDS.ID_1,
|
||||
displayName: 'Christoph',
|
||||
handle: 'outgoing',
|
||||
role: 'from',
|
||||
messageId: MESSAGE_DATA_SEED_IDS.ID_2,
|
||||
},
|
||||
{
|
||||
id: MESSAGE_PARTICIPANT_DATA_SEED_IDS.ID_5,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
workspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
|
||||
personId: PERSON_DATA_SEED_IDS.ID_2,
|
||||
displayName: 'Sylvie',
|
||||
handle: 'incoming',
|
||||
role: 'to',
|
||||
messageId: MESSAGE_DATA_SEED_IDS.ID_2,
|
||||
},
|
||||
{
|
||||
id: MESSAGE_PARTICIPANT_DATA_SEED_IDS.ID_6,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
workspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
|
||||
personId: PERSON_DATA_SEED_IDS.ID_3,
|
||||
displayName: 'Christopher',
|
||||
handle: 'incoming',
|
||||
role: 'to',
|
||||
messageId: MESSAGE_DATA_SEED_IDS.ID_2,
|
||||
},
|
||||
export const MESSAGE_PARTICIPANT_DATA_SEED_IDS =
|
||||
GENERATE_MESSAGE_PARTICIPANT_IDS();
|
||||
|
||||
const FAKE_EMAIL_PARTICIPANTS = [
|
||||
{ name: 'Alex Johnson', email: 'alex.johnson@company.com' },
|
||||
{ name: 'Sarah Williams', email: 'sarah.williams@company.com' },
|
||||
{ name: 'Michael Chen', email: 'michael.chen@company.com' },
|
||||
{ name: 'Emily Davis', email: 'emily.davis@company.com' },
|
||||
{ name: 'David Rodriguez', email: 'david.rodriguez@company.com' },
|
||||
{ name: 'Lisa Anderson', email: 'lisa.anderson@company.com' },
|
||||
{ name: 'James Wilson', email: 'james.wilson@company.com' },
|
||||
{ name: 'Jennifer Martinez', email: 'jennifer.martinez@company.com' },
|
||||
{ name: 'Robert Taylor', email: 'robert.taylor@company.com' },
|
||||
{ name: 'Maria Garcia', email: 'maria.garcia@company.com' },
|
||||
{ name: 'John Smith', email: 'john.smith@external.com' },
|
||||
{ name: 'Emma Johnson', email: 'emma.johnson@external.com' },
|
||||
{ name: 'Oliver Brown', email: 'oliver.brown@external.com' },
|
||||
{ name: 'Sophia Davis', email: 'sophia.davis@external.com' },
|
||||
{ name: 'William Miller', email: 'william.miller@external.com' },
|
||||
];
|
||||
|
||||
type ParticipantData = {
|
||||
workspaceMemberId: string;
|
||||
personId: string;
|
||||
displayName: string;
|
||||
};
|
||||
|
||||
const GET_RANDOM_FAKE_PARTICIPANT = () => {
|
||||
return FAKE_EMAIL_PARTICIPANTS[
|
||||
Math.floor(Math.random() * FAKE_EMAIL_PARTICIPANTS.length)
|
||||
];
|
||||
};
|
||||
|
||||
const FIND_UNUSED_PERSON_ID = (
|
||||
personIds: string[],
|
||||
usedPersonIds: Set<string>,
|
||||
): string | null => {
|
||||
const AVAILABLE_IDS = personIds.filter((id) => !usedPersonIds.has(id));
|
||||
|
||||
if (AVAILABLE_IDS.length === 0) return null;
|
||||
|
||||
return AVAILABLE_IDS[Math.floor(Math.random() * AVAILABLE_IDS.length)];
|
||||
};
|
||||
|
||||
const FIND_UNUSED_WORKSPACE_MEMBER_ID = (
|
||||
workspaceMemberIds: string[],
|
||||
usedWorkspaceMemberIds: Set<string>,
|
||||
): string | null => {
|
||||
const AVAILABLE_IDS = workspaceMemberIds.filter(
|
||||
(id) => !usedWorkspaceMemberIds.has(id),
|
||||
);
|
||||
|
||||
if (AVAILABLE_IDS.length === 0) return null;
|
||||
|
||||
return AVAILABLE_IDS[Math.floor(Math.random() * AVAILABLE_IDS.length)];
|
||||
};
|
||||
|
||||
const CREATE_PERSON_PARTICIPANT = (
|
||||
personIds: string[],
|
||||
usedPersonIds: Set<string>,
|
||||
defaultWorkspaceMemberId: string,
|
||||
): ParticipantData | null => {
|
||||
const PERSON_ID = FIND_UNUSED_PERSON_ID(personIds, usedPersonIds);
|
||||
|
||||
if (!PERSON_ID) return null;
|
||||
|
||||
usedPersonIds.add(PERSON_ID);
|
||||
const PERSON_INDEX = personIds.indexOf(PERSON_ID) + 1;
|
||||
|
||||
return {
|
||||
workspaceMemberId: defaultWorkspaceMemberId,
|
||||
personId: PERSON_ID,
|
||||
displayName: `Person ${PERSON_INDEX}`,
|
||||
};
|
||||
};
|
||||
|
||||
const CREATE_WORKSPACE_MEMBER_PARTICIPANT = (
|
||||
workspaceMemberIds: string[],
|
||||
personIds: string[],
|
||||
usedWorkspaceMemberIds: Set<string>,
|
||||
): ParticipantData | null => {
|
||||
const WORKSPACE_MEMBER_ID = FIND_UNUSED_WORKSPACE_MEMBER_ID(
|
||||
workspaceMemberIds,
|
||||
usedWorkspaceMemberIds,
|
||||
);
|
||||
|
||||
if (!WORKSPACE_MEMBER_ID) return null;
|
||||
|
||||
usedWorkspaceMemberIds.add(WORKSPACE_MEMBER_ID);
|
||||
|
||||
switch (WORKSPACE_MEMBER_ID) {
|
||||
case WORKSPACE_MEMBER_DATA_SEED_IDS.TIM:
|
||||
return {
|
||||
workspaceMemberId: WORKSPACE_MEMBER_ID,
|
||||
personId: personIds[0] || personIds[0],
|
||||
displayName: 'Tim Apple',
|
||||
};
|
||||
case WORKSPACE_MEMBER_DATA_SEED_IDS.JONY:
|
||||
return {
|
||||
workspaceMemberId: WORKSPACE_MEMBER_ID,
|
||||
personId: personIds[1] || personIds[0],
|
||||
displayName: 'Jony Ive',
|
||||
};
|
||||
case WORKSPACE_MEMBER_DATA_SEED_IDS.PHIL:
|
||||
return {
|
||||
workspaceMemberId: WORKSPACE_MEMBER_ID,
|
||||
personId: personIds[2] || personIds[0],
|
||||
displayName: 'Phil Schiller',
|
||||
};
|
||||
default:
|
||||
return {
|
||||
workspaceMemberId: WORKSPACE_MEMBER_ID,
|
||||
personId: personIds[0] || personIds[0],
|
||||
displayName: 'Workspace Member',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const CREATE_FAKE_PARTICIPANT = (
|
||||
workspaceMemberIds: string[],
|
||||
personIds: string[],
|
||||
): ParticipantData => {
|
||||
const FAKE = GET_RANDOM_FAKE_PARTICIPANT();
|
||||
|
||||
return {
|
||||
workspaceMemberId: workspaceMemberIds[0],
|
||||
personId:
|
||||
personIds[Math.floor(Math.random() * Math.min(10, personIds.length))],
|
||||
displayName: FAKE.name,
|
||||
};
|
||||
};
|
||||
|
||||
const CREATE_PARTICIPANT_DATA = (
|
||||
personIds: string[],
|
||||
workspaceMemberIds: string[],
|
||||
usedPersonIds: Set<string>,
|
||||
usedWorkspaceMemberIds: Set<string>,
|
||||
): ParticipantData => {
|
||||
const PARTICIPANT_TYPE = Math.random();
|
||||
|
||||
// Try person participant (40% chance)
|
||||
if (PARTICIPANT_TYPE < 0.4) {
|
||||
const PERSON_PARTICIPANT = CREATE_PERSON_PARTICIPANT(
|
||||
personIds,
|
||||
usedPersonIds,
|
||||
workspaceMemberIds[0],
|
||||
);
|
||||
|
||||
if (PERSON_PARTICIPANT) return PERSON_PARTICIPANT;
|
||||
}
|
||||
|
||||
// Try workspace member participant (30% chance, 0.4-0.7 range)
|
||||
if (PARTICIPANT_TYPE >= 0.4 && PARTICIPANT_TYPE < 0.7) {
|
||||
const WORKSPACE_PARTICIPANT = CREATE_WORKSPACE_MEMBER_PARTICIPANT(
|
||||
workspaceMemberIds,
|
||||
personIds,
|
||||
usedWorkspaceMemberIds,
|
||||
);
|
||||
|
||||
if (WORKSPACE_PARTICIPANT) return WORKSPACE_PARTICIPANT;
|
||||
}
|
||||
|
||||
// Fallback to fake participant
|
||||
return CREATE_FAKE_PARTICIPANT(workspaceMemberIds, personIds);
|
||||
};
|
||||
|
||||
const CREATE_MESSAGE_PARTICIPANTS = (
|
||||
messageId: string,
|
||||
personIds: string[],
|
||||
workspaceMemberIds: string[],
|
||||
participantIndex: number,
|
||||
): { participants: MessageParticipantDataSeed[]; nextIndex: number } => {
|
||||
const PARTICIPANTS: MessageParticipantDataSeed[] = [];
|
||||
const RECIPIENT_COUNT = 1 + Math.floor(Math.random() * 3); // 1-3 recipients
|
||||
const TOTAL_PARTICIPANTS = 1 + RECIPIENT_COUNT; // sender + recipients
|
||||
|
||||
const USED_PERSON_IDS = new Set<string>();
|
||||
const USED_WORKSPACE_MEMBER_IDS = new Set<string>();
|
||||
|
||||
for (let I = 0; I < TOTAL_PARTICIPANTS; I++) {
|
||||
const IS_SENDER = I === 0;
|
||||
const ROLE = IS_SENDER ? 'from' : 'to';
|
||||
const HANDLE = IS_SENDER ? 'outgoing' : 'incoming';
|
||||
|
||||
// Random date within the last 3 months
|
||||
const NOW = new Date();
|
||||
const RANDOM_DAYS_OFFSET = Math.floor(Math.random() * 90);
|
||||
const PARTICIPANT_DATE = new Date(
|
||||
NOW.getTime() - RANDOM_DAYS_OFFSET * 24 * 60 * 60 * 1000,
|
||||
);
|
||||
|
||||
const PARTICIPANT_DATA = CREATE_PARTICIPANT_DATA(
|
||||
personIds,
|
||||
workspaceMemberIds,
|
||||
USED_PERSON_IDS,
|
||||
USED_WORKSPACE_MEMBER_IDS,
|
||||
);
|
||||
|
||||
PARTICIPANTS.push({
|
||||
id: MESSAGE_PARTICIPANT_DATA_SEED_IDS[`ID_${participantIndex}`],
|
||||
createdAt: PARTICIPANT_DATE,
|
||||
updatedAt: PARTICIPANT_DATE,
|
||||
deletedAt: null,
|
||||
workspaceMemberId: PARTICIPANT_DATA.workspaceMemberId,
|
||||
personId: PARTICIPANT_DATA.personId,
|
||||
displayName: PARTICIPANT_DATA.displayName,
|
||||
handle: HANDLE,
|
||||
role: ROLE,
|
||||
messageId,
|
||||
});
|
||||
|
||||
participantIndex++;
|
||||
}
|
||||
|
||||
return { participants: PARTICIPANTS, nextIndex: participantIndex };
|
||||
};
|
||||
|
||||
const GENERATE_MESSAGE_PARTICIPANT_SEEDS = (): MessageParticipantDataSeed[] => {
|
||||
const PARTICIPANT_SEEDS: MessageParticipantDataSeed[] = [];
|
||||
let PARTICIPANT_INDEX = 1;
|
||||
|
||||
const MESSAGE_IDS = Object.keys(MESSAGE_DATA_SEED_IDS).map(
|
||||
(key) => MESSAGE_DATA_SEED_IDS[key as keyof typeof MESSAGE_DATA_SEED_IDS],
|
||||
);
|
||||
|
||||
const PERSON_IDS = Object.keys(PERSON_DATA_SEED_IDS).map(
|
||||
(key) => PERSON_DATA_SEED_IDS[key as keyof typeof PERSON_DATA_SEED_IDS],
|
||||
);
|
||||
const WORKSPACE_MEMBER_IDS = Object.values(WORKSPACE_MEMBER_DATA_SEED_IDS);
|
||||
|
||||
for (const MESSAGE_ID of MESSAGE_IDS) {
|
||||
const RESULT = CREATE_MESSAGE_PARTICIPANTS(
|
||||
MESSAGE_ID,
|
||||
PERSON_IDS,
|
||||
WORKSPACE_MEMBER_IDS,
|
||||
PARTICIPANT_INDEX,
|
||||
);
|
||||
|
||||
PARTICIPANT_SEEDS.push(...RESULT.participants);
|
||||
PARTICIPANT_INDEX = RESULT.nextIndex;
|
||||
}
|
||||
|
||||
return PARTICIPANT_SEEDS;
|
||||
};
|
||||
|
||||
export const MESSAGE_PARTICIPANT_DATA_SEEDS =
|
||||
GENERATE_MESSAGE_PARTICIPANT_SEEDS();
|
||||
|
||||
@ -8,37 +8,48 @@ type MessageThreadDataSeed = {
|
||||
export const MESSAGE_THREAD_DATA_SEED_COLUMNS: (keyof MessageThreadDataSeed)[] =
|
||||
['id', 'createdAt', 'updatedAt', 'deletedAt'];
|
||||
|
||||
export const MESSAGE_THREAD_DATA_SEED_IDS = {
|
||||
ID_1: '20202020-8bfa-453b-b99b-bc435a7d4da8',
|
||||
ID_2: '20202020-634a-4fde-aa7c-28a0eaf203ca',
|
||||
ID_3: '20202020-1b56-4f10-a2fa-2ccaddf81f6c',
|
||||
ID_4: '20202020-d51c-485a-b1b6-ed7c63e05d72',
|
||||
ID_5: '20202020-3f74-492d-a101-2a70f50a1645',
|
||||
const GENERATE_MESSAGE_THREAD_IDS = (): Record<string, string> => {
|
||||
const THREAD_IDS: Record<string, string> = {};
|
||||
|
||||
for (let INDEX = 1; INDEX <= 300; INDEX++) {
|
||||
const HEX_INDEX = INDEX.toString(16).padStart(4, '0');
|
||||
|
||||
THREAD_IDS[`ID_${INDEX}`] = `20202020-${HEX_INDEX}-4e7c-8001-123456789def`;
|
||||
}
|
||||
|
||||
return THREAD_IDS;
|
||||
};
|
||||
|
||||
export const MESSAGE_THREAD_DATA_SEEDS: MessageThreadDataSeed[] = [
|
||||
{
|
||||
id: MESSAGE_THREAD_DATA_SEED_IDS.ID_1,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
id: MESSAGE_THREAD_DATA_SEED_IDS.ID_2,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
id: MESSAGE_THREAD_DATA_SEED_IDS.ID_3,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
id: MESSAGE_THREAD_DATA_SEED_IDS.ID_4,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
},
|
||||
];
|
||||
export const MESSAGE_THREAD_DATA_SEED_IDS = GENERATE_MESSAGE_THREAD_IDS();
|
||||
|
||||
const GENERATE_MESSAGE_THREAD_SEEDS = (): MessageThreadDataSeed[] => {
|
||||
const THREAD_SEEDS: MessageThreadDataSeed[] = [];
|
||||
|
||||
for (let INDEX = 1; INDEX <= 300; INDEX++) {
|
||||
const NOW = new Date();
|
||||
const RANDOM_DAYS_OFFSET = Math.floor(Math.random() * 90); // 0 to 90 days ago
|
||||
const CREATED_DATE = new Date(
|
||||
NOW.getTime() - RANDOM_DAYS_OFFSET * 24 * 60 * 60 * 1000,
|
||||
);
|
||||
|
||||
// Updated date is between created date and now
|
||||
const DAYS_SINCE_CREATED = Math.floor(
|
||||
(NOW.getTime() - CREATED_DATE.getTime()) / (24 * 60 * 60 * 1000),
|
||||
);
|
||||
const UPDATE_OFFSET = Math.floor(Math.random() * (DAYS_SINCE_CREATED + 1));
|
||||
const UPDATED_DATE = new Date(
|
||||
CREATED_DATE.getTime() + UPDATE_OFFSET * 24 * 60 * 60 * 1000,
|
||||
);
|
||||
|
||||
THREAD_SEEDS.push({
|
||||
id: MESSAGE_THREAD_DATA_SEED_IDS[`ID_${INDEX}`],
|
||||
createdAt: CREATED_DATE,
|
||||
updatedAt: UPDATED_DATE,
|
||||
deletedAt: null,
|
||||
});
|
||||
}
|
||||
|
||||
return THREAD_SEEDS;
|
||||
};
|
||||
|
||||
export const MESSAGE_THREAD_DATA_SEEDS = GENERATE_MESSAGE_THREAD_SEEDS();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||
import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite';
|
||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||
import {
|
||||
@ -244,6 +245,16 @@ export class DevSeederDataService {
|
||||
await mainDataSource.transaction(
|
||||
async (entityManager: WorkspaceEntityManager) => {
|
||||
for (const recordSeedsConfig of RECORD_SEEDS_CONFIGS) {
|
||||
const objectMetadata = objectMetadataItems.find(
|
||||
(item) =>
|
||||
computeTableName(item.nameSingular, item.isCustom) ===
|
||||
recordSeedsConfig.tableName,
|
||||
);
|
||||
|
||||
if (!objectMetadata) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await this.seedRecords({
|
||||
entityManager,
|
||||
schemaName,
|
||||
@ -259,10 +270,14 @@ export class DevSeederDataService {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
// For now views/favorites are auto-created for custom
|
||||
// objects but not for standard objects.
|
||||
// This is probably something we want to fix in the future.
|
||||
|
||||
const viewDefinitionsWithId = await prefillViews(
|
||||
entityManager,
|
||||
schemaName,
|
||||
objectMetadataItems,
|
||||
objectMetadataItems.filter((item) => !item.isCustom),
|
||||
);
|
||||
|
||||
await prefillWorkspaceFavorites(
|
||||
|
||||
@ -4,7 +4,11 @@ import chunk from 'lodash.chunk';
|
||||
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { CALENDAR_EVENT_DATA_SEEDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/calendar-event-data-seeds.constant';
|
||||
import { CALENDAR_EVENT_PARTICIPANT_DATA_SEEDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/calendar-event-participant-data-seeds.constant';
|
||||
import { COMPANY_DATA_SEEDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/company-data-seeds.constant';
|
||||
import { MESSAGE_DATA_SEEDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/message-data-seeds.constant';
|
||||
import { MESSAGE_PARTICIPANT_DATA_SEEDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/message-participant-data-seeds.constant';
|
||||
import { NOTE_DATA_SEEDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/note-data-seeds.constant';
|
||||
import { NOTE_TARGET_DATA_SEEDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/note-target-data-seeds.constant';
|
||||
import { OPPORTUNITY_DATA_SEEDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/opportunity-data-seeds.constant';
|
||||
@ -14,7 +18,6 @@ import { TASK_TARGET_DATA_SEEDS } from 'src/engine/workspace-manager/dev-seeder/
|
||||
import { WORKSPACE_MEMBER_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/workspace-member-data-seeds.constant';
|
||||
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
||||
|
||||
// Seeding-specific type based on TimelineActivityWorkspaceEntity for raw data insertion
|
||||
type TimelineActivitySeedData = Pick<
|
||||
TimelineActivityWorkspaceEntity,
|
||||
| 'id'
|
||||
@ -47,7 +50,7 @@ type CreateTimelineActivityParams = {
|
||||
};
|
||||
|
||||
type CreateLinkedActivityParams = {
|
||||
activityType: 'note' | 'task';
|
||||
activityType: 'note' | 'task' | 'calendarEvent' | 'message';
|
||||
recordSeed: Record<string, unknown>;
|
||||
index: number;
|
||||
activityIndex: number;
|
||||
@ -55,10 +58,55 @@ type CreateLinkedActivityParams = {
|
||||
targetInfo: ActivityTargetInfo;
|
||||
};
|
||||
|
||||
type EntityConfig = {
|
||||
type: string;
|
||||
seeds: Array<Record<string, unknown>>;
|
||||
};
|
||||
|
||||
type ObjectMetadataIds = {
|
||||
noteMetadataId: string;
|
||||
taskMetadataId: string;
|
||||
calendarEventMetadataId: string;
|
||||
messageMetadataId: string;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class TimelineActivitySeederService {
|
||||
private readonly ENTITY_CODES = {
|
||||
company: '0001',
|
||||
person: '0201',
|
||||
note: '0601',
|
||||
task: '0651',
|
||||
opportunity: '0851',
|
||||
calendarEvent: '0701',
|
||||
message: '0751',
|
||||
} as const;
|
||||
|
||||
private readonly TARGET_CODES = {
|
||||
person: '1001',
|
||||
company: '2001',
|
||||
opportunity: '3001',
|
||||
} as const;
|
||||
|
||||
private readonly LINKABLE_TYPES = new Set([
|
||||
'note',
|
||||
'task',
|
||||
'calendarEvent',
|
||||
'message',
|
||||
]);
|
||||
|
||||
constructor(private readonly objectMetadataService: ObjectMetadataService) {}
|
||||
|
||||
private getLinkedActivityName(activityType: string): string {
|
||||
// Notes and tasks use the legacy format: linked-{type}.created
|
||||
if (activityType === 'note' || activityType === 'task') {
|
||||
return `linked-${activityType}.created`;
|
||||
}
|
||||
|
||||
// Calendar events and messages use the new format: {type}.linked
|
||||
return `${activityType}.linked`;
|
||||
}
|
||||
|
||||
async seedTimelineActivities({
|
||||
entityManager,
|
||||
schemaName,
|
||||
@ -69,13 +117,10 @@ export class TimelineActivitySeederService {
|
||||
workspaceId: string;
|
||||
}) {
|
||||
const timelineActivities: TimelineActivitySeedData[] = [];
|
||||
|
||||
const { noteMetadataId, taskMetadataId } =
|
||||
await this.getObjectMetadataIds(workspaceId);
|
||||
|
||||
const metadataIds = await this.getObjectMetadataIds(workspaceId);
|
||||
let activityIndex = 0;
|
||||
|
||||
const entityConfigs = [
|
||||
const entityConfigs: EntityConfig[] = [
|
||||
{ type: 'company', seeds: COMPANY_DATA_SEEDS },
|
||||
{ type: 'person', seeds: PERSON_DATA_SEEDS },
|
||||
{ type: 'note', seeds: NOTE_DATA_SEEDS },
|
||||
@ -83,6 +128,12 @@ export class TimelineActivitySeederService {
|
||||
{ type: 'opportunity', seeds: OPPORTUNITY_DATA_SEEDS },
|
||||
];
|
||||
|
||||
// Calendar events and messages only appear as linked activities
|
||||
const linkableConfigs: EntityConfig[] = [
|
||||
{ type: 'calendarEvent', seeds: CALENDAR_EVENT_DATA_SEEDS },
|
||||
{ type: 'message', seeds: MESSAGE_DATA_SEEDS },
|
||||
];
|
||||
|
||||
entityConfigs.forEach(({ type, seeds }) => {
|
||||
seeds.forEach((seed, index) => {
|
||||
const activity = this.createTimelineActivity({
|
||||
@ -94,55 +145,106 @@ export class TimelineActivitySeederService {
|
||||
timelineActivities.push(activity);
|
||||
activityIndex++;
|
||||
|
||||
// For notes and tasks, create additional linked timeline activities on target objects
|
||||
if (type === 'note' || type === 'task') {
|
||||
const linkedObjectMetadataId =
|
||||
type === 'note' ? noteMetadataId : taskMetadataId;
|
||||
const linkedActivities = this.computeLinkedTimelineActivityRecords({
|
||||
activityType: type,
|
||||
recordSeed: seed,
|
||||
index,
|
||||
activityIndex,
|
||||
linkedObjectMetadataId,
|
||||
});
|
||||
|
||||
timelineActivities.push(...linkedActivities);
|
||||
activityIndex += linkedActivities.length;
|
||||
if (!this.LINKABLE_TYPES.has(type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const linkedObjectMetadataId = this.getLinkedObjectMetadataId(
|
||||
type,
|
||||
metadataIds,
|
||||
);
|
||||
const linkedActivities = this.computeLinkedTimelineActivityRecords({
|
||||
activityType: type as 'note' | 'task' | 'calendarEvent' | 'message',
|
||||
recordSeed: seed,
|
||||
index,
|
||||
activityIndex,
|
||||
linkedObjectMetadataId,
|
||||
});
|
||||
|
||||
timelineActivities.push(...linkedActivities);
|
||||
activityIndex += linkedActivities.length;
|
||||
});
|
||||
});
|
||||
|
||||
if (timelineActivities.length > 0) {
|
||||
const batchSize = 100;
|
||||
const timelineActivityBatches = chunk(timelineActivities, batchSize);
|
||||
// Create only linked activities for calendar events and messages
|
||||
linkableConfigs.forEach(({ type, seeds }) => {
|
||||
seeds.forEach((seed, index) => {
|
||||
const linkedObjectMetadataId = this.getLinkedObjectMetadataId(
|
||||
type,
|
||||
metadataIds,
|
||||
);
|
||||
const linkedActivities = this.computeLinkedTimelineActivityRecords({
|
||||
activityType: type as 'calendarEvent' | 'message',
|
||||
recordSeed: seed,
|
||||
index,
|
||||
activityIndex,
|
||||
linkedObjectMetadataId,
|
||||
});
|
||||
|
||||
for (const batch of timelineActivityBatches) {
|
||||
await entityManager
|
||||
.createQueryBuilder(undefined, undefined, undefined, {
|
||||
shouldBypassPermissionChecks: true,
|
||||
})
|
||||
.insert()
|
||||
.into(`${schemaName}.timelineActivity`, [
|
||||
'id',
|
||||
'name',
|
||||
'properties',
|
||||
'linkedRecordCachedName',
|
||||
'linkedRecordId',
|
||||
'linkedObjectMetadataId',
|
||||
'workspaceMemberId',
|
||||
'companyId',
|
||||
'personId',
|
||||
'noteId',
|
||||
'taskId',
|
||||
'opportunityId',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'happensAt',
|
||||
])
|
||||
.orIgnore()
|
||||
.values(batch)
|
||||
.execute();
|
||||
}
|
||||
timelineActivities.push(...linkedActivities);
|
||||
activityIndex += linkedActivities.length;
|
||||
});
|
||||
});
|
||||
|
||||
await this.insertTimelineActivities(
|
||||
entityManager,
|
||||
schemaName,
|
||||
timelineActivities,
|
||||
);
|
||||
}
|
||||
|
||||
private getLinkedObjectMetadataId(
|
||||
type: string,
|
||||
metadataIds: ObjectMetadataIds,
|
||||
): string {
|
||||
const metadataMap = {
|
||||
note: metadataIds.noteMetadataId,
|
||||
task: metadataIds.taskMetadataId,
|
||||
calendarEvent: metadataIds.calendarEventMetadataId,
|
||||
message: metadataIds.messageMetadataId,
|
||||
};
|
||||
|
||||
return metadataMap[type as keyof typeof metadataMap] || '';
|
||||
}
|
||||
|
||||
private async insertTimelineActivities(
|
||||
entityManager: WorkspaceEntityManager,
|
||||
schemaName: string,
|
||||
timelineActivities: TimelineActivitySeedData[],
|
||||
) {
|
||||
if (timelineActivities.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const batchSize = 100;
|
||||
const timelineActivityBatches = chunk(timelineActivities, batchSize);
|
||||
|
||||
for (const batch of timelineActivityBatches) {
|
||||
await entityManager
|
||||
.createQueryBuilder(undefined, undefined, undefined, {
|
||||
shouldBypassPermissionChecks: true,
|
||||
})
|
||||
.insert()
|
||||
.into(`${schemaName}.timelineActivity`, [
|
||||
'id',
|
||||
'name',
|
||||
'properties',
|
||||
'linkedRecordCachedName',
|
||||
'linkedRecordId',
|
||||
'linkedObjectMetadataId',
|
||||
'workspaceMemberId',
|
||||
'companyId',
|
||||
'personId',
|
||||
'noteId',
|
||||
'taskId',
|
||||
'opportunityId',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'happensAt',
|
||||
])
|
||||
.orIgnore()
|
||||
.values(batch)
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,34 +253,21 @@ export class TimelineActivitySeederService {
|
||||
recordSeed,
|
||||
index,
|
||||
}: CreateTimelineActivityParams): TimelineActivitySeedData {
|
||||
const generateTimelineActivityId = (type: string, idx: number): string => {
|
||||
const prefix = '20202020';
|
||||
const entityCodes: Record<string, string> = {
|
||||
company: '0001',
|
||||
person: '0201',
|
||||
note: '0601',
|
||||
task: '0651',
|
||||
opportunity: '0851',
|
||||
};
|
||||
const code = entityCodes[type] || '0000';
|
||||
const paddedIndex = String(idx).padStart(4, '0');
|
||||
|
||||
return `${prefix}-${code}-4000-8001-${paddedIndex}00000001`;
|
||||
};
|
||||
|
||||
const timelineActivityId = this.generateTimelineActivityId(
|
||||
entityType,
|
||||
index + 1,
|
||||
);
|
||||
const creationDate = new Date().toISOString();
|
||||
const recordId = String(recordSeed.id || '');
|
||||
|
||||
const timelineActivity: TimelineActivitySeedData = {
|
||||
id: generateTimelineActivityId(entityType, index + 1),
|
||||
id: timelineActivityId,
|
||||
name: `${entityType}.created`,
|
||||
properties: JSON.stringify({
|
||||
after: this.getEventAfterRecordProperties({
|
||||
type: entityType,
|
||||
recordSeed,
|
||||
}),
|
||||
after: this.getEventAfterRecordProperties(entityType, recordSeed),
|
||||
}),
|
||||
linkedRecordCachedName: '',
|
||||
linkedRecordId: recordSeed.id as string,
|
||||
linkedRecordId: recordId,
|
||||
linkedObjectMetadataId: null,
|
||||
workspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
|
||||
companyId: null,
|
||||
@ -191,68 +280,85 @@ export class TimelineActivitySeederService {
|
||||
happensAt: creationDate,
|
||||
};
|
||||
|
||||
// Set the appropriate entity ID
|
||||
const entityIdKey = `${entityType}Id`;
|
||||
|
||||
// @ts-expect-error - This is okay for morph
|
||||
// alternative is to be explicit but that makes
|
||||
timelineActivity[`${entityType}Id`] = recordSeed.id;
|
||||
timelineActivity[entityIdKey] = recordId;
|
||||
|
||||
return timelineActivity;
|
||||
}
|
||||
|
||||
private getEventAfterRecordProperties({
|
||||
type,
|
||||
recordSeed,
|
||||
}: {
|
||||
type: string;
|
||||
recordSeed: Record<string, unknown>;
|
||||
}): Record<string, unknown> {
|
||||
const commonProperties = {
|
||||
id: recordSeed.id,
|
||||
private generateTimelineActivityId(type: string, index: number): string {
|
||||
const prefix = '20202020';
|
||||
const code =
|
||||
this.ENTITY_CODES[type as keyof typeof this.ENTITY_CODES] || '0000';
|
||||
const paddedIndex = String(index).padStart(4, '0');
|
||||
|
||||
return `${prefix}-${code}-4000-8001-${paddedIndex}00000001`;
|
||||
}
|
||||
|
||||
private getEventAfterRecordProperties(
|
||||
type: string,
|
||||
recordSeed: Record<string, unknown>,
|
||||
): Record<string, unknown> {
|
||||
const commonProperties = { id: recordSeed.id };
|
||||
|
||||
const propertyGetters = {
|
||||
company: () => ({
|
||||
...commonProperties,
|
||||
name: recordSeed.name,
|
||||
domainName: recordSeed.domainNamePrimaryLinkUrl,
|
||||
employees: recordSeed.employees,
|
||||
city: recordSeed.addressAddressCity,
|
||||
}),
|
||||
person: () => ({
|
||||
...commonProperties,
|
||||
name: {
|
||||
firstName: recordSeed.nameFirstName,
|
||||
lastName: recordSeed.nameLastName,
|
||||
},
|
||||
email: recordSeed.emailsPrimaryEmail,
|
||||
jobTitle: recordSeed.jobTitle,
|
||||
}),
|
||||
note: () => ({
|
||||
...commonProperties,
|
||||
title: recordSeed.title,
|
||||
body: recordSeed.body,
|
||||
}),
|
||||
task: () => ({
|
||||
...commonProperties,
|
||||
title: recordSeed.title,
|
||||
body: recordSeed.body,
|
||||
status: recordSeed.status,
|
||||
dueAt: recordSeed.dueAt,
|
||||
}),
|
||||
opportunity: () => ({
|
||||
...commonProperties,
|
||||
name: recordSeed.name,
|
||||
amount: recordSeed.amountAmountMicros,
|
||||
stage: recordSeed.stage,
|
||||
closeDate: recordSeed.closeDate,
|
||||
}),
|
||||
calendarEvent: () => ({
|
||||
...commonProperties,
|
||||
title: recordSeed.title,
|
||||
description: recordSeed.description,
|
||||
startsAt: recordSeed.startsAt,
|
||||
endsAt: recordSeed.endsAt,
|
||||
location: recordSeed.location,
|
||||
}),
|
||||
message: () => ({
|
||||
...commonProperties,
|
||||
subject: recordSeed.subject,
|
||||
text: recordSeed.text,
|
||||
receivedAt: recordSeed.receivedAt,
|
||||
}),
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case 'company':
|
||||
return {
|
||||
...commonProperties,
|
||||
name: recordSeed.name,
|
||||
domainName: recordSeed.domainNamePrimaryLinkUrl,
|
||||
employees: recordSeed.employees,
|
||||
city: recordSeed.addressAddressCity,
|
||||
};
|
||||
case 'person':
|
||||
return {
|
||||
...commonProperties,
|
||||
name: {
|
||||
firstName: recordSeed.nameFirstName,
|
||||
lastName: recordSeed.nameLastName,
|
||||
},
|
||||
email: recordSeed.emailsPrimaryEmail,
|
||||
jobTitle: recordSeed.jobTitle,
|
||||
};
|
||||
case 'note':
|
||||
return {
|
||||
...commonProperties,
|
||||
title: recordSeed.title,
|
||||
body: recordSeed.body,
|
||||
};
|
||||
case 'task':
|
||||
return {
|
||||
...commonProperties,
|
||||
title: recordSeed.title,
|
||||
body: recordSeed.body,
|
||||
status: recordSeed.status,
|
||||
dueAt: recordSeed.dueAt,
|
||||
};
|
||||
case 'opportunity':
|
||||
return {
|
||||
...commonProperties,
|
||||
name: recordSeed.name,
|
||||
amount: recordSeed.amountAmountMicros,
|
||||
stage: recordSeed.stage,
|
||||
closeDate: recordSeed.closeDate,
|
||||
};
|
||||
default:
|
||||
return commonProperties;
|
||||
}
|
||||
const getter = propertyGetters[type as keyof typeof propertyGetters];
|
||||
|
||||
return getter ? getter() : commonProperties;
|
||||
}
|
||||
|
||||
private computeLinkedTimelineActivityRecords({
|
||||
@ -262,103 +368,160 @@ export class TimelineActivitySeederService {
|
||||
activityIndex,
|
||||
linkedObjectMetadataId,
|
||||
}: {
|
||||
activityType: 'note' | 'task';
|
||||
activityType: 'note' | 'task' | 'calendarEvent' | 'message';
|
||||
recordSeed: Record<string, unknown>;
|
||||
index: number;
|
||||
activityIndex: number;
|
||||
linkedObjectMetadataId: string;
|
||||
}): TimelineActivitySeedData[] {
|
||||
const targetInfo = this.getActivityTargetInfo({
|
||||
activityType,
|
||||
recordSeed,
|
||||
});
|
||||
const targetInfos = this.getActivityTargetInfos(activityType, recordSeed);
|
||||
|
||||
if (!targetInfo) {
|
||||
if (targetInfos.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const linkedActivity = this.computeLinkedActivityRecord({
|
||||
activityType,
|
||||
recordSeed,
|
||||
index,
|
||||
activityIndex,
|
||||
linkedObjectMetadataId,
|
||||
targetInfo,
|
||||
});
|
||||
|
||||
return [linkedActivity];
|
||||
return targetInfos.map((targetInfo, targetIndex) =>
|
||||
this.computeLinkedActivityRecord({
|
||||
activityType,
|
||||
recordSeed,
|
||||
index,
|
||||
activityIndex: activityIndex + targetIndex,
|
||||
linkedObjectMetadataId,
|
||||
targetInfo,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private getActivityTargetInfo({
|
||||
activityType,
|
||||
recordSeed,
|
||||
}: {
|
||||
activityType: 'note' | 'task';
|
||||
recordSeed: Record<string, unknown>;
|
||||
}): ActivityTargetInfo | null {
|
||||
if (activityType === 'note') {
|
||||
const noteTargetSeed = NOTE_TARGET_DATA_SEEDS.find(
|
||||
(target) => target.noteId === recordSeed.id,
|
||||
);
|
||||
private getActivityTargetInfos(
|
||||
activityType: 'note' | 'task' | 'calendarEvent' | 'message',
|
||||
recordSeed: Record<string, unknown>,
|
||||
): ActivityTargetInfo[] {
|
||||
const targetGetters = {
|
||||
note: () => this.getNoteTargetInfos(recordSeed),
|
||||
task: () => this.getTaskTargetInfos(recordSeed),
|
||||
calendarEvent: () => this.getCalendarEventTargetInfos(recordSeed),
|
||||
message: () => this.getMessageTargetInfos(recordSeed),
|
||||
};
|
||||
|
||||
if (!noteTargetSeed) {
|
||||
return null;
|
||||
}
|
||||
const getter = targetGetters[activityType];
|
||||
|
||||
if (noteTargetSeed.personId) {
|
||||
return {
|
||||
targetType: 'person',
|
||||
targetId: noteTargetSeed.personId,
|
||||
};
|
||||
}
|
||||
return getter ? getter() : [];
|
||||
}
|
||||
|
||||
if (noteTargetSeed.companyId) {
|
||||
return {
|
||||
targetType: 'company',
|
||||
targetId: noteTargetSeed.companyId,
|
||||
};
|
||||
}
|
||||
private getNoteTargetInfos(
|
||||
recordSeed: Record<string, unknown>,
|
||||
): ActivityTargetInfo[] {
|
||||
const noteTargetSeed = NOTE_TARGET_DATA_SEEDS.find(
|
||||
(target) => target.noteId === recordSeed.id,
|
||||
);
|
||||
|
||||
if (noteTargetSeed.opportunityId) {
|
||||
return {
|
||||
targetType: 'opportunity',
|
||||
targetId: noteTargetSeed.opportunityId,
|
||||
};
|
||||
if (!noteTargetSeed) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const targetChecks = [
|
||||
{ id: noteTargetSeed.personId, type: 'person' },
|
||||
{ id: noteTargetSeed.companyId, type: 'company' },
|
||||
{ id: noteTargetSeed.opportunityId, type: 'opportunity' },
|
||||
];
|
||||
|
||||
for (const { id, type } of targetChecks) {
|
||||
if (id) {
|
||||
return [{ targetType: type, targetId: id }];
|
||||
}
|
||||
}
|
||||
|
||||
if (activityType === 'task') {
|
||||
const taskTargetSeed = TASK_TARGET_DATA_SEEDS.find(
|
||||
(target) => target.taskId === recordSeed.id,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!taskTargetSeed) {
|
||||
return null;
|
||||
}
|
||||
private getTaskTargetInfos(
|
||||
recordSeed: Record<string, unknown>,
|
||||
): ActivityTargetInfo[] {
|
||||
const taskTargetSeed = TASK_TARGET_DATA_SEEDS.find(
|
||||
(target) => target.taskId === recordSeed.id,
|
||||
);
|
||||
|
||||
if (taskTargetSeed.personId) {
|
||||
return {
|
||||
targetType: 'person',
|
||||
targetId: taskTargetSeed.personId,
|
||||
};
|
||||
}
|
||||
if (!taskTargetSeed) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (taskTargetSeed.companyId) {
|
||||
return {
|
||||
targetType: 'company',
|
||||
targetId: taskTargetSeed.companyId,
|
||||
};
|
||||
}
|
||||
const targetChecks = [
|
||||
{ id: taskTargetSeed.personId, type: 'person' },
|
||||
{ id: taskTargetSeed.companyId, type: 'company' },
|
||||
{ id: taskTargetSeed.opportunityId, type: 'opportunity' },
|
||||
];
|
||||
|
||||
if (taskTargetSeed.opportunityId) {
|
||||
return {
|
||||
targetType: 'opportunity',
|
||||
targetId: taskTargetSeed.opportunityId,
|
||||
};
|
||||
for (const { id, type } of targetChecks) {
|
||||
if (id) {
|
||||
return [{ targetType: type, targetId: id }];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return [];
|
||||
}
|
||||
|
||||
private getCalendarEventTargetInfos(
|
||||
recordSeed: Record<string, unknown>,
|
||||
): ActivityTargetInfo[] {
|
||||
const eventParticipants = CALENDAR_EVENT_PARTICIPANT_DATA_SEEDS.filter(
|
||||
(participant) => participant.calendarEventId === recordSeed.id,
|
||||
);
|
||||
|
||||
const targetInfos: ActivityTargetInfo[] = [];
|
||||
|
||||
eventParticipants.forEach((participant) => {
|
||||
if (participant.personId) {
|
||||
targetInfos.push({
|
||||
targetType: 'person',
|
||||
targetId: participant.personId as string,
|
||||
});
|
||||
|
||||
const person = PERSON_DATA_SEEDS.find(
|
||||
(p) => p.id === participant.personId,
|
||||
);
|
||||
|
||||
if (person?.companyId) {
|
||||
targetInfos.push({
|
||||
targetType: 'company',
|
||||
targetId: person.companyId as string,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return targetInfos;
|
||||
}
|
||||
|
||||
private getMessageTargetInfos(
|
||||
recordSeed: Record<string, unknown>,
|
||||
): ActivityTargetInfo[] {
|
||||
const messageParticipants = MESSAGE_PARTICIPANT_DATA_SEEDS.filter(
|
||||
(participant) => participant.messageId === recordSeed.id,
|
||||
);
|
||||
|
||||
const targetInfos: ActivityTargetInfo[] = [];
|
||||
|
||||
messageParticipants.forEach((participant) => {
|
||||
if (participant.personId) {
|
||||
targetInfos.push({
|
||||
targetType: 'person',
|
||||
targetId: participant.personId,
|
||||
});
|
||||
|
||||
const person = PERSON_DATA_SEEDS.find(
|
||||
(p) => p.id === participant.personId,
|
||||
);
|
||||
|
||||
if (person?.companyId) {
|
||||
targetInfos.push({
|
||||
targetType: 'company',
|
||||
targetId: person.companyId as string,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return targetInfos;
|
||||
}
|
||||
|
||||
private computeLinkedActivityRecord({
|
||||
@ -369,48 +532,21 @@ export class TimelineActivitySeederService {
|
||||
linkedObjectMetadataId,
|
||||
targetInfo,
|
||||
}: CreateLinkedActivityParams): TimelineActivitySeedData {
|
||||
const generateLinkedTimelineActivityId = (
|
||||
type: string,
|
||||
targetType: string,
|
||||
idx: number,
|
||||
): string => {
|
||||
const prefix = '20202020';
|
||||
|
||||
const entityCodes: Record<string, string> = {
|
||||
note: '0601',
|
||||
task: '0651',
|
||||
};
|
||||
const targetCodes: Record<string, string> = {
|
||||
person: '1001',
|
||||
company: '2001',
|
||||
opportunity: '3001',
|
||||
};
|
||||
const entityCode = entityCodes[type] || '0000';
|
||||
const targetCode = targetCodes[targetType] || '0000';
|
||||
const paddedIndex = idx.toString().padStart(4, '0');
|
||||
|
||||
return `${prefix}-${entityCode}-${targetCode}-8001-${paddedIndex}00000001`;
|
||||
};
|
||||
|
||||
const linkedActivityId = this.generateLinkedTimelineActivityId(
|
||||
activityType,
|
||||
targetInfo.targetType,
|
||||
activityIndex,
|
||||
);
|
||||
const creationDate = new Date().toISOString();
|
||||
const { linkedRecordCachedName, linkedProperties } =
|
||||
this.getLinkedRecordData(activityType, recordSeed, index);
|
||||
|
||||
const linkedActivity: TimelineActivitySeedData = {
|
||||
id: generateLinkedTimelineActivityId(
|
||||
activityType,
|
||||
targetInfo.targetType,
|
||||
activityIndex,
|
||||
),
|
||||
name: `linked-${activityType}.created`,
|
||||
properties: JSON.stringify({
|
||||
after: {
|
||||
id: recordSeed.id,
|
||||
title: recordSeed.title,
|
||||
body: recordSeed.body,
|
||||
},
|
||||
}),
|
||||
linkedRecordCachedName:
|
||||
(recordSeed.title as string) || `${activityType} ${index + 1}`,
|
||||
linkedRecordId: recordSeed.id as string,
|
||||
id: linkedActivityId,
|
||||
name: this.getLinkedActivityName(activityType),
|
||||
properties: JSON.stringify({ after: linkedProperties }),
|
||||
linkedRecordCachedName,
|
||||
linkedRecordId: String(recordSeed.id || ''),
|
||||
linkedObjectMetadataId,
|
||||
workspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
|
||||
companyId: null,
|
||||
@ -423,36 +559,127 @@ export class TimelineActivitySeederService {
|
||||
happensAt: creationDate,
|
||||
};
|
||||
|
||||
// Set target ID (person, company, or opportunity)
|
||||
const targetIdKey = `${targetInfo.targetType}Id`;
|
||||
|
||||
// @ts-expect-error - This is okay for morph
|
||||
// alternative is to be explicit but it's very verbose
|
||||
linkedActivity[`${targetInfo.targetType}Id`] = targetInfo.targetId;
|
||||
// @ts-expect-error - This is okay for morph
|
||||
linkedActivity[`${activityType}Id`] = recordSeed.id;
|
||||
linkedActivity[targetIdKey] = targetInfo.targetId;
|
||||
|
||||
// Only set activity ID for entities that have corresponding columns
|
||||
const entitiesWithColumns = new Set(['note', 'task']);
|
||||
|
||||
if (entitiesWithColumns.has(activityType)) {
|
||||
const activityIdKey = `${activityType}Id`;
|
||||
|
||||
// @ts-expect-error - This is okay for morph
|
||||
linkedActivity[activityIdKey] = recordSeed.id;
|
||||
}
|
||||
|
||||
return linkedActivity;
|
||||
}
|
||||
|
||||
private async getObjectMetadataIds(workspaceId: string): Promise<{
|
||||
noteMetadataId: string;
|
||||
taskMetadataId: string;
|
||||
}> {
|
||||
const noteMetadata =
|
||||
await this.objectMetadataService.findOneWithinWorkspace(workspaceId, {
|
||||
where: { nameSingular: 'note' },
|
||||
});
|
||||
private getLinkedRecordData(
|
||||
activityType: string,
|
||||
recordSeed: Record<string, unknown>,
|
||||
index: number,
|
||||
): {
|
||||
linkedRecordCachedName: string;
|
||||
linkedProperties: Record<string, unknown>;
|
||||
} {
|
||||
const baseProperties = { id: recordSeed.id };
|
||||
|
||||
const taskMetadata =
|
||||
await this.objectMetadataService.findOneWithinWorkspace(workspaceId, {
|
||||
where: { nameSingular: 'task' },
|
||||
});
|
||||
const dataGetters = {
|
||||
calendarEvent: () => ({
|
||||
linkedRecordCachedName: String(
|
||||
recordSeed.title || `Calendar Event ${index + 1}`,
|
||||
),
|
||||
linkedProperties: {
|
||||
...baseProperties,
|
||||
title: recordSeed.title,
|
||||
description: recordSeed.description,
|
||||
startsAt: recordSeed.startsAt,
|
||||
endsAt: recordSeed.endsAt,
|
||||
},
|
||||
}),
|
||||
message: () => ({
|
||||
linkedRecordCachedName: String(
|
||||
recordSeed.subject || `Message ${index + 1}`,
|
||||
),
|
||||
linkedProperties: {
|
||||
...baseProperties,
|
||||
subject: recordSeed.subject,
|
||||
text: recordSeed.text,
|
||||
receivedAt: recordSeed.receivedAt,
|
||||
},
|
||||
}),
|
||||
default: () => ({
|
||||
linkedRecordCachedName: String(
|
||||
recordSeed.title || `${activityType} ${index + 1}`,
|
||||
),
|
||||
linkedProperties: {
|
||||
...baseProperties,
|
||||
title: recordSeed.title,
|
||||
body: recordSeed.body,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
if (!noteMetadata || !taskMetadata) {
|
||||
throw new Error('Could not find note or task metadata');
|
||||
const getter =
|
||||
dataGetters[activityType as keyof typeof dataGetters] ||
|
||||
dataGetters.default;
|
||||
|
||||
return getter();
|
||||
}
|
||||
|
||||
private generateLinkedTimelineActivityId(
|
||||
type: string,
|
||||
targetType: string,
|
||||
index: number,
|
||||
): string {
|
||||
const prefix = '20202020';
|
||||
const entityCode =
|
||||
this.ENTITY_CODES[type as keyof typeof this.ENTITY_CODES] || '0000';
|
||||
const targetCode =
|
||||
this.TARGET_CODES[targetType as keyof typeof this.TARGET_CODES] || '0000';
|
||||
// Ensure the last segment is exactly 12 hex characters
|
||||
const indexHex = (index % 0xffffffff).toString(16).padStart(8, '0');
|
||||
|
||||
return `${prefix}-${entityCode}-${targetCode}-8001-${indexHex}0001`;
|
||||
}
|
||||
|
||||
private async getObjectMetadataIds(
|
||||
workspaceId: string,
|
||||
): Promise<ObjectMetadataIds> {
|
||||
const metadataQueries = [
|
||||
{ name: 'note', key: 'noteMetadataId' },
|
||||
{ name: 'task', key: 'taskMetadataId' },
|
||||
{ name: 'calendarEvent', key: 'calendarEventMetadataId' },
|
||||
{ name: 'message', key: 'messageMetadataId' },
|
||||
];
|
||||
|
||||
const metadataResults = await Promise.all(
|
||||
metadataQueries.map(({ name }) =>
|
||||
this.objectMetadataService.findOneWithinWorkspace(workspaceId, {
|
||||
where: { nameSingular: name },
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const missingMetadata = metadataResults
|
||||
.map((result, index) => (result ? null : metadataQueries[index].name))
|
||||
.filter(Boolean);
|
||||
|
||||
if (missingMetadata.length > 0) {
|
||||
throw new Error(
|
||||
`Could not find metadata for: ${missingMetadata.join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
noteMetadataId: noteMetadata.id,
|
||||
taskMetadataId: taskMetadata.id,
|
||||
noteMetadataId: metadataResults[0]?.id || '',
|
||||
taskMetadataId: metadataResults[1]?.id || '',
|
||||
calendarEventMetadataId: metadataResults[2]?.id || '',
|
||||
messageMetadataId: metadataResults[3]?.id || '',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,10 @@ import { Injectable } from '@nestjs/common';
|
||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service';
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import {
|
||||
SEED_APPLE_WORKSPACE_ID,
|
||||
SEED_YCOMBINATOR_WORKSPACE_ID,
|
||||
} from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspaces.util';
|
||||
import { COMPANY_CUSTOM_FIELD_SEEDS } from 'src/engine/workspace-manager/dev-seeder/metadata/custom-fields/constants/company-custom-field-seeds.constant';
|
||||
import { PERSON_CUSTOM_FIELD_SEEDS } from 'src/engine/workspace-manager/dev-seeder/metadata/custom-fields/constants/person-custom-field-seeds.constant';
|
||||
import { PET_CUSTOM_FIELD_SEEDS } from 'src/engine/workspace-manager/dev-seeder/metadata/custom-fields/constants/pet-custom-field-seeds.constant';
|
||||
@ -20,6 +24,41 @@ export class DevSeederMetadataService {
|
||||
private readonly fieldMetadataService: FieldMetadataService,
|
||||
) {}
|
||||
|
||||
private readonly workspaceConfigs: Record<
|
||||
string,
|
||||
{
|
||||
objects: { seed: ObjectMetadataSeed; fields?: FieldMetadataSeed[] }[];
|
||||
fields: { objectName: string; seeds: FieldMetadataSeed[] }[];
|
||||
}
|
||||
> = {
|
||||
[SEED_APPLE_WORKSPACE_ID]: {
|
||||
objects: [
|
||||
{ seed: ROCKET_CUSTOM_OBJECT_SEED },
|
||||
{ seed: PET_CUSTOM_OBJECT_SEED, fields: PET_CUSTOM_FIELD_SEEDS },
|
||||
{
|
||||
seed: SURVEY_RESULT_CUSTOM_OBJECT_SEED,
|
||||
fields: SURVEY_RESULT_CUSTOM_FIELD_SEEDS,
|
||||
},
|
||||
],
|
||||
fields: [
|
||||
{ objectName: 'company', seeds: COMPANY_CUSTOM_FIELD_SEEDS },
|
||||
{ objectName: 'person', seeds: PERSON_CUSTOM_FIELD_SEEDS },
|
||||
],
|
||||
},
|
||||
[SEED_YCOMBINATOR_WORKSPACE_ID]: {
|
||||
objects: [
|
||||
{
|
||||
seed: SURVEY_RESULT_CUSTOM_OBJECT_SEED,
|
||||
fields: SURVEY_RESULT_CUSTOM_FIELD_SEEDS,
|
||||
},
|
||||
],
|
||||
fields: [
|
||||
{ objectName: 'company', seeds: COMPANY_CUSTOM_FIELD_SEEDS },
|
||||
{ objectName: 'person', seeds: PERSON_CUSTOM_FIELD_SEEDS },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
public async seed({
|
||||
dataSourceMetadata,
|
||||
workspaceId,
|
||||
@ -27,47 +66,37 @@ export class DevSeederMetadataService {
|
||||
dataSourceMetadata: DataSourceEntity;
|
||||
workspaceId: string;
|
||||
}) {
|
||||
await this.seedCustomObject({
|
||||
dataSourceId: dataSourceMetadata.id,
|
||||
workspaceId,
|
||||
objectMetadataSeed: ROCKET_CUSTOM_OBJECT_SEED,
|
||||
});
|
||||
const config = this.workspaceConfigs[workspaceId];
|
||||
|
||||
await this.seedCustomObject({
|
||||
dataSourceId: dataSourceMetadata.id,
|
||||
workspaceId,
|
||||
objectMetadataSeed: PET_CUSTOM_OBJECT_SEED,
|
||||
});
|
||||
if (!config) {
|
||||
throw new Error(
|
||||
`Workspace configuration not found for workspaceId: ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
await this.seedCustomFields({
|
||||
workspaceId,
|
||||
objectMetadataNameSingular: PET_CUSTOM_OBJECT_SEED.nameSingular,
|
||||
fieldMetadataSeeds: PET_CUSTOM_FIELD_SEEDS,
|
||||
});
|
||||
for (const obj of config.objects) {
|
||||
await this.seedCustomObject({
|
||||
dataSourceId: dataSourceMetadata.id,
|
||||
workspaceId,
|
||||
objectMetadataSeed: obj.seed,
|
||||
});
|
||||
|
||||
await this.seedCustomObject({
|
||||
dataSourceId: dataSourceMetadata.id,
|
||||
workspaceId,
|
||||
objectMetadataSeed: SURVEY_RESULT_CUSTOM_OBJECT_SEED,
|
||||
});
|
||||
if (obj.fields) {
|
||||
await this.seedCustomFields({
|
||||
workspaceId,
|
||||
objectMetadataNameSingular: obj.seed.nameSingular,
|
||||
fieldMetadataSeeds: obj.fields,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await this.seedCustomFields({
|
||||
workspaceId,
|
||||
objectMetadataNameSingular: SURVEY_RESULT_CUSTOM_OBJECT_SEED.nameSingular,
|
||||
fieldMetadataSeeds: SURVEY_RESULT_CUSTOM_FIELD_SEEDS,
|
||||
});
|
||||
|
||||
await this.seedCustomFields({
|
||||
workspaceId,
|
||||
objectMetadataNameSingular: 'company',
|
||||
fieldMetadataSeeds: COMPANY_CUSTOM_FIELD_SEEDS,
|
||||
});
|
||||
|
||||
await this.seedCustomFields({
|
||||
workspaceId,
|
||||
objectMetadataNameSingular: 'person',
|
||||
fieldMetadataSeeds: PERSON_CUSTOM_FIELD_SEEDS,
|
||||
});
|
||||
for (const fieldConfig of config.fields) {
|
||||
await this.seedCustomFields({
|
||||
workspaceId,
|
||||
objectMetadataNameSingular: fieldConfig.objectName,
|
||||
fieldMetadataSeeds: fieldConfig.seeds,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async seedCustomObject({
|
||||
@ -101,7 +130,9 @@ export class DevSeederMetadataService {
|
||||
});
|
||||
|
||||
if (!objectMetadata) {
|
||||
throw new Error('Object metadata not found');
|
||||
throw new Error(
|
||||
`Object metadata not found for: ${objectMetadataNameSingular}`,
|
||||
);
|
||||
}
|
||||
|
||||
await this.fieldMetadataService.createMany(
|
||||
|
||||
@ -20,7 +20,7 @@ export const tasksAllView = (objectMetadataItems: ObjectMetadataEntity[]) => {
|
||||
type: 'table',
|
||||
key: 'INDEX',
|
||||
position: 0,
|
||||
icon: 'IconCheckbox',
|
||||
icon: 'IconList',
|
||||
kanbanFieldMetadataId: '',
|
||||
filters: [] /* [
|
||||
{
|
||||
|
||||
@ -4,11 +4,11 @@ exports[`calendarEventsResolver (e2e) should find many calendarEvents 1`] = `
|
||||
{
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"description": "Discuss project progress",
|
||||
"description": "Daily team synchronization meeting to discuss progress and blockers.",
|
||||
"endsAt": Any<String>,
|
||||
"id": "20202020-1c0e-494c-a1b6-85b1c6fefaa5",
|
||||
"id": "20202020-0001-4e7c-8001-123456789cde",
|
||||
"startsAt": Any<String>,
|
||||
"title": "Meeting with Christoph",
|
||||
"title": "Team Standup",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
@ -17,11 +17,11 @@ exports[`calendarEventsResolver (e2e) should find one calendarEvent 1`] = `
|
||||
{
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"description": "Discuss project progress",
|
||||
"description": "Daily team synchronization meeting to discuss progress and blockers.",
|
||||
"endsAt": Any<String>,
|
||||
"id": "20202020-1c0e-494c-a1b6-85b1c6fefaa5",
|
||||
"id": "20202020-0001-4e7c-8001-123456789cde",
|
||||
"startsAt": Any<String>,
|
||||
"title": "Meeting with Christoph",
|
||||
"title": "Team Standup",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
@ -4,12 +4,17 @@ exports[`messagesResolver (e2e) should find many messages 1`] = `
|
||||
{
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"id": "20202020-04c8-4f24-93f2-764948e95014",
|
||||
"subject": "Inquiry Regarding Topic",
|
||||
"text": "Good Morning,
|
||||
I am writing to inquire about information. Could you please provide me with details regarding this topic?
|
||||
Your assistance in this matter would be greatly appreciated. Thank you in advance for your prompt response.
|
||||
Best regards,Tim",
|
||||
"id": "20202020-0001-4e7c-8001-123456789bcd",
|
||||
"subject": "Meeting Request",
|
||||
"text": "Hello,
|
||||
|
||||
I hope this email finds you well. I am writing to request a meeting. I believe it would be beneficial for both parties to collaborate and explore potential opportunities.
|
||||
|
||||
Would you be available for a meeting sometime next week? Please let me know your availability, and I will arrange a suitable time.
|
||||
|
||||
Looking forward to your response.
|
||||
|
||||
Best regards",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
@ -18,12 +23,17 @@ exports[`messagesResolver (e2e) should find one message 1`] = `
|
||||
{
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"id": "20202020-2b8a-405d-8f42-e820ca921421",
|
||||
"id": "20202020-0001-4e7c-8001-123456789bcd",
|
||||
"subject": "Meeting Request",
|
||||
"text": "Hello,
|
||||
I hope this email finds you well. I am writing to request a meeting. I believe it would be beneficial for both parties to collaborate and explore potential opportunities. Would you be available for a meeting sometime next week? Please let me know your availability, and I will arrange a suitable time.
|
||||
Looking forward to your response.
|
||||
Best regards",
|
||||
"text": "Hello,
|
||||
|
||||
I hope this email finds you well. I am writing to request a meeting. I believe it would be beneficial for both parties to collaborate and explore potential opportunities.
|
||||
|
||||
Would you be available for a meeting sometime next week? Please let me know your availability, and I will arrange a suitable time.
|
||||
|
||||
Looking forward to your response.
|
||||
|
||||
Best regards",
|
||||
"updatedAt": Any<String>,
|
||||
}
|
||||
`;
|
||||
|
||||
@ -22,7 +22,7 @@ describe('calendarEventsResolver (e2e)', () => {
|
||||
|
||||
const edges = data.edges;
|
||||
|
||||
expect(edges.length).toEqual(1);
|
||||
expect(edges.length).toEqual(60);
|
||||
|
||||
const calendarEvent = edges[0].node;
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@ describe('messagesResolver (e2e)', () => {
|
||||
|
||||
const edges = data.edges;
|
||||
|
||||
expect(edges.length).toEqual(3);
|
||||
expect(edges.length).toEqual(60);
|
||||
|
||||
const message1 = edges[0].node;
|
||||
|
||||
|
||||
@ -31,8 +31,8 @@ describe('updateOneObjectRecordsPermissions', () => {
|
||||
objectMetadataSingularName: 'view',
|
||||
gqlFields: 'id',
|
||||
filter: {
|
||||
icon: {
|
||||
eq: 'IconCat',
|
||||
name: {
|
||||
eq: 'All Pets',
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -50,7 +50,7 @@ describe('updateOneObjectRecordsPermissions', () => {
|
||||
gqlFields: 'id',
|
||||
recordId: allPetsViewId,
|
||||
data: {
|
||||
icon: 'IconCat',
|
||||
icon: 'IconList',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user