Maintain INDEX view system side effects on deactivated views (#23590)

# Introduction

Follow-up on
https://github.com/twentyhq/twenty/pull/23585#discussion_r3683701472.

Two INDEX view side-effect handlers bailed out when the view had
`isActive: false`. This drops those gates.

# Why

`isActive: false` on a view has exactly one writer: the delete path,
when `isCallerOverridingEntity` is true
(`from-delete-view-input-to-flat-view-or-throw.util.ts`,
`view.service.ts`). It is not in `FLAT_VIEW_EDITABLE_PROPERTIES`, so
nothing else sets it.

So the flag means "the workspace deleted an engine-owned view, and since
the engine owns the row we deactivate instead of hard-deleting". It is a
workspace override of a row we still own, not a signal the row is gone
(that is `deletedAt`). Which makes it precisely the state where the
engine must keep maintaining its own rows: the row still exists, still
belongs to the engine, and is expected to be consistent whenever the
override is lifted.

Skipping the side effect instead left the view permanently incomplete,
with no repair path.

The gates were inherited from the candidate-view scan removed in the
same commit as these handlers were introduced
(`compute-flat-view-fields-from-fields-widgets.util.ts`, #23081). There,
`!view.isActive` filtered which of many views were candidates.
Transplanted into handlers that resolve *the* one deterministic
engine-owned INDEX view identifier, the same predicate stops meaning "is
this a candidate" and starts meaning "silently skip the system side
effect".

# Changes

- `fieldIndexViewFieldOnCreate`: create the INDEX view field even when
the view is deactivated.
- `objectIndexViewLabelIdentifierOnUpdate`: reconcile the label
identifier view field even when the view is deactivated.

`deletedAt` gates are unchanged in both.

The `should noop when the object has no active INDEX view` spec case is
inverted accordingly.

# Follow-up

Both handlers also drop inactive view fields when computing positions,
while `FlatViewFieldValidatorService` builds its `otherFlatViewFields`
with no `isActive` filter. An inactive view field below all active ones
would make a handler emit a label identifier position the validator then
rejects. Unreachable today (view fields are only ever soft-deleted,
never deactivated), so left out of this PR.
This commit is contained in:
Paul Rastoin
2026-07-30 17:42:30 +02:00
committed by GitHub
parent 550aeafd90
commit bc0ec6b104
3 changed files with 16 additions and 4 deletions
@@ -585,7 +585,7 @@ describe('FieldIndexViewFieldOnCreateSideEffectHandlerService', () => {
expect(result.status).toBe('noop');
});
it('should noop when the object has no active INDEX view', () => {
it('should emit a view field on a deactivated INDEX view', () => {
const result = handler.buildSideEffects(
buildArgs({
triggerFieldMetadata: PRIORITY_FIELD,
@@ -602,7 +602,21 @@ describe('FieldIndexViewFieldOnCreateSideEffectHandlerService', () => {
}),
);
expect(result.status).toBe('noop');
expect(result.status).toBe('success');
if (result.status !== 'success') {
throw new Error('expected success');
}
const viewFields = Object.values(
result.operations.viewField?.flatEntityToCreate ?? {},
);
expect(viewFields).toHaveLength(1);
expect(viewFields[0].viewUniversalIdentifier).toBe(
DERIVED_INDEX_VIEW_UNIVERSAL_IDENTIFIER,
);
expect(viewFields[0].isVisible).toBe(true);
});
});
@@ -183,7 +183,6 @@ export class FieldIndexViewFieldOnCreateSideEffectHandlerService extends Metadat
if (
!isDefined(existingIndexFlatView) ||
!existingIndexFlatView.isActive ||
isDefined(existingIndexFlatView.deletedAt)
) {
return undefined;
@@ -80,7 +80,6 @@ export class ObjectIndexViewLabelIdentifierOnUpdateSideEffectHandlerService exte
if (
!isDefined(indexFlatView) ||
indexFlatView.isSystemSideEffect !== true ||
!indexFlatView.isActive ||
isDefined(indexFlatView.deletedAt)
) {
return { status: 'noop' };