Skip to content

Conversation

@mabdullahabaid
Copy link
Member

Closes 1744.

This PR migrates attachments to morph relations behind a feature flag, following the TimelineActivity pattern. it introduces the IS_ATTACHMENT_MIGRATED flag, updates standard field metadata and indexes to use morph relations, adds a workspace migration that renames attachment.*Id columns to target*Id and converts the corresponding field metadata to MORPH_RELATION with a shared morphId. On the frontend, attachment read/write paths now switch to target*Id when the flag is enabled.

It also fixes optimistic filtering for morph join columns. The metadata API deduplicates morph fields, so attachments now expose a single target field of type MORPH_RELATION plus a morphRelations array listing each target object. Because only one settings.joinColumnName is returned (e.g. targetRocketId), filters like targetCompanyId don’t map to any field and the optimistic cache code throws. doesMorphRelationJoinColumnMatch resolves this by computing all valid join column names from morphRelations using computeMorphRelationFieldName and comparing them to the filter key. That makes filters like targetCompanyId resolvable even with a single target field, so attachment uploads and list matching no longer crash.

image

Today the metadata API returns one morph field called target and a list of possible targets (morphRelations), but it does not tell us the join column for each target. That’s why the Frontend had to compute join column names.

If we want to fix this at the API level, there are two options:

  • Add join column names to each target in morphRelations (e.g. company → targetCompanyId). This is additive and low‑risk.
  • Return each target as its own field (targetCompany, targetPerson, etc.) instead of a single target. This is a larger change because it changes the shape of metadata and would require more UI updates.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 31 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-migrate-attachment-to-morph-relations.command.ts">

<violation number="1" location="packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-migrate-attachment-to-morph-relations.command.ts:223">
P1: Feature flag enabling and cache operations after transaction commit can leave workspace in inconsistent state if they fail. Since the transaction is already committed, a failure here means database changes are persisted but the feature flag isn't set, causing the next migration attempt to fail on already-renamed columns. Consider enabling the feature flag before committing, or making the column renames idempotent by checking column existence first.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.


this.logger.log(`✅ Successfully migrated attachment fieldmetadata`);

await queryRunner.commitTransaction();
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Feature flag enabling and cache operations after transaction commit can leave workspace in inconsistent state if they fail. Since the transaction is already committed, a failure here means database changes are persisted but the feature flag isn't set, causing the next migration attempt to fail on already-renamed columns. Consider enabling the feature flag before committing, or making the column renames idempotent by checking column existence first.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-migrate-attachment-to-morph-relations.command.ts, line 223:

<comment>Feature flag enabling and cache operations after transaction commit can leave workspace in inconsistent state if they fail. Since the transaction is already committed, a failure here means database changes are persisted but the feature flag isn't set, causing the next migration attempt to fail on already-renamed columns. Consider enabling the feature flag before committing, or making the column renames idempotent by checking column existence first.</comment>

<file context>
@@ -0,0 +1,258 @@
+
+      this.logger.log(`✅ Successfully migrated attachment fieldmetadata`);
+
+      await queryRunner.commitTransaction();
+
+      await this.featureFlagService.enableFeatureFlags(
</file context>
Fix with Cubic

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 23, 2026

Greptile Overview

Greptile Summary

This PR migrates attachments from individual relation fields to morph relations, following the pattern established by TimelineActivity. The migration is protected by the IS_ATTACHMENT_MIGRATED feature flag.

Key changes:

  • Backend migration command renames database columns from {entity}Id to target{Entity}Id and converts field metadata from RELATION to MORPH_RELATION with a shared morphId
  • Frontend hooks (useAttachments, useUploadAttachmentFile) now switch field names based on the feature flag
  • Fixed critical bug in optimistic filtering where morph relation join columns (e.g., targetCompanyId) couldn't be resolved because the metadata API returns a single target field with only one joinColumnName
  • The fix introduces doesMorphRelationJoinColumnMatch which computes all valid join column names from morphRelations array and compares them to the filter key
  • New workspaces get the flag enabled by default, existing workspaces require running the migration command

Implementation quality:
The migration is well-structured with proper transaction handling, rollback on error, cache invalidation, and metadata version incrementing. The frontend changes are minimal and properly gated behind the feature flag. The fix for morph relation filtering is elegant and solves the real issue without requiring API changes.

Confidence Score: 4/5

  • This PR is safe to merge with proper testing of the migration path
  • The implementation follows established patterns (TimelineActivity migration), includes proper error handling and rollback logic, and the feature flag provides a safe rollout mechanism. Score is 4/5 (not 5) because database migrations always carry risk and the migration command should be thoroughly tested on a staging environment with real data before production deployment
  • packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-migrate-attachment-to-morph-relations.command.ts should be tested on staging with existing attachment data to ensure no data loss during column renames

Important Files Changed

Filename Overview
packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-migrate-attachment-to-morph-relations.command.ts Migration command that renames attachment columns from *Id to target*Id, updates field metadata to use MORPH_RELATION type with shared morphId, and enables feature flag
packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.ts Added doesMorphRelationJoinColumnMatch helper to resolve morph relation join columns by computing all valid join column names from morphRelations, fixing optimistic filter matching
packages/twenty-front/src/modules/activities/files/hooks/useAttachments.tsx Updated to use feature flag and call getActivityTargetObjectFieldIdName to switch between legacy *Id and new target*Id column names
packages/twenty-front/src/modules/activities/files/hooks/useUploadAttachmentFile.tsx Updated attachment creation to dynamically compute target field name based on feature flag state
packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-attachment-standard-flat-field-metadata.util.ts Changed all attachment relation fields from RELATION to MORPH_RELATION type with shared morphId, renamed fields to target* pattern, and updated join column names to target*Id

Sequence Diagram

sequenceDiagram
    participant User
    participant Frontend
    participant Backend
    participant Database
    participant FeatureFlag
    
    Note over Backend,Database: Migration Phase (1-17 Command)
    Backend->>FeatureFlag: Check IS_ATTACHMENT_MIGRATED
    alt Not Migrated
        Backend->>Database: Rename columns (taskId → targetTaskId, etc.)
        Backend->>Database: Update fieldMetadata type to MORPH_RELATION
        Backend->>Database: Set shared morphId on all target fields
        Backend->>FeatureFlag: Enable IS_ATTACHMENT_MIGRATED flag
        Backend->>Backend: Invalidate and recompute cache
    end
    
    Note over User,FeatureFlag: Runtime - Attachment Upload
    User->>Frontend: Upload attachment to entity
    Frontend->>FeatureFlag: Check IS_ATTACHMENT_MIGRATED
    alt Flag Enabled
        Frontend->>Frontend: Use target{Entity}Id format
    else Flag Disabled
        Frontend->>Frontend: Use {entity}Id format
    end
    Frontend->>Backend: Create attachment with computed field name
    Backend->>Database: Insert attachment record
    Database-->>Frontend: Return created attachment
    
    Note over Frontend,Database: Runtime - Attachment Query
    Frontend->>FeatureFlag: Check IS_ATTACHMENT_MIGRATED
    Frontend->>Frontend: Compute filter field name
    Frontend->>Backend: Query attachments with filter
    Backend->>Database: Execute query with join column
    Database-->>Frontend: Return matching attachments
    Frontend->>Frontend: Apply optimistic filtering
    Frontend->>Frontend: Match morph relation join columns
    Frontend-->>User: Display attachments
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@github-actions
Copy link
Contributor

github-actions bot commented Jan 23, 2026

🚀 Preview Environment Ready!

Your preview environment is available at: http://bore.pub:39070

This environment will automatically shut down when the PR is closed or after 5 hours.

@github-actions
Copy link
Contributor

📊 API Changes Report

GraphQL Schema Changes

GraphQL Schema Changes

[error] Error: Unable to read JSON file: /home/runner/work/twenty/twenty/current-schema-introspection.json: Not valid JSON content
at JsonFileLoader.handleFileContent (/opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/json-file-loader/cjs/index.js:147:19)
at /opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/json-file-loader/cjs/index.js:95:43
at async Promise.all (index 0)
at async JsonFileLoader.load (/opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/json-file-loader/cjs/index.js:88:9)
at async /opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/load/cjs/load-typedefs/load-file.js:15:39
at async Promise.all (index 4)
at async loadFile (/opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/load/cjs/load-typedefs/load-file.js:13:9)
at async /opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/load/cjs/load-typedefs/collect-sources.js:200:25
Error: Unable to read JSON file: /home/runner/work/twenty/twenty/current-schema-introspection.json: Not valid JSON content
at JsonFileLoader.handleFileContent (/opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/json-file-loader/cjs/index.js:147:19)
at /opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/json-file-loader/cjs/index.js:95:43
at async Promise.all (index 0)
at async JsonFileLoader.load (/opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/json-file-loader/cjs/index.js:88:9)
at async /opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/load/cjs/load-typedefs/load-file.js:15:39
at async Promise.all (index 4)
at async loadFile (/opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/load/cjs/load-typedefs/load-file.js:13:9)
at async /opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/load/cjs/load-typedefs/collect-sources.js:200:25
⚠️ Breaking changes or errors detected in GraphQL schema

[error] Error: Unable to read JSON file: /home/runner/work/twenty/twenty/current-schema-introspection.json: Not valid JSON content
    at JsonFileLoader.handleFileContent (/opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/json-file-loader/cjs/index.js:147:19)
    at /opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/json-file-loader/cjs/index.js:95:43
    at async Promise.all (index 0)
    at async JsonFileLoader.load (/opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/json-file-loader/cjs/index.js:88:9)
    at async /opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/load/cjs/load-typedefs/load-file.js:15:39
    at async Promise.all (index 4)
    at async loadFile (/opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/load/cjs/load-typedefs/load-file.js:13:9)
    at async /opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/load/cjs/load-typedefs/collect-sources.js:200:25
Error generating diff

GraphQL Metadata Schema Changes

GraphQL Metadata Schema Changes

[error] Error: Unable to read JSON file: /home/runner/work/twenty/twenty/current-metadata-schema-introspection.json: Not valid JSON content
at JsonFileLoader.handleFileContent (/opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/json-file-loader/cjs/index.js:147:19)
at /opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/json-file-loader/cjs/index.js:95:43
at async Promise.all (index 0)
at async JsonFileLoader.load (/opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/json-file-loader/cjs/index.js:88:9)
at async /opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/load/cjs/load-typedefs/load-file.js:15:39
at async Promise.all (index 4)
at async loadFile (/opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/load/cjs/load-typedefs/load-file.js:13:9)
at async /opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/load/cjs/load-typedefs/collect-sources.js:200:25
Error: Unable to read JSON file: /home/runner/work/twenty/twenty/current-metadata-schema-introspection.json: Not valid JSON content
at JsonFileLoader.handleFileContent (/opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/json-file-loader/cjs/index.js:147:19)
at /opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/json-file-loader/cjs/index.js:95:43
at async Promise.all (index 0)
at async JsonFileLoader.load (/opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/json-file-loader/cjs/index.js:88:9)
at async /opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/load/cjs/load-typedefs/load-file.js:15:39
at async Promise.all (index 4)
at async loadFile (/opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/load/cjs/load-typedefs/load-file.js:13:9)
at async /opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/load/cjs/load-typedefs/collect-sources.js:200:25
⚠️ Breaking changes or errors detected in GraphQL metadata schema

[error] Error: Unable to read JSON file: /home/runner/work/twenty/twenty/current-metadata-schema-introspection.json: Not valid JSON content
    at JsonFileLoader.handleFileContent (/opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/json-file-loader/cjs/index.js:147:19)
    at /opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/json-file-loader/cjs/index.js:95:43
    at async Promise.all (index 0)
    at async JsonFileLoader.load (/opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/json-file-loader/cjs/index.js:88:9)
    at async /opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/load/cjs/load-typedefs/load-file.js:15:39
    at async Promise.all (index 4)
    at async loadFile (/opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/load/cjs/load-typedefs/load-file.js:13:9)
    at async /opt/hostedtoolcache/node/24.13.0/x64/lib/node_modules/@graphql-inspector/cli/node_modules/@graphql-tools/load/cjs/load-typedefs/collect-sources.js:200:25
Error generating diff

REST API Analysis Error

⚠️ Error occurred while analyzing REST API changes

Error Output

REST Metadata API Analysis Error

⚠️ Error occurred while analyzing REST Metadata API changes

Error Output

⚠️ Please review these API changes carefully before merging.

⚠️ Breaking Change Protocol

Breaking changes detected but PR title does not contain "breaking" - CI will pass but action needed.

🔄 Options:

  1. If this IS a breaking change: Add "breaking" to your PR title and add BREAKING CHANGE: to your commit message
  2. If this is NOT a breaking change: The API diff tool may have false positives - please review carefully

For breaking changes, add to commit message:

feat: add new API endpoint

BREAKING CHANGE: removed deprecated field from User schema

@mabdullahabaid mabdullahabaid marked this pull request as draft January 23, 2026 05:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Attachements

2 participants