Skip to content

[Requires "warm" cache flush (no immediate downtime before flush)] Migrate viewGroup.fieldMetadataId -> view.mainGroupByFieldMetadataId (1/3)#16206

Merged
charlesBochet merged 27 commits intomainfrom
fix--corrupted-kanban-view-data
Dec 2, 2025
Merged

[Requires "warm" cache flush (no immediate downtime before flush)] Migrate viewGroup.fieldMetadataId -> view.mainGroupByFieldMetadataId (1/3)#16206
charlesBochet merged 27 commits intomainfrom
fix--corrupted-kanban-view-data

Conversation

@ijreilly
Copy link
Copy Markdown
Contributor

@ijreilly ijreilly commented Dec 1, 2025

In this PR (1/3)

  • introduce view.mainGroupByFieldMetadataId as the new reference determining which fieldMetadataId is used in a grouped view, in order to deprecate viewGroup.fieldMetadataId which creates inconsistencies. view.mainGroupByFieldMetadataId is now filled at every view creation, though not in use yet.
  • Introduce a command to backfill view.mainGroupByFieldMetadataId for existing views + delete all viewGroup.fieldMetadataId with a fieldMetadataId that is not view.mainGroupByFieldMetadataId. (It should concern 37 active workspaces)
  • Temporarily disable the option to change a grouped view's fieldMetadataId as for now it creates inconsistencies. This feature can be reintroduced when we have done the full migration.

In a next PR

  • (2/3) use view.mainGroupByFieldMetadataId instead of viewGroup.fieldMetadataId. In FE we may keep viewGroup.fieldMetadataId as a state (TBD). View groups will now be created / deleted as a side effect of view's mainGroupByFieldMetadataId update.
  • (3/3) remove viewGroup.fieldMetadataId

@ijreilly ijreilly changed the title Migrate viewGroup.fieldMetadataId -> view.mainGroupByFieldMetadataId Migrate viewGroup.fieldMetadataId -> view.mainGroupByFieldMetadataId (1/3) Dec 1, 2025
Comment on lines 182 to 187
}

if (type === ViewType.Kanban) {
if (!isDefined(kanbanFieldMetadataId)) {
if (!isDefined(mainGroupByFieldMetadataId)) {
throw new Error('Kanban view must have a kanban field');
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: Duplicating old Kanban views before backfill completes crashes due to mainGroupByFieldMetadataId being null.
Severity: CRITICAL | Confidence: High

🔍 Detailed Analysis

The mainGroupByFieldMetadataId can be null for existing Kanban views after deployment but before the backfill command has completed. When a user attempts to duplicate such an old Kanban view using the 'create-from-current' mode, the sourceView.mainGroupByFieldMetadataId (which is null) is passed. This null value then triggers an error at lines 182-184, if (!isDefined(mainGroupByFieldMetadataId)) { throw new Error('Kanban view must have a kanban field'); }, leading to a crash in the view creation flow. This bug occurs during the critical transition period between deployment and the completion of the backfill operation.

💡 Suggested Fix

Ensure mainGroupByFieldMetadataId is always defined when duplicating Kanban views, possibly by adding a fallback to a default field or by preventing duplication until the backfill is confirmed complete for the sourceView.

🤖 Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location:
packages/twenty-front/src/modules/views/hooks/useCreateViewFromCurrentView.ts#L182-L187

Potential issue: The `mainGroupByFieldMetadataId` can be `null` for existing Kanban
views after deployment but before the backfill command has completed. When a user
attempts to duplicate such an old Kanban view using the 'create-from-current' mode, the
`sourceView.mainGroupByFieldMetadataId` (which is `null`) is passed. This `null` value
then triggers an error at lines 182-184, `if (!isDefined(mainGroupByFieldMetadataId)) {
throw new Error('Kanban view must have a kanban field'); }`, leading to a crash in the
view creation flow. This bug occurs during the critical transition period between
deployment and the completion of the backfill operation.

Did we get this right? 👍 / 👎 to inform future reviews.
Reference ID: 4468079

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No it's not a problem because this reflects viewPickerMainGroupByFieldMetadataIdComponentState which is not nullable. What we should do is actually prevent this from being equal to '', which we will be able to do after we have done the migration

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Dec 1, 2025

Greptile Overview

Greptile Summary

Introduces view.mainGroupByFieldMetadataId as the new source of truth for grouped views to replace the inconsistent viewGroup.fieldMetadataId. The migration includes database schema changes, a backfill command for existing data, and temporary UI restrictions.

Key Changes:

  • Added mainGroupByFieldMetadataId column to view table with foreign key to fieldMetadata
  • Created backfill command that migrates existing views and cleans up inconsistent viewGroups
  • Updated all view creation flows (frontend & backend) to populate the new field
  • Temporarily disabled the "Group by" field selector to prevent new inconsistencies
  • Critical Issue: mainGroupByFieldMetadataId is commented out in the GraphQL fragment (viewFragment.ts:27), preventing the frontend from reading this field despite being referenced in multiple locations

Migration Strategy:
The PR follows a phased approach: (1) introduce field and populate at creation, (2) backfill existing data, (3) temporarily disable field changes. A future PR will switch to using mainGroupByFieldMetadataId everywhere and remove viewGroup.fieldMetadataId.

Issues Found:

  • Module naming error: V1_12_UpgradeVersionCommandModule should be V1_13_UpgradeVersionCommandModule
  • GraphQL fragment field is commented out but code expects it, creating potential runtime errors

Confidence Score: 2/5

  • This PR has critical issues that will cause runtime errors and should not be merged as-is
  • The commented-out GraphQL field creates a disconnect between what the backend provides and what the frontend expects, causing mainGroupByFieldMetadataId to be undefined in all queries. Additionally, the module naming error indicates insufficient testing. While the migration strategy is sound, these implementation issues need resolution before deployment.
  • Pay close attention to viewFragment.ts (GraphQL field is commented out) and 1-13-upgrade-version-command.module.ts (incorrect module name)

Important Files Changed

File Analysis

Filename Score Overview
packages/twenty-server/src/database/commands/upgrade-version-command/1-13/1-13-upgrade-version-command.module.ts 3/5 Module name is incorrect (V1_12 instead of V1_13)
packages/twenty-front/src/modules/views/graphql/fragments/viewFragment.ts 2/5 mainGroupByFieldMetadataId is commented out, which prevents frontend from reading this field
packages/twenty-server/src/database/commands/upgrade-version-command/1-13/1-13-backfill-view-main-group-by-field-metadata-id.command.ts 4/5 Backfill command handles inconsistent viewGroups by selecting most numerous fieldMetadataId, then cleans up inconsistent records
packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownRecordGroupsContent.tsx 4/5 Disabled "Group by" field selection for non-INDEX views (temporarily prevents changing grouped view's field)

Sequence Diagram

sequenceDiagram
    participant User
    participant Frontend
    participant GraphQL
    participant Backend
    participant Database

    Note over Database: Migration Phase
    Backend->>Database: Run migration 1764300000000
    Database-->>Backend: Add mainGroupByFieldMetadataId column
    Backend->>Database: Run migration 1764300000001
    Database-->>Backend: Add foreign key constraint
    Backend->>Database: Run backfill command
    Database-->>Backend: Populate mainGroupByFieldMetadataId from viewGroups
    Backend->>Database: Delete inconsistent viewGroups
    
    Note over User,Database: View Creation Flow
    User->>Frontend: Create new Kanban view
    Frontend->>Frontend: Select mainGroupByFieldMetadataId
    Frontend->>GraphQL: createCoreView mutation
    GraphQL->>Backend: CreateViewInput with mainGroupByFieldMetadataId
    Backend->>Database: INSERT view with mainGroupByFieldMetadataId
    Backend->>Database: CREATE viewGroups with same fieldMetadataId
    Database-->>Backend: View created
    Backend-->>GraphQL: Return view data
    GraphQL-->>Frontend: View data (mainGroupByFieldMetadataId commented out)
    Frontend->>Frontend: mainGroupByFieldMetadataId is undefined
    
    Note over User,Database: Temporary Restriction
    User->>Frontend: Try to change Group By field
    Frontend->>Frontend: "Group by" menu item disabled
    Frontend-->>User: Cannot change field (prevents inconsistencies)
Loading

Copy link
Copy Markdown
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.

31 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

openRecordIn
kanbanAggregateOperation
kanbanAggregateOperationFieldMetadataId
# mainGroupByFieldMetadataId
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

logic: The field is commented out but still referenced in multiple places (convertCoreViewToView.ts:41, getObjectMetadataItemViews.ts:19, useCreateViewFromCurrentView.ts:136-137). The codebase expects this field to exist but will receive undefined from GraphQL queries, which could cause runtime issues.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/twenty-front/src/modules/views/graphql/fragments/viewFragment.ts
Line: 27:27

Comment:
**logic:** The field is commented out but still referenced in multiple places (`convertCoreViewToView.ts:41`, `getObjectMetadataItemViews.ts:19`, `useCreateViewFromCurrentView.ts:136-137`). The codebase expects this field to exist but will receive `undefined` from GraphQL queries, which could cause runtime issues.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It won't because it accept null values for mainGroupByFieldMetadataId. I want to avoid querying it to avoid downtime until we have cleared the cache and as this will have no effect for now

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Dec 1, 2025

🚀 Preview Environment Ready!

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

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

<MenuItem
focused={selectedItemId === 'GroupBy'}
onClick={() => onContentChange('recordGroupFields')}
disabled
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

temporarily disabling this group by field switch on an existing view


const recordGroupDefinitionsFromViewGroups = viewGroups
.map((viewGroup) => {
if (viewGroup.fieldMetadataId !== selectFieldMetadataItem.id) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

temporary, will be removed, but helps fix the display issues we currently have on views where some viewGroup are on a different fieldMetadataId

@@ -0,0 +1,20 @@
import { type MigrationInterface, type QueryRunner } from 'typeorm';

export class AddMainGroupByFieldMetadataIdForeignKey1764300000001
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

generate with typeorm

description:
'Backfill mainGroupByFieldMetadataId on views and clean up inconsistent viewGroups',
})
export class BackfillViewMainGroupByFieldMetadataIdCommand extends MigrationCommandRunner {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

extends ActiveOrSuspendedCommandRunner

return;
}

await updateView({
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this is required to make sure that newly created grouped table views (not kanban) have mainGroupByFieldMetadataId (silently) filled

viewPickerVisibilityCallbackState,
);

const shouldCopyFiltersAndSortsAndAggregate =
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

const isCreateFromCurrent =

type,
kanbanFieldMetadataId,
mainGroupByFieldMetadataId:
type === ViewType.Kanban ? mainGroupByFieldMetadataId : null,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

use isCreateFromCurrent

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

actually oK for this PR

@ijreilly ijreilly changed the title Migrate viewGroup.fieldMetadataId -> view.mainGroupByFieldMetadataId (1/3) [Requires "warm" cache flush (no immediate downtime before flush)] Migrate viewGroup.fieldMetadataId -> view.mainGroupByFieldMetadataId (1/3) Dec 2, 2025
@ijreilly ijreilly enabled auto-merge (squash) December 2, 2025 15:15
@charlesBochet charlesBochet merged commit 77409b6 into main Dec 2, 2025
64 of 66 checks passed
@charlesBochet charlesBochet deleted the fix--corrupted-kanban-view-data branch December 2, 2025 15:31
@twenty-eng-sync
Copy link
Copy Markdown

Hey @ijreilly! After you've done the QA of your Pull Request, you can mark it as done here. Thank you!

ijreilly added a commit that referenced this pull request Dec 5, 2025
…ByFieldMetadataId (2/3) (#16277)

Should be merged once #16206 has
been released + command run to prod

In this PR
- Remove usage of viewGroup.fieldMetadataId, both in BE and FE states. 
- But we still need to properly populate it until we fully remove
viewGroup.fieldMetadataId from db and ORM entity (upcoming 3rd PR out of
3). fieldMetadataId was removed from CoreViewGroup type and
CreateViewGroupInput and is determined BE-side based on the associated
view's mainGroupByFieldMetadataId. **I expect this means a downtime on
viewGroup creation, until both FE and BE are deployed and cache is
flushed.** This seems acceptable to me as it only regards viewGroup
creation.
    - this information is replaced by view.mainGroupByFieldMetadataID
- Handle view group creation, update and deletion in the BE as a
side-effect of a view creation, update or deletion. Optimistic effects
are still used
- Add validation at view creation or update regarding
mainGroupByFieldMetadata

Left to do in 3rd PR
- Remove viewGroup.fieldMetadataId from db and ORM entity
- Restore feature allowing to update an existing grouped view's group by
field (already OK on BE side but need to rebuild FE optimistic)
NotYen pushed a commit to NotYen/twenty-ym that referenced this pull request Jan 15, 2026
- 新增 mainGroupByFieldMetadataId 欄位到 View entity
- 新增資料庫遷移 1764680275312-addMainGroupByFieldMetadataId
- 補齊 usePersistViewGroup.ts 的 createViewGroups、deleteViewGroups、destroyViewGroups 函數
- 啟用 viewFragment.ts 的 mainGroupByFieldMetadataId 查詢
- 新增 isPersistingViewFieldsState 狀態
- 更新 ViewPicker 相關組件支援 mainGroupByFieldMetadataId
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants