Fix author attachment field (#15065)
# Migrate Attachment Author to CreatedBy Field **Twill Task**: https://twill.ai/twentyhq/ENG/tasks/7 ## Summary This PR implements a migration to transition the `Attachment` object from using an `author` relation field to using the standard `createdBy` field, addressing issue https://github.com/twentyhq/core-team-issues/issues/1594. ## Changes - **Added migration command** (`1-8-migrate-attachment-author-to-created-by.command.ts`): - Migrates existing attachment data to use `createdBy` instead of `author` - Ensures data integrity during the transition to the standard field pattern - **Updated Attachment workspace entity**: - Added `createdBy` relation field to the `Attachment` standard object - Registered new field ID in `standard-field-ids.ts` constants - **Integrated migration into upgrade pipeline**: - Added migration module for version 1.8 - Registered in the main upgrade version command module This change aligns the `Attachment` object with Twenty's standard field conventions by using the built-in `createdBy` field instead of a custom `author` field. --- Fixes https://github.com/twentyhq/core-team-issues/issues/1594 --------- Co-authored-by: Twill <agent@twill.ai> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
+172
-66
@@ -1,3 +1,4 @@
|
||||
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||
import { COMPANY_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/company-data-seeds.constant';
|
||||
import { NOTE_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/note-data-seeds.constant';
|
||||
import { OPPORTUNITY_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/opportunity-data-seeds.constant';
|
||||
@@ -10,10 +11,12 @@ type AttachmentDataSeed = {
|
||||
name: string;
|
||||
fullPath: string;
|
||||
type: string;
|
||||
fileCategory?: string;
|
||||
// Deprecated: Use createdBy instead
|
||||
authorId: string | null;
|
||||
// createdBySource: string;
|
||||
// createdByWorkspaceMemberId: string;
|
||||
// createdByName: string;
|
||||
createdBySource: string;
|
||||
createdByWorkspaceMemberId: string;
|
||||
createdByName: string;
|
||||
personId: string | null;
|
||||
companyId: string | null;
|
||||
noteId: string | null;
|
||||
@@ -26,10 +29,11 @@ export const ATTACHMENT_DATA_SEED_COLUMNS: (keyof AttachmentDataSeed)[] = [
|
||||
'name',
|
||||
'fullPath',
|
||||
'type',
|
||||
'authorId',
|
||||
// 'createdBySource',
|
||||
// 'createdByWorkspaceMemberId',
|
||||
// 'createdByName',
|
||||
'fileCategory',
|
||||
'authorId', // Deprecated: kept for backward compatibility during migration
|
||||
'createdBySource',
|
||||
'createdByWorkspaceMemberId',
|
||||
'createdByName',
|
||||
'personId',
|
||||
'companyId',
|
||||
'noteId',
|
||||
@@ -52,69 +56,169 @@ const GENERATE_ATTACHMENT_IDS = (): Record<string, string> => {
|
||||
|
||||
export const ATTACHMENT_DATA_SEED_IDS = GENERATE_ATTACHMENT_IDS();
|
||||
|
||||
// Pool of 5 reusable file templates
|
||||
const FILE_TEMPLATES = [
|
||||
{
|
||||
name: 'Contract Agreement.pdf',
|
||||
fullPath: 'attachment/sample-contract.pdf',
|
||||
type: 'TextDocument',
|
||||
},
|
||||
{
|
||||
name: 'Budget 2024.xlsx',
|
||||
fullPath: 'attachment/budget-2024.xlsx',
|
||||
type: 'Spreadsheet',
|
||||
},
|
||||
{
|
||||
name: 'Product Presentation.pptx',
|
||||
fullPath: 'attachment/presentation.pptx',
|
||||
type: 'Presentation',
|
||||
},
|
||||
{
|
||||
name: 'Screenshot.png',
|
||||
fullPath: 'attachment/screenshot.png',
|
||||
type: 'Image',
|
||||
},
|
||||
{
|
||||
name: 'Archive.zip',
|
||||
fullPath: 'attachment/archive.zip',
|
||||
type: 'Archive',
|
||||
},
|
||||
// Pool of 5 reusable file paths for attachments
|
||||
const FILE_TEMPLATE_PATHS = [
|
||||
'attachment/sample-contract.pdf',
|
||||
'attachment/budget-2024.xlsx',
|
||||
'attachment/presentation.pptx',
|
||||
'attachment/screenshot.png',
|
||||
'attachment/archive.zip',
|
||||
];
|
||||
|
||||
// Additional name variations for more realistic variety
|
||||
const FILE_NAME_VARIATIONS = [
|
||||
// Documents
|
||||
{ name: 'Service Agreement.pdf', type: 'TextDocument', pathIndex: 0 },
|
||||
{ name: 'NDA Document.pdf', type: 'TextDocument', pathIndex: 0 },
|
||||
{ name: 'Project Proposal.pdf', type: 'TextDocument', pathIndex: 0 },
|
||||
{ name: 'Invoice Q1 2024.pdf', type: 'TextDocument', pathIndex: 0 },
|
||||
{ name: 'Meeting Notes.pdf', type: 'TextDocument', pathIndex: 0 },
|
||||
{ name: 'Report Final.pdf', type: 'TextDocument', pathIndex: 0 },
|
||||
{ name: 'Contract Signed.pdf', type: 'TextDocument', pathIndex: 0 },
|
||||
{
|
||||
name: 'Service Agreement.pdf',
|
||||
type: 'TextDocument',
|
||||
fileCategory: 'TEXT_DOCUMENT',
|
||||
pathIndex: 0,
|
||||
},
|
||||
{
|
||||
name: 'NDA Document.pdf',
|
||||
type: 'TextDocument',
|
||||
fileCategory: 'TEXT_DOCUMENT',
|
||||
pathIndex: 0,
|
||||
},
|
||||
{
|
||||
name: 'Project Proposal.pdf',
|
||||
type: 'TextDocument',
|
||||
fileCategory: 'TEXT_DOCUMENT',
|
||||
pathIndex: 0,
|
||||
},
|
||||
{
|
||||
name: 'Invoice Q1 2024.pdf',
|
||||
type: 'TextDocument',
|
||||
fileCategory: 'TEXT_DOCUMENT',
|
||||
pathIndex: 0,
|
||||
},
|
||||
{
|
||||
name: 'Meeting Notes.pdf',
|
||||
type: 'TextDocument',
|
||||
fileCategory: 'TEXT_DOCUMENT',
|
||||
pathIndex: 0,
|
||||
},
|
||||
{
|
||||
name: 'Report Final.pdf',
|
||||
type: 'TextDocument',
|
||||
fileCategory: 'TEXT_DOCUMENT',
|
||||
pathIndex: 0,
|
||||
},
|
||||
{
|
||||
name: 'Contract Signed.pdf',
|
||||
type: 'TextDocument',
|
||||
fileCategory: 'TEXT_DOCUMENT',
|
||||
pathIndex: 0,
|
||||
},
|
||||
// Spreadsheets
|
||||
{ name: 'Financial Forecast.xlsx', type: 'Spreadsheet', pathIndex: 1 },
|
||||
{ name: 'Sales Report Q4.xlsx', type: 'Spreadsheet', pathIndex: 1 },
|
||||
{ name: 'Team Roster.xlsx', type: 'Spreadsheet', pathIndex: 1 },
|
||||
{ name: 'Expense Report.xlsx', type: 'Spreadsheet', pathIndex: 1 },
|
||||
{ name: 'Inventory List.xlsx', type: 'Spreadsheet', pathIndex: 1 },
|
||||
{ name: 'Data Export.csv', type: 'Spreadsheet', pathIndex: 1 },
|
||||
{
|
||||
name: 'Financial Forecast.xlsx',
|
||||
type: 'Spreadsheet',
|
||||
fileCategory: 'SPREADSHEET',
|
||||
pathIndex: 1,
|
||||
},
|
||||
{
|
||||
name: 'Sales Report Q4.xlsx',
|
||||
type: 'Spreadsheet',
|
||||
fileCategory: 'SPREADSHEET',
|
||||
pathIndex: 1,
|
||||
},
|
||||
{
|
||||
name: 'Team Roster.xlsx',
|
||||
type: 'Spreadsheet',
|
||||
fileCategory: 'SPREADSHEET',
|
||||
pathIndex: 1,
|
||||
},
|
||||
{
|
||||
name: 'Expense Report.xlsx',
|
||||
type: 'Spreadsheet',
|
||||
fileCategory: 'SPREADSHEET',
|
||||
pathIndex: 1,
|
||||
},
|
||||
{
|
||||
name: 'Inventory List.xlsx',
|
||||
type: 'Spreadsheet',
|
||||
fileCategory: 'SPREADSHEET',
|
||||
pathIndex: 1,
|
||||
},
|
||||
{
|
||||
name: 'Data Export.csv',
|
||||
type: 'Spreadsheet',
|
||||
fileCategory: 'SPREADSHEET',
|
||||
pathIndex: 1,
|
||||
},
|
||||
// Presentations
|
||||
{ name: 'Pitch Deck.pptx', type: 'Presentation', pathIndex: 2 },
|
||||
{ name: 'Q4 Results.pptx', type: 'Presentation', pathIndex: 2 },
|
||||
{ name: 'Roadmap 2024.pptx', type: 'Presentation', pathIndex: 2 },
|
||||
{ name: 'Company Overview.pptx', type: 'Presentation', pathIndex: 2 },
|
||||
{ name: 'Training Materials.pptx', type: 'Presentation', pathIndex: 2 },
|
||||
{
|
||||
name: 'Pitch Deck.pptx',
|
||||
type: 'Presentation',
|
||||
fileCategory: 'PRESENTATION',
|
||||
pathIndex: 2,
|
||||
},
|
||||
{
|
||||
name: 'Q4 Results.pptx',
|
||||
type: 'Presentation',
|
||||
fileCategory: 'PRESENTATION',
|
||||
pathIndex: 2,
|
||||
},
|
||||
{
|
||||
name: 'Roadmap 2024.pptx',
|
||||
type: 'Presentation',
|
||||
fileCategory: 'PRESENTATION',
|
||||
pathIndex: 2,
|
||||
},
|
||||
{
|
||||
name: 'Company Overview.pptx',
|
||||
type: 'Presentation',
|
||||
fileCategory: 'PRESENTATION',
|
||||
pathIndex: 2,
|
||||
},
|
||||
{
|
||||
name: 'Training Materials.pptx',
|
||||
type: 'Presentation',
|
||||
fileCategory: 'PRESENTATION',
|
||||
pathIndex: 2,
|
||||
},
|
||||
// Images
|
||||
{ name: 'Company Logo.png', type: 'Image', pathIndex: 3 },
|
||||
{ name: 'Product Photo.jpg', type: 'Image', pathIndex: 3 },
|
||||
{ name: 'Diagram.png', type: 'Image', pathIndex: 3 },
|
||||
{ name: 'Wireframe.png', type: 'Image', pathIndex: 3 },
|
||||
{ name: 'Mockup Design.png', type: 'Image', pathIndex: 3 },
|
||||
{ name: 'Headshot.jpg', type: 'Image', pathIndex: 3 },
|
||||
{
|
||||
name: 'Company Logo.png',
|
||||
type: 'Image',
|
||||
fileCategory: 'IMAGE',
|
||||
pathIndex: 3,
|
||||
},
|
||||
{
|
||||
name: 'Product Photo.jpg',
|
||||
type: 'Image',
|
||||
fileCategory: 'IMAGE',
|
||||
pathIndex: 3,
|
||||
},
|
||||
{ name: 'Diagram.png', type: 'Image', fileCategory: 'IMAGE', pathIndex: 3 },
|
||||
{ name: 'Wireframe.png', type: 'Image', fileCategory: 'IMAGE', pathIndex: 3 },
|
||||
{
|
||||
name: 'Mockup Design.png',
|
||||
type: 'Image',
|
||||
fileCategory: 'IMAGE',
|
||||
pathIndex: 3,
|
||||
},
|
||||
{ name: 'Headshot.jpg', type: 'Image', fileCategory: 'IMAGE', pathIndex: 3 },
|
||||
// Archives
|
||||
{ name: 'Project Files.zip', type: 'Archive', pathIndex: 4 },
|
||||
{ name: 'Backup Data.zip', type: 'Archive', pathIndex: 4 },
|
||||
{ name: 'Source Code.zip', type: 'Archive', pathIndex: 4 },
|
||||
{
|
||||
name: 'Project Files.zip',
|
||||
type: 'Archive',
|
||||
fileCategory: 'ARCHIVE',
|
||||
pathIndex: 4,
|
||||
},
|
||||
{
|
||||
name: 'Backup Data.zip',
|
||||
type: 'Archive',
|
||||
fileCategory: 'ARCHIVE',
|
||||
pathIndex: 4,
|
||||
},
|
||||
{
|
||||
name: 'Source Code.zip',
|
||||
type: 'Archive',
|
||||
fileCategory: 'ARCHIVE',
|
||||
pathIndex: 4,
|
||||
},
|
||||
];
|
||||
|
||||
const GENERATE_ATTACHMENT_SEEDS = (): AttachmentDataSeed[] => {
|
||||
@@ -133,7 +237,7 @@ const GENERATE_ATTACHMENT_SEEDS = (): AttachmentDataSeed[] => {
|
||||
// Cycle through file name variations
|
||||
const NAME_VARIATION_INDEX = INDEX % FILE_NAME_VARIATIONS.length;
|
||||
const NAME_VARIATION = FILE_NAME_VARIATIONS[NAME_VARIATION_INDEX];
|
||||
const FILE_TEMPLATE = FILE_TEMPLATES[NAME_VARIATION.pathIndex];
|
||||
const FILE_PATH = FILE_TEMPLATE_PATHS[NAME_VARIATION.pathIndex];
|
||||
|
||||
// Determine which entity this attachment belongs to
|
||||
// Distribution: ~30% person, ~30% company, ~20% note, ~15% task, ~5% opportunity
|
||||
@@ -170,12 +274,14 @@ const GENERATE_ATTACHMENT_SEEDS = (): AttachmentDataSeed[] => {
|
||||
ATTACHMENT_SEEDS.push({
|
||||
id: ATTACHMENT_DATA_SEED_IDS[`ID_${INDEX}`],
|
||||
name: NAME_VARIATION.name,
|
||||
fullPath: FILE_TEMPLATE.fullPath,
|
||||
fullPath: FILE_PATH,
|
||||
type: NAME_VARIATION.type,
|
||||
fileCategory: NAME_VARIATION.fileCategory,
|
||||
// Deprecated: Use createdBy fields instead
|
||||
authorId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
|
||||
// createdBySource: 'MANUAL',
|
||||
//createdByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
|
||||
//createdByName: 'Tim A',
|
||||
createdBySource: FieldActorSource.MANUAL,
|
||||
createdByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
|
||||
createdByName: 'Tim A',
|
||||
personId,
|
||||
companyId,
|
||||
noteId,
|
||||
|
||||
Reference in New Issue
Block a user