Add weekly layout to record calendar (#22819)
## Summary - Add a week layout to record calendar views and persist the selected layout. - Render `DATE` calendars as an all-day week and `DATE_TIME` calendars as an hourly week. - Add an optional end date field across calendar configuration, metadata, persistence, and complete-view upserts. - Use configured end values for ranged and multi-day events, with a one-hour fallback when a `DATE_TIME` end is absent or invalid. - Keep calendar cards consistent with the existing compact view, including checkbox selection and whole-card record opening. - Gate the weekly layout and end-date behavior behind the public Labs `IS_CALENDAR_WEEK_VIEW_ENABLED` workspace feature flag. ## Week interactions - Show overlapping timed events side by side and cap the visible records at two per day. - Display start and end times on timed cards, enforce a readable 30-minute minimum height, and keep today’s text contrast stronger. - Drag timed events between days and times with 30-minute snapping while preserving their duration, including zero-duration events. - Show a create button when hovering a 30-minute slot; keyboard users can focus a day, move the slot with the arrow keys, and reach the same contextual action. - Initialize new records with the selected slot time and a compatible writable end value one hour later. - Show the workspace time zone and current-time indicator in timed weeks; date-only weeks keep the all-day section without an hourly grid. ## Configuration and data loading - Only allow end fields that match the start field type, and prevent selecting the same field for both boundaries. - Load records whose ranges overlap the visible period so month and week layouts display the same relevant records. - Resolve and persist calendar end fields when updating existing views through `upsert_complete_view`. - Fall back to Month and ignore the configured end field while the flag is disabled, without overwriting either persisted setting, so re-enabling restores the previous configuration. - Expose the flag in Labs and keep it default-off for workspaces without a stored value; enable it in the development seeder. <img width="1285" height="808" alt="Screenshot 2026-07-15 at 15 50 17" src="https://github.com/user-attachments/assets/b7e3f7f1-ca77-492f-8cce-cca186ebca0b" />
This commit is contained in:
+3
@@ -249,6 +249,9 @@ export class FieldMetadataEntity<
|
||||
@OneToMany(() => ViewEntity, (view) => view.calendarFieldMetadata)
|
||||
calendarViews: Relation<ViewEntity[]>;
|
||||
|
||||
@OneToMany(() => ViewEntity, (view) => view.calendarEndFieldMetadata)
|
||||
calendarEndViews: Relation<ViewEntity[]>;
|
||||
|
||||
@OneToMany(() => ViewEntity, (view) => view.mainGroupByFieldMetadata)
|
||||
mainGroupByFieldMetadataViews: Relation<ViewEntity[]>;
|
||||
|
||||
|
||||
+1
@@ -343,6 +343,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma
|
||||
"anyFieldFilterValue",
|
||||
"calendarLayout",
|
||||
"calendarFieldMetadataUniversalIdentifier",
|
||||
"calendarEndFieldMetadataUniversalIdentifier",
|
||||
"visibility",
|
||||
"mainGroupByFieldMetadataUniversalIdentifier",
|
||||
"shouldHideEmptyGroups",
|
||||
|
||||
+1
@@ -69,6 +69,7 @@ exports[`registry-derived override property maps derives the overridable propert
|
||||
"anyFieldFilterValue",
|
||||
"calendarLayout",
|
||||
"calendarFieldMetadataId",
|
||||
"calendarEndFieldMetadataId",
|
||||
"visibility",
|
||||
"mainGroupByFieldMetadataId",
|
||||
"shouldHideEmptyGroups",
|
||||
|
||||
+6
@@ -381,6 +381,12 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
||||
universalProperty: 'calendarFieldMetadataUniversalIdentifier',
|
||||
isOverridable: true,
|
||||
},
|
||||
calendarEndFieldMetadataId: {
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
universalProperty: 'calendarEndFieldMetadataUniversalIdentifier',
|
||||
isOverridable: true,
|
||||
},
|
||||
visibility: {
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
|
||||
+3
@@ -98,6 +98,9 @@ export const ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY = {
|
||||
calendarFieldMetadata: {
|
||||
foreignKey: 'calendarFieldMetadataId',
|
||||
},
|
||||
calendarEndFieldMetadata: {
|
||||
foreignKey: 'calendarEndFieldMetadataId',
|
||||
},
|
||||
kanbanAggregateOperationFieldMetadata: {
|
||||
foreignKey: 'kanbanAggregateOperationFieldMetadataId',
|
||||
},
|
||||
|
||||
+7
@@ -174,6 +174,13 @@ export const ALL_MANY_TO_ONE_METADATA_RELATIONS = {
|
||||
isNullable: true,
|
||||
universalForeignKey: 'calendarFieldMetadataUniversalIdentifier',
|
||||
},
|
||||
calendarEndFieldMetadata: {
|
||||
metadataName: 'fieldMetadata',
|
||||
foreignKey: 'calendarEndFieldMetadataId',
|
||||
inverseOneToManyProperty: 'calendarEndViews',
|
||||
isNullable: true,
|
||||
universalForeignKey: 'calendarEndFieldMetadataUniversalIdentifier',
|
||||
},
|
||||
kanbanAggregateOperationFieldMetadata: {
|
||||
metadataName: 'fieldMetadata',
|
||||
foreignKey: 'kanbanAggregateOperationFieldMetadataId',
|
||||
|
||||
+6
@@ -71,6 +71,12 @@ export const ALL_ONE_TO_MANY_METADATA_RELATIONS = {
|
||||
universalFlatEntityForeignKeyAggregator:
|
||||
'calendarViewUniversalIdentifiers',
|
||||
},
|
||||
calendarEndViews: {
|
||||
metadataName: 'view',
|
||||
flatEntityForeignKeyAggregator: 'calendarEndViewIds',
|
||||
universalFlatEntityForeignKeyAggregator:
|
||||
'calendarEndViewUniversalIdentifiers',
|
||||
},
|
||||
mainGroupByFieldMetadataViews: {
|
||||
metadataName: 'view',
|
||||
flatEntityForeignKeyAggregator: 'mainGroupByFieldMetadataViewIds',
|
||||
|
||||
+3
-1
@@ -17,7 +17,7 @@ type FieldMetadataOneToManySyncableRelations =
|
||||
type Assertions = [
|
||||
// FieldMetadataEntity OneToMany relations (all targets):
|
||||
// - indexFieldMetadatas, fieldPermissions, viewFields, viewFilters,
|
||||
// - kanbanAggregateOperationViews, calendarViews, mainGroupByFieldMetadataViews,
|
||||
// - kanbanAggregateOperationViews, calendarViews, calendarEndViews, mainGroupByFieldMetadataViews,
|
||||
// - viewSorts, searchFieldMetadatas
|
||||
Expect<
|
||||
Equal<
|
||||
@@ -28,6 +28,7 @@ type Assertions = [
|
||||
| 'viewFilters'
|
||||
| 'kanbanAggregateOperationViews'
|
||||
| 'calendarViews'
|
||||
| 'calendarEndViews'
|
||||
| 'mainGroupByFieldMetadataViews'
|
||||
| 'viewSorts'
|
||||
| 'searchFieldMetadatas'
|
||||
@@ -43,6 +44,7 @@ type Assertions = [
|
||||
| 'viewFilters'
|
||||
| 'kanbanAggregateOperationViews'
|
||||
| 'calendarViews'
|
||||
| 'calendarEndViews'
|
||||
| 'mainGroupByFieldMetadataViews'
|
||||
| 'viewSorts'
|
||||
| 'searchFieldMetadatas'
|
||||
|
||||
+2
-1
@@ -10,7 +10,7 @@ type FieldMetadataRelatedProperties =
|
||||
type Assertions = [
|
||||
// FieldMetadataEntity has both ManyToOne and OneToMany relations
|
||||
// ManyToOne: object, workspace, application, relationTargetFieldMetadata, relationTargetObjectMetadata
|
||||
// OneToMany: indexFieldMetadatas, fieldPermissions, viewFields, viewFilters, kanbanAggregateOperationViews, calendarViews, mainGroupByFieldMetadataViews, viewSorts, searchFieldMetadatas
|
||||
// OneToMany: indexFieldMetadatas, fieldPermissions, viewFields, viewFilters, kanbanAggregateOperationViews, calendarViews, calendarEndViews, mainGroupByFieldMetadataViews, viewSorts, searchFieldMetadatas
|
||||
Expect<
|
||||
Equal<
|
||||
FieldMetadataRelatedProperties,
|
||||
@@ -25,6 +25,7 @@ type Assertions = [
|
||||
| 'viewFilters'
|
||||
| 'kanbanAggregateOperationViews'
|
||||
| 'calendarViews'
|
||||
| 'calendarEndViews'
|
||||
| 'mainGroupByFieldMetadataViews'
|
||||
| 'viewSorts'
|
||||
| 'searchFieldMetadatas'
|
||||
|
||||
+4
@@ -57,6 +57,8 @@ describe('addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow',
|
||||
applicationId,
|
||||
calendarFieldMetadataId: mockFieldMetadata.id,
|
||||
calendarFieldMetadataUniversalIdentifier: fieldUniversalIdentifier,
|
||||
calendarEndFieldMetadataId: mockFieldMetadata.id,
|
||||
calendarEndFieldMetadataUniversalIdentifier: fieldUniversalIdentifier,
|
||||
};
|
||||
|
||||
const flatEntityAndRelatedMapsToMutate: MetadataFlatEntityAndRelatedFlatEntityMaps<'view'> =
|
||||
@@ -103,6 +105,8 @@ describe('addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow',
|
||||
).toMatchObject<Partial<FlatFieldMetadata>>({
|
||||
calendarViewIds: [mockView.id],
|
||||
calendarViewUniversalIdentifiers: [viewUniversalIdentifier],
|
||||
calendarEndViewIds: [mockView.id],
|
||||
calendarEndViewUniversalIdentifiers: [viewUniversalIdentifier],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+6
@@ -43,6 +43,8 @@ describe('deleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOrThr
|
||||
workspaceId,
|
||||
calendarViewIds: [viewId],
|
||||
calendarViewUniversalIdentifiers: [viewUniversalIdentifier],
|
||||
calendarEndViewIds: [viewId],
|
||||
calendarEndViewUniversalIdentifiers: [viewUniversalIdentifier],
|
||||
mainGroupByFieldMetadataViewIds: [],
|
||||
applicationId,
|
||||
});
|
||||
@@ -58,6 +60,8 @@ describe('deleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOrThr
|
||||
workspaceId,
|
||||
calendarFieldMetadataId: mockFieldMetadata.id,
|
||||
calendarFieldMetadataUniversalIdentifier: fieldUniversalIdentifier,
|
||||
calendarEndFieldMetadataId: mockFieldMetadata.id,
|
||||
calendarEndFieldMetadataUniversalIdentifier: fieldUniversalIdentifier,
|
||||
createdAt: new Date('2024-01-01').toISOString(),
|
||||
updatedAt: new Date('2024-01-01').toISOString(),
|
||||
icon: 'icon',
|
||||
@@ -114,6 +118,8 @@ describe('deleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOrThr
|
||||
).toMatchObject<Partial<FlatFieldMetadata>>({
|
||||
calendarViewIds: [],
|
||||
calendarViewUniversalIdentifiers: [],
|
||||
calendarEndViewIds: [],
|
||||
calendarEndViewUniversalIdentifiers: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+2
@@ -17,6 +17,7 @@ export const getFlatFieldMetadataMock = <T extends FieldMetadataType>(
|
||||
|
||||
return {
|
||||
calendarViewIds: [],
|
||||
calendarEndViewIds: [],
|
||||
viewFilterIds: [],
|
||||
kanbanAggregateOperationViewIds: [],
|
||||
viewFieldIds: [],
|
||||
@@ -54,6 +55,7 @@ export const getFlatFieldMetadataMock = <T extends FieldMetadataType>(
|
||||
fieldPermissionUniversalIdentifiers: [],
|
||||
kanbanAggregateOperationViewUniversalIdentifiers: [],
|
||||
calendarViewUniversalIdentifiers: [],
|
||||
calendarEndViewUniversalIdentifiers: [],
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
viewSortIds: [],
|
||||
viewSortUniversalIdentifiers: [],
|
||||
|
||||
+4
@@ -117,6 +117,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
|
||||
"flatFieldMetadatas": [
|
||||
{
|
||||
"applicationUniversalIdentifier": Any<String>,
|
||||
"calendarEndViewUniversalIdentifiers": [],
|
||||
"calendarViewUniversalIdentifiers": [],
|
||||
"createdAt": Any<String>,
|
||||
"defaultValue": null,
|
||||
@@ -153,6 +154,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
|
||||
},
|
||||
{
|
||||
"applicationUniversalIdentifier": Any<String>,
|
||||
"calendarEndViewUniversalIdentifiers": [],
|
||||
"calendarViewUniversalIdentifiers": [],
|
||||
"createdAt": Any<String>,
|
||||
"defaultValue": null,
|
||||
@@ -191,6 +193,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
|
||||
},
|
||||
{
|
||||
"applicationUniversalIdentifier": Any<String>,
|
||||
"calendarEndViewUniversalIdentifiers": [],
|
||||
"calendarViewUniversalIdentifiers": [],
|
||||
"createdAt": Any<String>,
|
||||
"defaultValue": null,
|
||||
@@ -227,6 +230,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
|
||||
},
|
||||
{
|
||||
"applicationUniversalIdentifier": Any<String>,
|
||||
"calendarEndViewUniversalIdentifiers": [],
|
||||
"calendarViewUniversalIdentifiers": [],
|
||||
"createdAt": Any<String>,
|
||||
"defaultValue": null,
|
||||
|
||||
+8
@@ -108,6 +108,7 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
|
||||
'universalIdentifier',
|
||||
'kanbanAggregateOperationFieldMetadataId',
|
||||
'calendarFieldMetadataId',
|
||||
'calendarEndFieldMetadataId',
|
||||
'mainGroupByFieldMetadataId',
|
||||
],
|
||||
withDeleted: true,
|
||||
@@ -121,6 +122,7 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
|
||||
viewFieldsByFieldId,
|
||||
viewFiltersByFieldId,
|
||||
calendarViewsByFieldId,
|
||||
calendarEndViewsByFieldId,
|
||||
kanbanViewsByFieldId,
|
||||
mainGroupByFieldMetadataViewsByFieldId,
|
||||
viewSortsByFieldId,
|
||||
@@ -139,6 +141,10 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
|
||||
entities: views,
|
||||
foreignKey: 'calendarFieldMetadataId',
|
||||
},
|
||||
{
|
||||
entities: views,
|
||||
foreignKey: 'calendarEndFieldMetadataId',
|
||||
},
|
||||
{
|
||||
entities: views,
|
||||
foreignKey: 'kanbanAggregateOperationFieldMetadataId',
|
||||
@@ -180,6 +186,8 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
|
||||
kanbanViewsByFieldId.get(fieldMetadataEntity.id) || [],
|
||||
calendarViews:
|
||||
calendarViewsByFieldId.get(fieldMetadataEntity.id) || [],
|
||||
calendarEndViews:
|
||||
calendarEndViewsByFieldId.get(fieldMetadataEntity.id) || [],
|
||||
mainGroupByFieldMetadataViews:
|
||||
mainGroupByFieldMetadataViewsByFieldId.get(
|
||||
fieldMetadataEntity.id,
|
||||
|
||||
+1
@@ -30,6 +30,7 @@ type OneToManyRelationIdArrays = {
|
||||
viewFilterIds: string[];
|
||||
kanbanAggregateOperationViewIds: string[];
|
||||
calendarViewIds: string[];
|
||||
calendarEndViewIds: string[];
|
||||
mainGroupByFieldMetadataViewIds: string[];
|
||||
};
|
||||
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
|
||||
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
|
||||
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
|
||||
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
|
||||
import { handleFieldMetadataDeactivationSideEffects } from 'src/engine/metadata-modules/flat-field-metadata/utils/handle-field-metadata-deactivation-side-effects.util';
|
||||
|
||||
const getFlatView = (id: string): FlatView =>
|
||||
({
|
||||
id,
|
||||
universalIdentifier: `${id}-universal-identifier`,
|
||||
applicationId: 'application-id',
|
||||
calendarEndFieldMetadataId: 'calendar-end-field-id',
|
||||
calendarEndFieldMetadataUniversalIdentifier:
|
||||
'calendar-end-field-universal-identifier',
|
||||
}) as FlatView;
|
||||
|
||||
describe('handleFieldMetadataDeactivationSideEffects', () => {
|
||||
const getEmptyRelatedMaps = () => ({
|
||||
flatViewFieldMaps: createEmptyFlatEntityMaps(),
|
||||
flatViewFilterMaps: createEmptyFlatEntityMaps(),
|
||||
flatViewGroupMaps: createEmptyFlatEntityMaps(),
|
||||
});
|
||||
|
||||
it('clears an optional calendar end field without deleting the view', () => {
|
||||
const view = getFlatView('calendar-view-id');
|
||||
const fromFlatFieldMetadata = getFlatFieldMetadataMock({
|
||||
id: 'calendar-end-field-id',
|
||||
objectMetadataId: 'object-id',
|
||||
universalIdentifier: 'calendar-end-field-universal-identifier',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
calendarEndViewIds: [view.id],
|
||||
});
|
||||
|
||||
const result = handleFieldMetadataDeactivationSideEffects({
|
||||
fromFlatFieldMetadata,
|
||||
toFlatFieldMetadata: {
|
||||
...fromFlatFieldMetadata,
|
||||
isActive: false,
|
||||
},
|
||||
flatViewMaps: addFlatEntityToFlatEntityMapsOrThrow({
|
||||
flatEntity: view,
|
||||
flatEntityMaps: createEmptyFlatEntityMaps(),
|
||||
}),
|
||||
...getEmptyRelatedMaps(),
|
||||
});
|
||||
|
||||
expect(result.flatViewsToDelete).toEqual([]);
|
||||
expect(result.flatViewsToUpdate).toEqual([
|
||||
expect.objectContaining({
|
||||
id: view.id,
|
||||
calendarEndFieldMetadataId: null,
|
||||
calendarEndFieldMetadataUniversalIdentifier: null,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('deletes the view when the deactivated field is both its start and end field', () => {
|
||||
const view = getFlatView('calendar-view-id');
|
||||
const fromFlatFieldMetadata = getFlatFieldMetadataMock({
|
||||
id: 'calendar-field-id',
|
||||
objectMetadataId: 'object-id',
|
||||
universalIdentifier: 'calendar-field-universal-identifier',
|
||||
type: FieldMetadataType.DATE,
|
||||
calendarViewIds: [view.id],
|
||||
calendarEndViewIds: [view.id],
|
||||
});
|
||||
|
||||
const result = handleFieldMetadataDeactivationSideEffects({
|
||||
fromFlatFieldMetadata,
|
||||
toFlatFieldMetadata: {
|
||||
...fromFlatFieldMetadata,
|
||||
isActive: false,
|
||||
},
|
||||
flatViewMaps: addFlatEntityToFlatEntityMapsOrThrow({
|
||||
flatEntity: view,
|
||||
flatEntityMaps: createEmptyFlatEntityMaps(),
|
||||
}),
|
||||
...getEmptyRelatedMaps(),
|
||||
});
|
||||
|
||||
expect(result.flatViewsToDelete).toEqual([view]);
|
||||
expect(result.flatViewsToUpdate).toEqual([]);
|
||||
});
|
||||
});
|
||||
+7
@@ -49,6 +49,9 @@ export const fromFieldMetadataEntityToFlatFieldMetadata = (
|
||||
kanbanAggregateOperationViewIds:
|
||||
fieldMetadataEntity.kanbanAggregateOperationViews.map(({ id }) => id),
|
||||
calendarViewIds: fieldMetadataEntity.calendarViews.map(({ id }) => id),
|
||||
calendarEndViewIds: fieldMetadataEntity.calendarEndViews.map(
|
||||
({ id }) => id,
|
||||
),
|
||||
mainGroupByFieldMetadataViewIds:
|
||||
fieldMetadataEntity.mainGroupByFieldMetadataViews?.map(({ id }) => id) ??
|
||||
[],
|
||||
@@ -69,6 +72,10 @@ export const fromFieldMetadataEntityToFlatFieldMetadata = (
|
||||
calendarViewUniversalIdentifiers: fieldMetadataEntity.calendarViews.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
),
|
||||
calendarEndViewUniversalIdentifiers:
|
||||
fieldMetadataEntity.calendarEndViews.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
),
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers:
|
||||
fieldMetadataEntity.mainGroupByFieldMetadataViews?.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
|
||||
+1
@@ -74,6 +74,7 @@ export const getDefaultFlatFieldMetadata = ({
|
||||
fieldPermissionUniversalIdentifiers: [],
|
||||
kanbanAggregateOperationViewUniversalIdentifiers: [],
|
||||
calendarViewUniversalIdentifiers: [],
|
||||
calendarEndViewUniversalIdentifiers: [],
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
universalSettings: settings ?? null,
|
||||
viewSortUniversalIdentifiers: [],
|
||||
|
||||
+25
-10
@@ -85,19 +85,34 @@ export const handleFieldMetadataDeactivationSideEffects = ({
|
||||
flatEntityMaps: flatViewMaps,
|
||||
});
|
||||
|
||||
const viewIdsToUpdate =
|
||||
fromFlatFieldMetadata.kanbanAggregateOperationViewIds.filter(
|
||||
(viewId) => !viewIdsToDelete.includes(viewId),
|
||||
);
|
||||
const kanbanAggregateOperationViewIds = new Set(
|
||||
fromFlatFieldMetadata.kanbanAggregateOperationViewIds,
|
||||
);
|
||||
const calendarEndViewIds = new Set(fromFlatFieldMetadata.calendarEndViewIds);
|
||||
const viewIdsToUpdate = [
|
||||
...new Set([...kanbanAggregateOperationViewIds, ...calendarEndViewIds]),
|
||||
].filter((viewId) => !viewIdsToDelete.includes(viewId));
|
||||
const flatViewsToUpdate = findManyFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityIds: viewIdsToUpdate,
|
||||
flatEntityMaps: flatViewMaps,
|
||||
}).map((flatView) => ({
|
||||
...flatView,
|
||||
kanbanAggregateOperation: null,
|
||||
kanbanAggregateOperationFieldMetadataId: null,
|
||||
kanbanAggregateOperationFieldMetadataUniversalIdentifier: null,
|
||||
}));
|
||||
}).map((flatView) => {
|
||||
const shouldClearKanbanAggregateOperation =
|
||||
kanbanAggregateOperationViewIds.has(flatView.id);
|
||||
const shouldClearCalendarEndField = calendarEndViewIds.has(flatView.id);
|
||||
|
||||
return {
|
||||
...flatView,
|
||||
...(shouldClearKanbanAggregateOperation && {
|
||||
kanbanAggregateOperation: null,
|
||||
kanbanAggregateOperationFieldMetadataId: null,
|
||||
kanbanAggregateOperationFieldMetadataUniversalIdentifier: null,
|
||||
}),
|
||||
...(shouldClearCalendarEndField && {
|
||||
calendarEndFieldMetadataId: null,
|
||||
calendarEndFieldMetadataUniversalIdentifier: null,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
flatViewsToUpdate,
|
||||
|
||||
+1
@@ -12,6 +12,7 @@ export const FLAT_VIEW_EDITABLE_PROPERTIES = [
|
||||
'anyFieldFilterValue',
|
||||
'calendarLayout',
|
||||
'calendarFieldMetadataId',
|
||||
'calendarEndFieldMetadataId',
|
||||
'visibility',
|
||||
'mainGroupByFieldMetadataId',
|
||||
'shouldHideEmptyGroups',
|
||||
|
||||
+3
@@ -45,6 +45,7 @@ export const fromCreateViewInputToFlatViewToCreate = ({
|
||||
const {
|
||||
objectMetadataUniversalIdentifier,
|
||||
calendarFieldMetadataUniversalIdentifier,
|
||||
calendarEndFieldMetadataUniversalIdentifier,
|
||||
kanbanAggregateOperationFieldMetadataUniversalIdentifier,
|
||||
mainGroupByFieldMetadataUniversalIdentifier,
|
||||
} = resolveEntityRelationUniversalIdentifiers({
|
||||
@@ -52,6 +53,7 @@ export const fromCreateViewInputToFlatViewToCreate = ({
|
||||
foreignKeyValues: {
|
||||
objectMetadataId,
|
||||
calendarFieldMetadataId: createViewInput.calendarFieldMetadataId,
|
||||
calendarEndFieldMetadataId: createViewInput.calendarEndFieldMetadataId,
|
||||
kanbanAggregateOperationFieldMetadataId:
|
||||
createViewInput.kanbanAggregateOperationFieldMetadataId,
|
||||
mainGroupByFieldMetadataId: createViewInput.mainGroupByFieldMetadataId,
|
||||
@@ -72,6 +74,7 @@ export const fromCreateViewInputToFlatViewToCreate = ({
|
||||
isCustom: true,
|
||||
anyFieldFilterValue: createViewInput.anyFieldFilterValue ?? null,
|
||||
calendarFieldMetadataUniversalIdentifier,
|
||||
calendarEndFieldMetadataUniversalIdentifier,
|
||||
calendarLayout: createViewInput.calendarLayout ?? null,
|
||||
icon: createViewInput.icon,
|
||||
isCompact: createViewInput.isCompact ?? false,
|
||||
|
||||
+14
@@ -129,6 +129,20 @@ export const fromUpdateViewInputToFlatViewToUpdateOrThrow = ({
|
||||
calendarFieldMetadataUniversalIdentifier;
|
||||
}
|
||||
|
||||
if (updatedEditableProperties.calendarEndFieldMetadataId !== undefined) {
|
||||
const { calendarEndFieldMetadataUniversalIdentifier } =
|
||||
resolveEntityRelationUniversalIdentifiers({
|
||||
metadataName: 'view',
|
||||
foreignKeyValues: {
|
||||
calendarEndFieldMetadataId: mergedRecord.calendarEndFieldMetadataId,
|
||||
},
|
||||
flatEntityMaps: { flatFieldMetadataMaps },
|
||||
});
|
||||
|
||||
flatViewToUpdate.calendarEndFieldMetadataUniversalIdentifier =
|
||||
calendarEndFieldMetadataUniversalIdentifier;
|
||||
}
|
||||
|
||||
if (updatedEditableProperties.mainGroupByFieldMetadataId !== undefined) {
|
||||
const { mainGroupByFieldMetadataUniversalIdentifier } =
|
||||
resolveEntityRelationUniversalIdentifiers({
|
||||
|
||||
+2
@@ -13,6 +13,7 @@ type UniversalViewOverrides =
|
||||
const VIEW_OVERRIDES_FIELD_METADATA_FOREIGN_KEYS = [
|
||||
'kanbanAggregateOperationFieldMetadataId',
|
||||
'calendarFieldMetadataId',
|
||||
'calendarEndFieldMetadataId',
|
||||
'mainGroupByFieldMetadataId',
|
||||
] as const;
|
||||
|
||||
@@ -39,6 +40,7 @@ export const fromViewOverridesToUniversalOverrides = ({
|
||||
const {
|
||||
kanbanAggregateOperationFieldMetadataId: _kanban,
|
||||
calendarFieldMetadataId: _calendar,
|
||||
calendarEndFieldMetadataId: _calendarEnd,
|
||||
mainGroupByFieldMetadataId: _mainGroupBy,
|
||||
...scalarOverrides
|
||||
} = overrides;
|
||||
|
||||
+8
@@ -37,6 +37,7 @@ const PARTIAL_ID_FIELD = {
|
||||
viewFieldUniversalIdentifiers: [],
|
||||
kanbanAggregateOperationViewUniversalIdentifiers: [],
|
||||
calendarViewUniversalIdentifiers: [],
|
||||
calendarEndViewUniversalIdentifiers: [],
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
fieldPermissionUniversalIdentifiers: [],
|
||||
viewSortUniversalIdentifiers: [],
|
||||
@@ -67,6 +68,7 @@ const PARTIAL_CREATED_AT_FIELD = {
|
||||
viewFieldUniversalIdentifiers: [],
|
||||
kanbanAggregateOperationViewUniversalIdentifiers: [],
|
||||
calendarViewUniversalIdentifiers: [],
|
||||
calendarEndViewUniversalIdentifiers: [],
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
fieldPermissionUniversalIdentifiers: [],
|
||||
viewSortUniversalIdentifiers: [],
|
||||
@@ -97,6 +99,7 @@ const PARTIAL_UPDATED_AT_FIELD = {
|
||||
viewFieldUniversalIdentifiers: [],
|
||||
kanbanAggregateOperationViewUniversalIdentifiers: [],
|
||||
calendarViewUniversalIdentifiers: [],
|
||||
calendarEndViewUniversalIdentifiers: [],
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
fieldPermissionUniversalIdentifiers: [],
|
||||
viewSortUniversalIdentifiers: [],
|
||||
@@ -127,6 +130,7 @@ const PARTIAL_DELETED_AT_FIELD = {
|
||||
viewFieldUniversalIdentifiers: [],
|
||||
kanbanAggregateOperationViewUniversalIdentifiers: [],
|
||||
calendarViewUniversalIdentifiers: [],
|
||||
calendarEndViewUniversalIdentifiers: [],
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
fieldPermissionUniversalIdentifiers: [],
|
||||
viewSortUniversalIdentifiers: [],
|
||||
@@ -157,6 +161,7 @@ const PARTIAL_CREATED_BY_FIELD = {
|
||||
viewFieldUniversalIdentifiers: [],
|
||||
kanbanAggregateOperationViewUniversalIdentifiers: [],
|
||||
calendarViewUniversalIdentifiers: [],
|
||||
calendarEndViewUniversalIdentifiers: [],
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
fieldPermissionUniversalIdentifiers: [],
|
||||
viewSortUniversalIdentifiers: [],
|
||||
@@ -187,6 +192,7 @@ const PARTIAL_UPDATED_BY_FIELD = {
|
||||
viewFieldUniversalIdentifiers: [],
|
||||
kanbanAggregateOperationViewUniversalIdentifiers: [],
|
||||
calendarViewUniversalIdentifiers: [],
|
||||
calendarEndViewUniversalIdentifiers: [],
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
fieldPermissionUniversalIdentifiers: [],
|
||||
viewSortUniversalIdentifiers: [],
|
||||
@@ -217,6 +223,7 @@ const PARTIAL_POSITION_FIELD = {
|
||||
viewFieldUniversalIdentifiers: [],
|
||||
kanbanAggregateOperationViewUniversalIdentifiers: [],
|
||||
calendarViewUniversalIdentifiers: [],
|
||||
calendarEndViewUniversalIdentifiers: [],
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
fieldPermissionUniversalIdentifiers: [],
|
||||
viewSortUniversalIdentifiers: [],
|
||||
@@ -249,6 +256,7 @@ const PARTIAL_SEARCH_VECTOR_FIELD = {
|
||||
viewFieldUniversalIdentifiers: [],
|
||||
kanbanAggregateOperationViewUniversalIdentifiers: [],
|
||||
calendarViewUniversalIdentifiers: [],
|
||||
calendarEndViewUniversalIdentifiers: [],
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
fieldPermissionUniversalIdentifiers: [],
|
||||
viewSortUniversalIdentifiers: [],
|
||||
|
||||
+1
@@ -717,6 +717,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
isCustom: true,
|
||||
anyFieldFilterValue: null,
|
||||
calendarFieldMetadataUniversalIdentifier: null,
|
||||
calendarEndFieldMetadataUniversalIdentifier: null,
|
||||
calendarLayout: null,
|
||||
isCompact: false,
|
||||
shouldHideEmptyGroups: false,
|
||||
|
||||
+1
@@ -51,6 +51,7 @@ export const buildNameFlatFieldMetadataForCustomObject = ({
|
||||
viewFieldUniversalIdentifiers: [],
|
||||
kanbanAggregateOperationViewUniversalIdentifiers: [],
|
||||
calendarViewUniversalIdentifiers: [],
|
||||
calendarEndViewUniversalIdentifiers: [],
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
fieldPermissionUniversalIdentifiers: [],
|
||||
universalSettings: null,
|
||||
|
||||
+1
@@ -31,6 +31,7 @@ export const computeFlatRecordPageFieldsViewToCreate = ({
|
||||
isCustom: true,
|
||||
anyFieldFilterValue: null,
|
||||
calendarFieldMetadataUniversalIdentifier: null,
|
||||
calendarEndFieldMetadataUniversalIdentifier: null,
|
||||
calendarLayout: null,
|
||||
isCompact: false,
|
||||
shouldHideEmptyGroups: false,
|
||||
|
||||
+5
@@ -111,6 +111,11 @@ export class CreateViewInput {
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
calendarFieldMetadataId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
calendarEndFieldMetadataId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
|
||||
+6
-1
@@ -88,7 +88,12 @@ export class UpdateViewInput {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
calendarFieldMetadataId?: string;
|
||||
calendarFieldMetadataId?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
calendarEndFieldMetadataId?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(ViewVisibility)
|
||||
|
||||
@@ -82,6 +82,9 @@ export class ViewDTO {
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
calendarFieldMetadataId?: string | null;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
calendarEndFieldMetadataId?: string | null;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: false })
|
||||
workspaceId: string;
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import { WasIntroducedInUpgrade } from 'src/engine/core-modules/upgrade/decorato
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { ADD_IS_SYSTEM_SIDE_EFFECT_UPGRADE_COMMAND_NAME } from 'src/database/commands/upgrade-version-command/2-15/is-system-side-effect-upgrade-command-name.constant';
|
||||
import { ADD_VIEW_KANBAN_COLUMN_WIDTH_UPGRADE_COMMAND_NAME } from 'src/database/commands/upgrade-version-command/2-15/add-view-kanban-column-width-upgrade-command-name.constant';
|
||||
import { ADD_CALENDAR_END_FIELD_METADATA_ID_TO_VIEW_UPGRADE_COMMAND_NAME } from 'src/database/commands/upgrade-version-command/2-22/add-calendar-end-field-metadata-id-to-view-upgrade-command-name.constant';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ViewFieldGroupEntity } from 'src/engine/metadata-modules/view-field-group/entities/view-field-group.entity';
|
||||
@@ -48,6 +49,7 @@ export type ViewOverrides = {
|
||||
anyFieldFilterValue?: string | null;
|
||||
calendarLayout?: ViewCalendarLayout | null;
|
||||
calendarFieldMetadataId?: SerializedRelation | null;
|
||||
calendarEndFieldMetadataId?: SerializedRelation | null;
|
||||
visibility?: ViewVisibility;
|
||||
mainGroupByFieldMetadataId?: SerializedRelation | null;
|
||||
shouldHideEmptyGroups?: boolean;
|
||||
@@ -62,6 +64,7 @@ export type ViewOverrides = {
|
||||
])
|
||||
@Index('IDX_VIEW_VISIBILITY', ['visibility'])
|
||||
@Index('IDX_VIEW_CALENDAR_FIELD_METADATA', ['calendarFieldMetadataId'])
|
||||
@Index('IDX_VIEW_CALENDAR_END_FIELD_METADATA', ['calendarEndFieldMetadataId'])
|
||||
@Index('IDX_VIEW_KANBAN_FIELD_METADATA', [
|
||||
'kanbanAggregateOperationFieldMetadataId',
|
||||
])
|
||||
@@ -170,6 +173,24 @@ export class ViewEntity
|
||||
@JoinColumn({ name: 'calendarFieldMetadataId' })
|
||||
calendarFieldMetadata: Relation<FieldMetadataEntity> | null;
|
||||
|
||||
@WasIntroducedInUpgrade({
|
||||
upgradeCommandName:
|
||||
ADD_CALENDAR_END_FIELD_METADATA_ID_TO_VIEW_UPGRADE_COMMAND_NAME,
|
||||
})
|
||||
@Column({ nullable: true, type: 'uuid' })
|
||||
calendarEndFieldMetadataId: string | null;
|
||||
|
||||
@ManyToOne(
|
||||
() => FieldMetadataEntity,
|
||||
(fieldMetadata) => fieldMetadata.calendarEndViews,
|
||||
{
|
||||
onDelete: 'SET NULL',
|
||||
nullable: true,
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: 'calendarEndFieldMetadataId' })
|
||||
calendarEndFieldMetadata: Relation<FieldMetadataEntity> | null;
|
||||
|
||||
@Column({ nullable: true, type: 'uuid' })
|
||||
mainGroupByFieldMetadataId: string | null;
|
||||
|
||||
|
||||
+42
@@ -30,6 +30,7 @@ describe('ViewToolsFactory', () => {
|
||||
const mockObjectMetadataId = 'object-metadata-id';
|
||||
const mockObjectNameSingular = 'company';
|
||||
const mockCalendarFieldMetadataId = 'calendar-field-metadata-id';
|
||||
const mockCalendarEndFieldMetadataId = 'calendar-end-field-metadata-id';
|
||||
|
||||
const mockNameFieldMetadataId = 'name-field-metadata-id';
|
||||
const mockStageFieldMetadataId = 'stage-field-metadata-id';
|
||||
@@ -43,6 +44,13 @@ describe('ViewToolsFactory', () => {
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
universalIdentifier: 'field-universal-id',
|
||||
},
|
||||
'end-field-universal-id': {
|
||||
id: mockCalendarEndFieldMetadataId,
|
||||
name: 'endsAt',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
universalIdentifier: 'end-field-universal-id',
|
||||
},
|
||||
'name-field-universal-id': {
|
||||
id: mockNameFieldMetadataId,
|
||||
name: 'name',
|
||||
@@ -783,6 +791,40 @@ describe('ViewToolsFactory', () => {
|
||||
expect(viewService.createOne).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should resolve and update the calendar end field on an existing view', async () => {
|
||||
const existingView = {
|
||||
...mockView,
|
||||
type: ViewType.CALENDAR,
|
||||
visibility: ViewVisibility.WORKSPACE,
|
||||
};
|
||||
|
||||
viewService.findById.mockResolvedValue(existingView as any);
|
||||
completeViewUpsertService.upsertCompleteView.mockResolvedValue({
|
||||
...existingView,
|
||||
calendarEndFieldMetadataId: mockCalendarEndFieldMetadataId,
|
||||
} as any);
|
||||
|
||||
const tools = viewToolsFactory.generateWriteTools(
|
||||
mockWorkspaceId,
|
||||
mockUserWorkspaceId,
|
||||
);
|
||||
|
||||
await callExecute(tools['upsert_complete_view'], {
|
||||
id: mockViewId,
|
||||
calendarEndFieldName: 'endsAt',
|
||||
});
|
||||
|
||||
expect(
|
||||
completeViewUpsertService.upsertCompleteView,
|
||||
).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
existingViewId: mockViewId,
|
||||
objectMetadataId: mockObjectMetadataId,
|
||||
calendarEndFieldMetadataId: mockCalendarEndFieldMetadataId,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should pass an empty sorts array through to upsertCompleteView on update', async () => {
|
||||
const existingView = {
|
||||
...mockView,
|
||||
|
||||
+20
-2
@@ -72,6 +72,7 @@ export class CompleteViewUpsertService {
|
||||
kanbanAggregateOperationFieldMetadataId,
|
||||
calendarLayout,
|
||||
calendarFieldMetadataId,
|
||||
calendarEndFieldMetadataId,
|
||||
fields,
|
||||
filters,
|
||||
sorts,
|
||||
@@ -89,6 +90,7 @@ export class CompleteViewUpsertService {
|
||||
kanbanAggregateOperationFieldMetadataId?: string;
|
||||
calendarLayout?: ViewCalendarLayout;
|
||||
calendarFieldMetadataId?: string;
|
||||
calendarEndFieldMetadataId?: string;
|
||||
fields?: CompleteViewFieldSpec[];
|
||||
filters?: CompleteViewFilterSpec[];
|
||||
sorts?: CompleteViewSortSpec[];
|
||||
@@ -138,6 +140,7 @@ export class CompleteViewUpsertService {
|
||||
existingViewId,
|
||||
name,
|
||||
icon,
|
||||
calendarEndFieldMetadataId,
|
||||
userWorkspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
flatViewMaps,
|
||||
@@ -155,6 +158,7 @@ export class CompleteViewUpsertService {
|
||||
kanbanAggregateOperationFieldMetadataId,
|
||||
calendarLayout,
|
||||
calendarFieldMetadataId,
|
||||
calendarEndFieldMetadataId,
|
||||
userWorkspaceId,
|
||||
flatApplication: workspaceCustomFlatApplication,
|
||||
flatFieldMetadataMaps,
|
||||
@@ -251,6 +255,7 @@ export class CompleteViewUpsertService {
|
||||
kanbanAggregateOperationFieldMetadataId,
|
||||
calendarLayout,
|
||||
calendarFieldMetadataId,
|
||||
calendarEndFieldMetadataId,
|
||||
userWorkspaceId,
|
||||
flatApplication,
|
||||
flatFieldMetadataMaps,
|
||||
@@ -266,6 +271,7 @@ export class CompleteViewUpsertService {
|
||||
kanbanAggregateOperationFieldMetadataId?: string;
|
||||
calendarLayout?: ViewCalendarLayout;
|
||||
calendarFieldMetadataId?: string;
|
||||
calendarEndFieldMetadataId?: string;
|
||||
userWorkspaceId?: string;
|
||||
flatApplication: FlatApplication;
|
||||
flatFieldMetadataMaps: AllFlatEntityMaps['flatFieldMetadataMaps'];
|
||||
@@ -291,6 +297,7 @@ export class CompleteViewUpsertService {
|
||||
kanbanAggregateOperationFieldMetadataId,
|
||||
calendarLayout,
|
||||
calendarFieldMetadataId,
|
||||
calendarEndFieldMetadataId,
|
||||
},
|
||||
createdByUserWorkspaceId: userWorkspaceId,
|
||||
flatApplication,
|
||||
@@ -322,6 +329,7 @@ export class CompleteViewUpsertService {
|
||||
existingViewId,
|
||||
name,
|
||||
icon,
|
||||
calendarEndFieldMetadataId,
|
||||
userWorkspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
flatViewMaps,
|
||||
@@ -331,19 +339,29 @@ export class CompleteViewUpsertService {
|
||||
existingViewId: string;
|
||||
name?: string;
|
||||
icon?: string;
|
||||
calendarEndFieldMetadataId?: string;
|
||||
userWorkspaceId?: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
flatViewMaps: AllFlatEntityMaps['flatViewMaps'];
|
||||
flatViewGroupMaps: AllFlatEntityMaps['flatViewGroupMaps'];
|
||||
flatFieldMetadataMaps: AllFlatEntityMaps['flatFieldMetadataMaps'];
|
||||
}): ViewUpsertRootOperations {
|
||||
if (!isDefined(name) && !isDefined(icon)) {
|
||||
if (
|
||||
!isDefined(name) &&
|
||||
!isDefined(icon) &&
|
||||
!isDefined(calendarEndFieldMetadataId)
|
||||
) {
|
||||
return { viewId: existingViewId };
|
||||
}
|
||||
|
||||
const { flatViewToUpdate, flatViewGroupsToDelete, flatViewGroupsToCreate } =
|
||||
fromUpdateViewInputToFlatViewToUpdateOrThrow({
|
||||
updateViewInput: { id: existingViewId, name, icon },
|
||||
updateViewInput: {
|
||||
id: existingViewId,
|
||||
name,
|
||||
icon,
|
||||
calendarEndFieldMetadataId,
|
||||
},
|
||||
flatViewMaps,
|
||||
flatViewGroupMaps,
|
||||
flatFieldMetadataMaps,
|
||||
|
||||
@@ -107,6 +107,12 @@ const CreateViewInputSchema = z.object({
|
||||
.describe(
|
||||
'Date field name to use for the calendar (required for CALENDAR views, must be a DATE or DATE_TIME field, e.g., "createdAt", "dueAt")',
|
||||
),
|
||||
calendarEndFieldName: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Optional end date field name for the calendar. It must have the same DATE or DATE_TIME type as calendarFieldName.',
|
||||
),
|
||||
fieldNames: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
@@ -244,6 +250,12 @@ const UpsertCompleteViewInputSchema = z.object({
|
||||
.describe(
|
||||
'Date field name for the calendar (required for CALENDAR, must be DATE or DATE_TIME).',
|
||||
),
|
||||
calendarEndFieldName: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Optional end date field name for the calendar. It must match the type of calendarFieldName.',
|
||||
),
|
||||
fields: z
|
||||
.array(UpsertCompleteViewFieldSchema)
|
||||
.optional()
|
||||
@@ -272,6 +284,7 @@ type UpsertCompleteViewIdentifiers = {
|
||||
mainGroupByFieldMetadataId?: string;
|
||||
kanbanAggregateOperationFieldMetadataId?: string;
|
||||
calendarFieldMetadataId?: string;
|
||||
calendarEndFieldMetadataId?: string;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
@@ -448,6 +461,7 @@ export class ViewToolsFactory {
|
||||
kanbanAggregateOperationFieldName?: string;
|
||||
calendarLayout?: ViewCalendarLayout;
|
||||
calendarFieldName?: string;
|
||||
calendarEndFieldName?: string;
|
||||
};
|
||||
workspaceId: string;
|
||||
userWorkspaceId?: string;
|
||||
@@ -469,9 +483,20 @@ export class ViewToolsFactory {
|
||||
throw new Error('You can only update your own unlisted views');
|
||||
}
|
||||
|
||||
const calendarEndFieldMetadataId = isDefined(
|
||||
parameters.calendarEndFieldName,
|
||||
)
|
||||
? await this.resolveCalendarFieldMetadataId(
|
||||
workspaceId,
|
||||
existingView.objectMetadataId,
|
||||
parameters.calendarEndFieldName,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
existingViewId: existingView.id,
|
||||
objectMetadataId: existingView.objectMetadataId,
|
||||
calendarEndFieldMetadataId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -537,11 +562,22 @@ export class ViewToolsFactory {
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const calendarEndFieldMetadataId = isDefined(
|
||||
parameters.calendarEndFieldName,
|
||||
)
|
||||
? await this.resolveCalendarFieldMetadataId(
|
||||
workspaceId,
|
||||
objectMetadataId,
|
||||
parameters.calendarEndFieldName,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
objectMetadataId,
|
||||
mainGroupByFieldMetadataId,
|
||||
kanbanAggregateOperationFieldMetadataId,
|
||||
calendarFieldMetadataId,
|
||||
calendarEndFieldMetadataId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -636,6 +672,7 @@ VIEW TYPES: TABLE (default), KANBAN (requires mainGroupByFieldName, a SELECT fie
|
||||
kanbanAggregateOperationFieldName?: string;
|
||||
calendarLayout?: ViewCalendarLayout;
|
||||
calendarFieldName?: string;
|
||||
calendarEndFieldName?: string;
|
||||
fields?: Array<
|
||||
FieldReference & { isVisible?: boolean; size?: number }
|
||||
>;
|
||||
@@ -655,6 +692,7 @@ VIEW TYPES: TABLE (default), KANBAN (requires mainGroupByFieldName, a SELECT fie
|
||||
mainGroupByFieldMetadataId,
|
||||
kanbanAggregateOperationFieldMetadataId,
|
||||
calendarFieldMetadataId,
|
||||
calendarEndFieldMetadataId,
|
||||
} = await this.resolveUpsertCompleteViewIdentifiersOrThrow({
|
||||
parameters,
|
||||
workspaceId,
|
||||
@@ -718,6 +756,7 @@ VIEW TYPES: TABLE (default), KANBAN (requires mainGroupByFieldName, a SELECT fie
|
||||
kanbanAggregateOperationFieldMetadataId,
|
||||
calendarLayout: parameters.calendarLayout,
|
||||
calendarFieldMetadataId,
|
||||
calendarEndFieldMetadataId,
|
||||
fields,
|
||||
filters,
|
||||
sorts,
|
||||
@@ -757,6 +796,7 @@ VIEW TYPES: TABLE (default), KANBAN (requires mainGroupByFieldName, a SELECT fie
|
||||
kanbanAggregateOperationFieldName?: string;
|
||||
calendarLayout?: ViewCalendarLayout;
|
||||
calendarFieldName?: string;
|
||||
calendarEndFieldName?: string;
|
||||
fieldNames?: string[];
|
||||
}) => {
|
||||
try {
|
||||
@@ -791,6 +831,7 @@ VIEW TYPES: TABLE (default), KANBAN (requires mainGroupByFieldName, a SELECT fie
|
||||
let mainGroupByFieldMetadataId: string | undefined;
|
||||
let kanbanAggregateOperationFieldMetadataId: string | undefined;
|
||||
let calendarFieldMetadataId: string | undefined;
|
||||
let calendarEndFieldMetadataId: string | undefined;
|
||||
|
||||
if (parameters.mainGroupByFieldName) {
|
||||
mainGroupByFieldMetadataId =
|
||||
@@ -819,6 +860,15 @@ VIEW TYPES: TABLE (default), KANBAN (requires mainGroupByFieldName, a SELECT fie
|
||||
);
|
||||
}
|
||||
|
||||
if (parameters.calendarEndFieldName) {
|
||||
calendarEndFieldMetadataId =
|
||||
await this.resolveCalendarFieldMetadataId(
|
||||
workspaceId,
|
||||
objectMetadataId,
|
||||
parameters.calendarEndFieldName,
|
||||
);
|
||||
}
|
||||
|
||||
const view = await this.viewService.createOne({
|
||||
createViewInput: {
|
||||
name: parameters.name,
|
||||
@@ -832,6 +882,7 @@ VIEW TYPES: TABLE (default), KANBAN (requires mainGroupByFieldName, a SELECT fie
|
||||
kanbanAggregateOperationFieldMetadataId,
|
||||
calendarLayout: parameters.calendarLayout,
|
||||
calendarFieldMetadataId,
|
||||
calendarEndFieldMetadataId,
|
||||
},
|
||||
workspaceId,
|
||||
createdByUserWorkspaceId: userWorkspaceId,
|
||||
|
||||
Reference in New Issue
Block a user