Compare commits

...
Author SHA1 Message Date
Thomas Trompette 546b486bd3 Fix: composite update events not received (#19053)
Database batch events (update / insert / soft-delete / hard-delete) were
building recordsBefore / recordsAfter after formatResult ran twice: once
inside WorkspaceSelectQueryBuilder.getMany() / getOne(), and again in
the CUD query builders. On the second pass, already-shaped composite
fields (e.g. emails) went through formatFieldMetadataValue and kept the
same object references as the live TypeORM row, so recordsBefore could
change when the entity was updated—breaking workflow triggers and diffs.
2026-04-01 11:10:46 +02:00
Charles BochetandGitHub 1ce4da5b67 Fix upgrade commands 2 (#19157) 2026-03-31 12:47:01 +02:00
ef66d6b337 i18n - translations (#19158)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-31 12:23:19 +02:00
bcca5d0002 fix: show disabled file chip when file no longer exists in timeline (#19045)
Fixes: #18943 
Follow-up pr: #19001

## Summary:                                                             
- Timeline activity shows file upload history, but deleted files had no
signed URL and were still rendered as clickable — clicking did nothing
- grab the fileId from properties.diff.after, look it up in the current
record's files field: if present, use live signed URL; if absent, mark
as deleted
- Deleted file chips show line-through label, not-allowed cursor, and
"File no longer exists" tooltip on hover
  


https://github.com/user-attachments/assets/5df6a675-0003-4fd1-ad57-a07e4338923f

---------

Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
2026-03-31 10:07:19 +00:00
WeikoandGitHub e0630b8653 Fix TransactionNotStartedError (#19155)
## Context
Checked in the codebase where we are trying to rollback a non-active
transaction.


packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts
seemed to be the only place where it happens.

We could enforce this with a lint rule in the future 🤔
2026-03-31 10:02:50 +00:00
Paul RastoinandGitHub 61a27984e8 0.8.0.canary.7 bump (#19150)
Already published on npm
2026-03-31 09:50:28 +02:00
87 changed files with 397 additions and 86 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "create-twenty-app",
"version": "0.8.0-canary.6",
"version": "0.8.0-canary.7",
"description": "Command-line interface to create Twenty application",
"main": "dist/cli.cjs",
"bin": "dist/cli.cjs",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "twenty-client-sdk",
"version": "0.8.0-canary.6",
"version": "0.8.0-canary.7",
"sideEffects": false,
"license": "AGPL-3.0",
"scripts": {
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "Lêeretiket"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "تسمية الملف"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "Etiqueta del fitxer"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "Štítek souboru"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "Fil-etiket"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "Dateibezeichnung"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "Ετικέτα αρχείου"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
+6
View File
@@ -6887,6 +6887,12 @@ msgstr "File field not found for attachment object"
msgid "File label"
msgstr "File label"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr "File no longer exists - {0}"
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "Etiqueta del archivo"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "Tiedoston otsikko"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "Étiquette du fichier"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "תווית קובץ"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr "Fájlmező nem található a melléklet objektumhoz"
msgid "File label"
msgstr "Fájlcímke"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "Etichetta del file"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "ファイルのラベル"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "파일 라벨"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "Bestandslabel"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "Etikett for fil"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "Etykieta pliku"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6887,6 +6887,12 @@ msgstr ""
msgid "File label"
msgstr ""
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "Rótulo do arquivo"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "Rótulo do arquivo"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "Eticheta fișierului"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
Binary file not shown.
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "Ознака датотеке"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "Filens etikett"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "Dosya etiketi"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "Мітка файлу"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "Nhãn tệp"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "文件标签"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -6892,6 +6892,12 @@ msgstr ""
msgid "File label"
msgstr "檔案標籤"
#. js-lingui-id: QWWgBD
#. placeholder {0}: file.label
#: src/modules/ui/field/display/components/FileChip.tsx
msgid "File no longer exists - {0}"
msgstr ""
#. js-lingui-id: ncBVqE
#: src/modules/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile.ts
msgid "File upload failed"
@@ -1,9 +1,13 @@
import { useEffect } from 'react';
import { useContext, useEffect } from 'react';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { TimelineActivityContext } from '@/activities/timeline-activities/contexts/TimelineActivityContext';
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type FieldFilesValue } from '@/object-record/record-field/ui/types/FieldMetadata';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
export const EventFieldDiffValueEffect = ({
@@ -22,13 +26,39 @@ export const EventFieldDiffValueEffect = ({
diffArtificialRecordStoreId,
);
const { recordId } = useContext(TimelineActivityContext);
const recordStore = useAtomFamilyStateValue(recordStoreFamilyState, recordId);
useEffect(() => {
if (!isDefined(diffRecord)) return;
let fieldValue = diffRecord;
if (
fieldMetadataItem.type === FieldMetadataType.FILES &&
isDefined(recordStore) &&
Array.isArray(diffRecord)
) {
const currentFiles = Array.isArray(recordStore[fieldMetadataItem.name])
? (recordStore[fieldMetadataItem.name] as FieldFilesValue[])
: [];
const currentFileMap = new Map(
currentFiles.map((file) => [file.fileId, file]),
);
fieldValue = (diffRecord as FieldFilesValue[]).map((file) => {
const currentFile = currentFileMap.get(file.fileId);
if (isDefined(currentFile)) {
return { ...file, url: currentFile.url };
}
return { ...file, isDeleted: true, url: undefined };
});
}
const forgedObjectRecord = {
__typename: mainObjectMetadataItem.nameSingular,
id: diffArtificialRecordStoreId,
[fieldMetadataItem.name]: diffRecord,
[fieldMetadataItem.name]: fieldValue,
};
setRecordStore(forgedObjectRecord);
@@ -36,8 +66,10 @@ export const EventFieldDiffValueEffect = ({
diffRecord,
diffArtificialRecordStoreId,
fieldMetadataItem.name,
fieldMetadataItem.type,
mainObjectMetadataItem.nameSingular,
setRecordStore,
recordStore,
]);
return <></>;
@@ -335,4 +335,5 @@ export type FieldFilesValue = {
extension?: string;
url?: string;
fileCategory?: FileCategory;
isDeleted?: boolean;
};
@@ -1,4 +1,5 @@
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { FileIcon } from '@/file/components/FileIcon';
import { type FieldFilesValue } from '@/object-record/record-field/ui/types/FieldMetadata';
@@ -24,7 +25,8 @@ export const FileChip = ({
onClick,
forceDisableClick,
}: FileChipProps) => {
const isClickable = forceDisableClick !== true;
const isDeleted = file.isDeleted === true;
const isClickable = forceDisableClick !== true && !isDeleted;
const handleMouseDown = (event: React.MouseEvent): void => {
if (!isClickable) {
@@ -45,17 +47,24 @@ export const FileChip = ({
);
return (
<StyledClickableContainer
clickable={isClickable}
onMouseDown={handleMouseDown}
>
<Chip
label={file.label ?? ''}
maxWidth={MAX_WIDTH}
leftComponent={fileIcon}
variant={ChipVariant.Highlighted}
<>
<StyledClickableContainer
clickable={isClickable}
/>
</StyledClickableContainer>
onMouseDown={handleMouseDown}
>
<Chip
label={file.label}
alwaysShowTooltip={isDeleted}
tooltipLabel={
isDeleted ? t`File no longer exists - ${file.label}` : undefined
}
disabled={isDeleted}
maxWidth={MAX_WIDTH}
leftComponent={fileIcon}
variant={isDeleted ? ChipVariant.Static : ChipVariant.Highlighted}
clickable={isClickable}
/>
</StyledClickableContainer>
</>
);
};
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "twenty-sdk",
"version": "0.8.0-canary.6",
"version": "0.8.0-canary.7",
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "dist/sdk/index.d.ts",
@@ -3,7 +3,6 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { BackfillCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-command-menu-items.command';
import { BackfillNavigationMenuItemTypeCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-navigation-menu-item-type.command';
import { BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts-and-fields-widget-view-fields.command';
import { BackfillSelectFieldOptionIdsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-select-field-option-ids.command';
import { DeleteOrphanNavigationMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-delete-orphan-navigation-menu-items.command';
import { IdentifyFieldPermissionMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-identify-field-permission-metadata.command';
@@ -66,7 +65,6 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo
MakeFieldPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
BackfillCommandMenuItemsCommand,
BackfillNavigationMenuItemTypeCommand,
BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
BackfillSelectFieldOptionIdsCommand,
DeleteOrphanNavigationMenuItemsCommand,
SeedCliApplicationRegistrationCommand,
@@ -84,7 +82,6 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo
MakeFieldPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
BackfillCommandMenuItemsCommand,
BackfillNavigationMenuItemTypeCommand,
BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
BackfillSelectFieldOptionIdsCommand,
DeleteOrphanNavigationMenuItemsCommand,
SeedCliApplicationRegistrationCommand,
@@ -87,7 +87,7 @@ const isRelationTargetAvailable = (
};
@Command({
name: 'upgrade:1-20:backfill-page-layouts-and-fields-widget-view-fields',
name: 'upgrade:1-21:backfill-page-layouts-and-fields-widget-view-fields',
description:
'Backfill RECORD_PAGE page layouts, sync FIELDS_WIDGET view fields, create FIELD widgets, and enable IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED',
})
@@ -594,7 +594,7 @@ export class BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand extends ActiveO
}
}
// Build object ID objectMetadata map
// Build object ID -> objectMetadata map
const objectById = new Map<string, FlatObjectMetadata>();
for (const obj of Object.values(
@@ -605,7 +605,7 @@ export class BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand extends ActiveO
}
}
// Build object ID RECORD_PAGE layout map
// Build object ID -> RECORD_PAGE layout map
const recordPageLayoutByObjectId = new Map<
string,
{ id: string; universalIdentifier: string }
@@ -626,7 +626,7 @@ export class BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand extends ActiveO
}
}
// Build pageLayoutId home tab map
// Build pageLayoutId -> home tab map
const homeTabByPageLayoutId = new Map<
string,
{
@@ -655,7 +655,7 @@ export class BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand extends ActiveO
// Group fields by objectMetadataId
const fieldsByObjectId = new Map<string, FlatFieldMetadata[]>();
// Map morphId all field IDs sharing that morphId (for dedup)
// Map morphId -> all field IDs sharing that morphId (for dedup)
const fieldIdsByMorphId = new Map<string, string[]>();
for (const field of Object.values(
@@ -0,0 +1,24 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-backfill-page-layouts-and-fields-widget-view-fields.command';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
@Module({
imports: [
TypeOrmModule.forFeature([WorkspaceEntity]),
DataSourceModule,
WorkspaceCacheModule,
ApplicationModule,
WorkspaceMigrationModule,
FeatureFlagModule,
],
providers: [BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand],
exports: [BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand],
})
export class V1_21_UpgradeVersionCommandModule {}
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { V1_20_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-20/1-20-upgrade-version-command.module';
import { V1_21_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-21/1-21-upgrade-version-command.module';
import { UpgradeCommand } from 'src/database/commands/upgrade-version-command/upgrade.command';
import { CoreEngineVersionModule } from 'src/engine/core-engine-version/core-engine-version.module';
import { CoreMigrationRunnerModule } from 'src/database/commands/core-migration-runner/core-migration-runner.module';
@@ -13,6 +14,7 @@ import { WorkspaceVersionModule } from 'src/engine/workspace-manager/workspace-v
imports: [
TypeOrmModule.forFeature([WorkspaceEntity]),
V1_20_UpgradeVersionCommandModule,
V1_21_UpgradeVersionCommandModule,
DataSourceModule,
CoreEngineVersionModule,
CoreMigrationRunnerModule,
@@ -12,7 +12,7 @@ import {
import { CoreMigrationRunnerService } from 'src/database/commands/core-migration-runner/services/core-migration-runner.service';
import { BackfillCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-command-menu-items.command';
import { BackfillNavigationMenuItemTypeCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-navigation-menu-item-type.command';
import { BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts-and-fields-widget-view-fields.command';
import { BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-backfill-page-layouts-and-fields-widget-view-fields.command';
import { IdentifyFieldPermissionMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-identify-field-permission-metadata.command';
import { BackfillSelectFieldOptionIdsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-select-field-option-ids.command';
import { DeleteOrphanNavigationMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-delete-orphan-navigation-menu-items.command';
@@ -60,13 +60,15 @@ export class UpgradeCommand extends UpgradeCommandRunner {
protected readonly backfillNavigationMenuItemTypeCommand: BackfillNavigationMenuItemTypeCommand,
protected readonly backfillCommandMenuItemsCommand: BackfillCommandMenuItemsCommand,
protected readonly deleteOrphanNavigationMenuItemsCommand: DeleteOrphanNavigationMenuItemsCommand,
protected readonly backfillPageLayoutsAndFieldsWidgetViewFieldsCommand: BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
protected readonly seedCliApplicationRegistrationCommand: SeedCliApplicationRegistrationCommand,
protected readonly migrateRichTextToTextCommand: MigrateRichTextToTextCommand,
protected readonly migrateMessagingInfrastructureToMetadataCommand: MigrateMessagingInfrastructureToMetadataCommand,
protected readonly backfillSelectFieldOptionIdsCommand: BackfillSelectFieldOptionIdsCommand,
protected readonly updateStandardIndexViewNamesCommand: UpdateStandardIndexViewNamesCommand,
protected readonly makeWorkflowSearchableCommand: MakeWorkflowSearchableCommand,
// 1.21 Commands
protected readonly backfillPageLayoutsAndFieldsWidgetViewFieldsCommand: BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
) {
super(
workspaceRepository,
@@ -92,7 +94,6 @@ export class UpgradeCommand extends UpgradeCommandRunner {
this.migrateRichTextToTextCommand,
this.deleteOrphanNavigationMenuItemsCommand,
this.backfillCommandMenuItemsCommand,
this.backfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
this.seedCliApplicationRegistrationCommand,
this.migrateMessagingInfrastructureToMetadataCommand,
this.backfillSelectFieldOptionIdsCommand,
@@ -100,9 +101,14 @@ export class UpgradeCommand extends UpgradeCommandRunner {
this.makeWorkflowSearchableCommand,
];
const commands_1210: VersionCommands = [
this.backfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
];
this.allCommands = {
'1.19.0': [],
'1.20.0': commands_1200,
'1.21.0': commands_1210,
};
}
@@ -14,6 +14,9 @@ export const makePermissionFlagUniversalIdentifierAndApplicationIdNotNullQueries
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_da8ffd3c24b4a819430a861067" ON "core"."permissionFlag" ("workspaceId", "universalIdentifier")`,
);
await queryRunner.query(
`ALTER TABLE "core"."permissionFlag" DROP CONSTRAINT IF EXISTS "FK_b26a9d39a88d0e72373c677c6c5"`,
);
await queryRunner.query(
`ALTER TABLE "core"."permissionFlag" ADD CONSTRAINT "FK_b26a9d39a88d0e72373c677c6c5" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
@@ -14,6 +14,9 @@ export const makeObjectPermissionUniversalIdentifierAndApplicationIdNotNullQueri
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_c5ea53618b32558fe24e495f21" ON "core"."objectPermission" ("workspaceId", "universalIdentifier")`,
);
await queryRunner.query(
`ALTER TABLE "core"."objectPermission" DROP CONSTRAINT IF EXISTS "FK_f2ecee1066fd43800dbc85f87e4"`,
);
await queryRunner.query(
`ALTER TABLE "core"."objectPermission" ADD CONSTRAINT "FK_f2ecee1066fd43800dbc85f87e4" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
@@ -14,6 +14,9 @@ export const makeFieldPermissionUniversalIdentifierAndApplicationIdNotNullQuerie
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_0dedb90c717e179ef653c512b9" ON "core"."fieldPermission" ("workspaceId", "universalIdentifier")`,
);
await queryRunner.query(
`ALTER TABLE "core"."fieldPermission" DROP CONSTRAINT IF EXISTS "FK_71cc60c4a1c9f8a7c434d91d38c"`,
);
await queryRunner.query(
`ALTER TABLE "core"."fieldPermission" ADD CONSTRAINT "FK_71cc60c4a1c9f8a7c434d91d38c" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
@@ -7,6 +7,9 @@ export const makeNavigationMenuItemTypeNotNullQueries = async (
`ALTER TABLE "core"."navigationMenuItem" ALTER COLUMN "type" SET NOT NULL`,
);
await queryRunner.query(
`ALTER TABLE "core"."navigationMenuItem" DROP CONSTRAINT IF EXISTS "CHK_navigation_menu_item_type_fields"`,
);
await queryRunner.query(
`ALTER TABLE "core"."navigationMenuItem" ADD CONSTRAINT "CHK_navigation_menu_item_type_fields" CHECK (
("type" = 'FOLDER')
@@ -1,4 +1,8 @@
export const UPGRADE_COMMAND_SUPPORTED_VERSIONS = ['1.19.0', '1.20.0'] as const;
export const UPGRADE_COMMAND_SUPPORTED_VERSIONS = [
'1.19.0',
'1.20.0',
'1.21.0',
] as const;
export type UpgradeCommandVersion =
(typeof UPGRADE_COMMAND_SUPPORTED_VERSIONS)[number];
@@ -9,7 +9,6 @@ import { Repository, type DataSource, type QueryRunner } from 'typeorm';
import { v4 } from 'uuid';
import { USER_SIGNUP_EVENT_NAME } from 'src/engine/api/graphql/workspace-query-runner/constants/user-signup-event-name.constants';
import { MAX_WORKSPACES_WITHOUT_ENTERPRISE_KEY } from 'src/engine/core-modules/auth/constants/max-workspaces-without-enterprise-key.constants';
import { type AppTokenEntity } from 'src/engine/core-modules/app-token/app-token.entity';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import {
@@ -21,6 +20,7 @@ import {
compareHash,
hashPassword,
} from 'src/engine/core-modules/auth/auth.util';
import { MAX_WORKSPACES_WITHOUT_ENTERPRISE_KEY } from 'src/engine/core-modules/auth/constants/max-workspaces-without-enterprise-key.constants';
import {
type AuthProviderWithPasswordType,
type ExistingUserOrPartialUserWithPicture,
@@ -593,16 +593,18 @@ export class SignInUpService {
);
await queryRunner.commitTransaction();
await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [
'flatApplicationMaps',
]);
return { user, workspace };
} catch (error) {
await queryRunner.rollbackTransaction();
if (queryRunner.isTransactionActive) {
await queryRunner.rollbackTransaction();
}
throw error;
} finally {
await queryRunner.release();
await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [
'flatApplicationMaps',
]);
}
}
@@ -104,7 +104,9 @@ export class WorkspaceDeleteQueryBuilder<
objectMetadata.isCustom,
);
const before = await eventSelectQueryBuilder.getOne();
const before = await eventSelectQueryBuilder.getOne({
noFormatting: true,
});
this.expressionMap.wheres = applyTableAliasOnWhereCondition({
condition: this.expressionMap.wheres,
@@ -241,7 +241,9 @@ export class WorkspaceInsertQueryBuilder<
result.identifiers.map((identifier) => identifier.id),
);
const afterResult = await eventSelectQueryBuilder.getMany();
const afterResult = await eventSelectQueryBuilder.getMany({
noFormatting: true,
});
const formattedResultForEvent = formatResult<T[]>(
afterResult,
@@ -102,10 +102,16 @@ export class WorkspaceSelectQueryBuilder<
}
}
override async getMany(): Promise<T[]> {
override async getMany(options?: { noFormatting?: boolean }): Promise<T[]> {
try {
this.validatePermissions();
const result = await super.getMany();
if (options?.noFormatting === true) {
return result;
}
const mainAliasTarget = this.getMainAliasTarget();
const objectMetadata = getObjectMetadataFromEntityTarget(
@@ -113,8 +119,6 @@ export class WorkspaceSelectQueryBuilder<
this.internalContext,
);
const result = await super.getMany();
const formattedResult = formatResult<T[]>(
result,
objectMetadata,
@@ -150,10 +154,20 @@ export class WorkspaceSelectQueryBuilder<
}
}
override async getOne(): Promise<T | null> {
override async getOne(options?: {
noFormatting?: boolean;
}): Promise<T | null> {
try {
this.validatePermissions();
this.take(1);
const result = await super.getOne();
if (options?.noFormatting === true) {
return result;
}
const mainAliasTarget = this.getMainAliasTarget();
const objectMetadata = getObjectMetadataFromEntityTarget(
@@ -161,10 +175,6 @@ export class WorkspaceSelectQueryBuilder<
this.internalContext,
);
this.take(1);
const result = await super.getOne();
const formattedResult = formatResult<T>(
result,
objectMetadata,
@@ -103,7 +103,9 @@ export class WorkspaceSoftDeleteQueryBuilder<
objectMetadata.isCustom,
);
const before = await beforeEventSelectQueryBuilder.getMany();
const before = await beforeEventSelectQueryBuilder.getMany({
noFormatting: true,
});
this.expressionMap.wheres = applyTableAliasOnWhereCondition({
condition: this.expressionMap.wheres,
@@ -113,7 +115,9 @@ export class WorkspaceSoftDeleteQueryBuilder<
const typeORMSoftRemoveResultWithOnlyIdColumn = await super.execute();
const afterWithAllFields = await beforeEventSelectQueryBuilder.getMany();
const afterWithAllFields = await beforeEventSelectQueryBuilder.getMany({
noFormatting: true,
});
const formattedAfter = formatResult<T[]>(
afterWithAllFields,
@@ -136,7 +136,9 @@ export class WorkspaceUpdateQueryBuilder<
objectMetadata.isCustom,
);
const before = await eventSelectQueryBuilder.getMany();
const before = await eventSelectQueryBuilder.getMany({
noFormatting: true,
});
if (before.length > QUERY_MAX_RECORDS) {
throw new TwentyORMException(
@@ -236,7 +238,9 @@ export class WorkspaceUpdateQueryBuilder<
await this.filesFieldSync.updateFileEntityRecords(filesFieldFileIds);
}
const after = await eventSelectQueryBuilder.getMany();
const after = await eventSelectQueryBuilder.getMany({
noFormatting: true,
});
const formattedAfter = formatResult<T[]>(
after,
@@ -338,7 +342,9 @@ export class WorkspaceUpdateQueryBuilder<
this.manyInputs.map((input) => input.criteria),
);
const beforeRecords = await eventSelectQueryBuilder.getMany();
const beforeRecords = await eventSelectQueryBuilder.getMany({
noFormatting: true,
});
const formattedBefore = formatResult<T[]>(
beforeRecords,
@@ -447,7 +453,9 @@ export class WorkspaceUpdateQueryBuilder<
await this.filesFieldSync.updateFileEntityRecords(filesFieldFileIds);
}
const afterRecords = await eventSelectQueryBuilder.getMany();
const afterRecords = await eventSelectQueryBuilder.getMany({
noFormatting: true,
});
const formattedAfter = formatResult<T[]>(
afterRecords,
@@ -182,8 +182,13 @@ function formatResultInternal<T>(
continue;
}
const formattedFieldValue = formatFieldMetadataValue(
value,
fieldMetadata.type,
);
// @ts-expect-error legacy noImplicitAny
newData[key] = formatFieldMetadataValue(value, fieldMetadata.type);
newData[key] = formattedFieldValue;
}
// After assembling composite fields, handle those with missing required subfields
@@ -29,6 +29,8 @@ export type ChipProps = {
disabled?: boolean;
clickable?: boolean;
label: string;
tooltipLabel?: string;
alwaysShowTooltip?: boolean;
isLabelHidden?: boolean;
isBold?: boolean;
maxWidth?: number;
@@ -172,6 +174,8 @@ const renderRightComponent = (
export const Chip = ({
size = ChipSize.Small,
label,
tooltipLabel,
alwaysShowTooltip = false,
isLabelHidden = false,
isBold = false,
disabled = false,
@@ -200,7 +204,12 @@ export const Chip = ({
>
{leftComponent}
{!isLabelHidden && isDefined(label) && isNonEmptyString(label) ? (
<OverflowingTextWithTooltip size={size} text={label} />
<OverflowingTextWithTooltip
size={size}
text={label}
tooltipContent={tooltipLabel}
alwaysShowTooltip={alwaysShowTooltip}
/>
) : !forceEmptyText && !isLabelHidden ? (
<StyledDiv>{emptyLabel}</StyledDiv>
) : (
@@ -3,8 +3,8 @@ import { type ReactNode, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { isNonEmptyString } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
import { themeCssVariables } from '@ui/theme-constants';
import { isDefined } from 'twenty-shared/utils';
import { AppTooltip, TooltipDelay } from './AppTooltip';
const spacing4 = themeCssVariables.spacing[4];
@@ -64,6 +64,7 @@ type OverflowingTextWithTooltipProps = {
isTooltipMultiline?: boolean;
displayedMaxRows?: number;
tooltipDelay?: TooltipDelay;
alwaysShowTooltip?: boolean;
} & (
| {
text: string | null | undefined;
@@ -82,6 +83,7 @@ export const OverflowingTextWithTooltip = ({
displayedMaxRows,
tooltipContent,
tooltipDelay = TooltipDelay.mediumDelay,
alwaysShowTooltip = false,
}: OverflowingTextWithTooltipProps) => {
const textElementId = `title-id-${+new Date()}`;
@@ -146,7 +148,7 @@ export const OverflowingTextWithTooltip = ({
)}
{shouldRenderTooltip &&
isTitleOverflowing &&
(isTitleOverflowing || alwaysShowTooltip) &&
isDefined(tooltipText) &&
createPortal(
<div onClick={handleTooltipClick}>