Add is active to overridable entities and deactivation logic for page layouts (#19200)

- Replace soft-deletion (deletedAt) with isActive boolean for
overridable entities (tabs, widgets, viewFieldGroups, viewFields).
Standard entities are deactivated (isActive: false) when removed from
update payloads, while custom entities are hard-deleted.
- When a viewFieldGroup is deactivated/deleted, its viewFields are
reassigned to the next section by position (or null if none remain).
- Add isActive: true filters to viewFieldGroup and viewField API queries
so deactivated entities are excluded from responses.

Next: 
- fields-widget-upsert.service.ts should be refactored a bit
- Add restore logic
This commit is contained in:
Weiko
2026-04-02 16:50:50 +02:00
committed by GitHub
parent 67d34be7c8
commit c1e4756f9c
35 changed files with 392 additions and 286 deletions
@@ -1,5 +1,14 @@
import { isDefined } from '@/utils/validation';
import { computeDiffBetweenObjects } from '../compute-diff-between-objects';
const isEntityIncludedByDeletedAt = (entity: {
deletedAt: string | null;
}): boolean => !isDefined(entity.deletedAt);
const isEntityIncludedByIsActive = (entity: { isActive: boolean }): boolean =>
entity.isActive;
describe('computeDiffBetweenObjects', () => {
it('should return the correct diff', () => {
const existingObjects = [
@@ -15,13 +24,14 @@ describe('computeDiffBetweenObjects', () => {
existingObjects,
receivedObjects,
propertiesToCompare: ['name'],
isEntityIncluded: isEntityIncludedByDeletedAt,
});
expect(diff).toEqual({
toCreate: [{ id: '3', name: 'Object 3' }],
toUpdate: [],
toRestoreAndUpdate: [],
idsToDelete: ['2'],
idsToRemove: ['2'],
});
});
@@ -33,13 +43,14 @@ describe('computeDiffBetweenObjects', () => {
existingObjects,
receivedObjects,
propertiesToCompare: [],
isEntityIncluded: isEntityIncludedByDeletedAt,
});
expect(diff).toEqual({
toCreate: [],
toUpdate: [],
toRestoreAndUpdate: [],
idsToDelete: [],
idsToRemove: [],
});
});
@@ -51,13 +62,14 @@ describe('computeDiffBetweenObjects', () => {
existingObjects,
receivedObjects,
propertiesToCompare: ['name'],
isEntityIncluded: isEntityIncludedByDeletedAt,
});
expect(diff).toEqual({
toCreate: [{ id: '1', name: 'Object 1' }],
toUpdate: [],
toRestoreAndUpdate: [],
idsToDelete: [],
idsToRemove: [],
});
});
@@ -69,13 +81,14 @@ describe('computeDiffBetweenObjects', () => {
existingObjects,
receivedObjects,
propertiesToCompare: ['name'],
isEntityIncluded: isEntityIncludedByDeletedAt,
});
expect(diff).toEqual({
toCreate: [],
toUpdate: [],
toRestoreAndUpdate: [],
idsToDelete: ['1'],
idsToRemove: ['1'],
});
});
@@ -87,17 +100,18 @@ describe('computeDiffBetweenObjects', () => {
existingObjects,
receivedObjects,
propertiesToCompare: ['name'],
isEntityIncluded: isEntityIncludedByDeletedAt,
});
expect(diff).toEqual({
toCreate: [],
toUpdate: [{ id: '1', name: 'Updated Object 1' }],
toRestoreAndUpdate: [],
idsToDelete: [],
idsToRemove: [],
});
});
it('should restore and update deleted objects', () => {
it('should restore and update excluded objects when using deletedAt', () => {
const existingObjects = [
{ id: '1', name: 'Object 1', deletedAt: '2024-01-01' },
];
@@ -107,17 +121,18 @@ describe('computeDiffBetweenObjects', () => {
existingObjects,
receivedObjects,
propertiesToCompare: ['name'],
isEntityIncluded: isEntityIncludedByDeletedAt,
});
expect(diff).toEqual({
toCreate: [],
toUpdate: [],
toRestoreAndUpdate: [{ id: '1', name: 'Restored Object 1' }],
idsToDelete: [],
idsToRemove: [],
});
});
it('should not include deleted objects in idsToDelete', () => {
it('should not include excluded objects in idsToRemove', () => {
const existingObjects = [
{ id: '1', name: 'Object 1', deletedAt: null },
{ id: '2', name: 'Object 2', deletedAt: '2024-01-01' },
@@ -128,13 +143,55 @@ describe('computeDiffBetweenObjects', () => {
existingObjects,
receivedObjects,
propertiesToCompare: ['name'],
isEntityIncluded: isEntityIncludedByDeletedAt,
});
expect(diff).toEqual({
toCreate: [],
toUpdate: [],
toRestoreAndUpdate: [],
idsToDelete: ['1'],
idsToRemove: ['1'],
});
});
it('should restore and update inactive objects when using isActive', () => {
const existingObjects = [{ id: '1', name: 'Object 1', isActive: false }];
const receivedObjects = [{ id: '1', name: 'Restored Object 1' }];
const diff = computeDiffBetweenObjects({
existingObjects,
receivedObjects,
propertiesToCompare: ['name'],
isEntityIncluded: isEntityIncludedByIsActive,
});
expect(diff).toEqual({
toCreate: [],
toUpdate: [],
toRestoreAndUpdate: [{ id: '1', name: 'Restored Object 1' }],
idsToRemove: [],
});
});
it('should not include inactive objects in idsToRemove when using isActive', () => {
const existingObjects = [
{ id: '1', name: 'Object 1', isActive: true },
{ id: '2', name: 'Object 2', isActive: false },
];
const receivedObjects: { id: string; name: string }[] = [];
const diff = computeDiffBetweenObjects({
existingObjects,
receivedObjects,
propertiesToCompare: ['name'],
isEntityIncluded: isEntityIncludedByIsActive,
});
expect(diff).toEqual({
toCreate: [],
toUpdate: [],
toRestoreAndUpdate: [],
idsToRemove: ['1'],
});
});
});