Files - Migrate attachments in activities (#17808)
As attachment files have migrated from fullPath to file files field, need to migrate richText logic to fit to new attachment file handling + data migration
This commit is contained in:
+15
-10
@@ -9,18 +9,22 @@ describe('filterAttachmentsToRestore', () => {
|
||||
fullPath: 'https://exemple.com/test.txt',
|
||||
},
|
||||
] as Attachment[];
|
||||
const attachmentIdsToRestore = filterAttachmentsToRestore(
|
||||
[],
|
||||
const attachmentIdsToRestore = filterAttachmentsToRestore({
|
||||
attachmentPathsToRestore: [],
|
||||
softDeletedAttachments,
|
||||
);
|
||||
isFilesFieldMigrated: false,
|
||||
});
|
||||
expect(attachmentIdsToRestore).toEqual([]);
|
||||
});
|
||||
|
||||
it('should not return any ids if there are no soft deleted attachments', () => {
|
||||
const attachmentIdsToRestore = filterAttachmentsToRestore(
|
||||
['https://exemple.com/files/attachment/test.txt'],
|
||||
[],
|
||||
);
|
||||
const attachmentIdsToRestore = filterAttachmentsToRestore({
|
||||
attachmentPathsToRestore: [
|
||||
'https://exemple.com/files/attachment/test.txt',
|
||||
],
|
||||
softDeletedAttachments: [],
|
||||
isFilesFieldMigrated: false,
|
||||
});
|
||||
expect(attachmentIdsToRestore).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -35,10 +39,11 @@ describe('filterAttachmentsToRestore', () => {
|
||||
fullPath: 'https://exemple.com/files/images/test2.txt',
|
||||
},
|
||||
] as Attachment[];
|
||||
const attachmentIdsToRestore = filterAttachmentsToRestore(
|
||||
['https://exemple.com/files/images/test.txt'],
|
||||
const attachmentIdsToRestore = filterAttachmentsToRestore({
|
||||
attachmentPathsToRestore: ['https://exemple.com/files/images/test.txt'],
|
||||
softDeletedAttachments,
|
||||
);
|
||||
isFilesFieldMigrated: false,
|
||||
});
|
||||
expect(attachmentIdsToRestore).toEqual(['1']);
|
||||
});
|
||||
});
|
||||
|
||||
+2
-2
@@ -33,7 +33,7 @@ describe('getActivityAttachmentIdsAndNameToUpdate', () => {
|
||||
},
|
||||
]);
|
||||
const attachmentIdsAndNameToUpdate =
|
||||
getActivityAttachmentIdsAndNameToUpdate(activityBody, attachments);
|
||||
getActivityAttachmentIdsAndNameToUpdate(activityBody, attachments, false);
|
||||
expect(attachmentIdsAndNameToUpdate).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -68,7 +68,7 @@ describe('getActivityAttachmentIdsAndNameToUpdate', () => {
|
||||
},
|
||||
]);
|
||||
const attachmentIdsAndNameToUpdate =
|
||||
getActivityAttachmentIdsAndNameToUpdate(activityBody, attachments);
|
||||
getActivityAttachmentIdsAndNameToUpdate(activityBody, attachments, false);
|
||||
expect(attachmentIdsAndNameToUpdate).toEqual([{ id: '2', name: 'image4' }]);
|
||||
});
|
||||
});
|
||||
|
||||
+2
@@ -37,6 +37,7 @@ describe('getActivityAttachmentIdsToDelete', () => {
|
||||
newActivityBody,
|
||||
attachments,
|
||||
oldActivityBody,
|
||||
false,
|
||||
);
|
||||
expect(attachmentIdsToDelete).toEqual([]);
|
||||
});
|
||||
@@ -72,6 +73,7 @@ describe('getActivityAttachmentIdsToDelete', () => {
|
||||
newActivityBody,
|
||||
attachments,
|
||||
oldActivityBody,
|
||||
false,
|
||||
);
|
||||
expect(attachmentIdsToDelete).toEqual(['2']);
|
||||
});
|
||||
|
||||
+2
@@ -17,6 +17,7 @@ describe('getActivityAttachmentPathsToRestore', () => {
|
||||
const attachmentPathsToRestore = getActivityAttachmentPathsToRestore(
|
||||
newActivityBody,
|
||||
oldActivityAttachments,
|
||||
false,
|
||||
);
|
||||
expect(attachmentPathsToRestore).toEqual([]);
|
||||
});
|
||||
@@ -43,6 +44,7 @@ describe('getActivityAttachmentPathsToRestore', () => {
|
||||
const attachmentPathsToRestore = getActivityAttachmentPathsToRestore(
|
||||
newActivityBody,
|
||||
oldActivityAttachments,
|
||||
false,
|
||||
);
|
||||
expect(attachmentPathsToRestore).toEqual([
|
||||
'https://example.com/files/images/test2.txt',
|
||||
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
import { getAttachmentPath } from '@/activities/utils/getAttachmentPath';
|
||||
|
||||
describe('getAttachmentPath', () => {
|
||||
it('should extract the correct path from a locally stored file URL with token', () => {
|
||||
const token = 'dybszrrxvgrtefeidgybxzfxzr';
|
||||
const res = getAttachmentPath(
|
||||
`https://server.com/files/attachment/${token}/image.jpg?queryParam=value`,
|
||||
);
|
||||
expect(res).toEqual('https://server.com/files/attachment/image.jpg');
|
||||
});
|
||||
|
||||
it('should extract the correct path from a regular file URL', () => {
|
||||
const res = getAttachmentPath(
|
||||
'https://exemple.com/files/images/image.jpg?queryParam=value',
|
||||
);
|
||||
expect(res).toEqual('https://exemple.com/files/images/image.jpg');
|
||||
});
|
||||
});
|
||||
@@ -4,11 +4,15 @@ export const compareUrls = (
|
||||
firstAttachmentUrl: string,
|
||||
secondAttachmentUrl: string,
|
||||
): boolean => {
|
||||
const urlA = new URL(firstAttachmentUrl);
|
||||
const urlB = new URL(secondAttachmentUrl);
|
||||
if (urlA.hostname !== urlB.hostname) return false;
|
||||
return (
|
||||
getAttachmentPath(firstAttachmentUrl) ===
|
||||
getAttachmentPath(secondAttachmentUrl)
|
||||
);
|
||||
try {
|
||||
const urlA = new URL(firstAttachmentUrl);
|
||||
const urlB = new URL(secondAttachmentUrl);
|
||||
if (urlA.hostname !== urlB.hostname) return false;
|
||||
return (
|
||||
getAttachmentPath(firstAttachmentUrl) ===
|
||||
getAttachmentPath(secondAttachmentUrl)
|
||||
);
|
||||
} catch {
|
||||
return firstAttachmentUrl === secondAttachmentUrl;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
import { type Attachment } from '@/activities/files/types/Attachment';
|
||||
import { compareUrls } from '@/activities/utils/compareUrls';
|
||||
import { getAttachmentUrl } from '@/activities/utils/getAttachmentUrl';
|
||||
|
||||
export const filterAttachmentsToRestore = (
|
||||
attachmentPathsToRestore: string[],
|
||||
softDeletedAttachments: Attachment[],
|
||||
) => {
|
||||
export const filterAttachmentsToRestore = ({
|
||||
attachmentPathsToRestore,
|
||||
softDeletedAttachments,
|
||||
isFilesFieldMigrated,
|
||||
}: {
|
||||
attachmentPathsToRestore: string[];
|
||||
softDeletedAttachments: Attachment[];
|
||||
isFilesFieldMigrated: boolean;
|
||||
}) => {
|
||||
return softDeletedAttachments
|
||||
.filter((attachment) =>
|
||||
attachmentPathsToRestore.some((path) =>
|
||||
compareUrls(attachment.fullPath, path),
|
||||
compareUrls(
|
||||
getAttachmentUrl({ attachment, isFilesFieldMigrated }),
|
||||
path,
|
||||
),
|
||||
),
|
||||
)
|
||||
.map((attachment) => attachment.id);
|
||||
|
||||
+6
-1
@@ -4,11 +4,13 @@ import {
|
||||
type AttachmentInfo,
|
||||
getActivityAttachmentPathsAndName,
|
||||
} from '@/activities/utils/getActivityAttachmentPathsAndName';
|
||||
import { getAttachmentUrl } from '@/activities/utils/getAttachmentUrl';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const getActivityAttachmentIdsAndNameToUpdate = (
|
||||
newActivityBody: string,
|
||||
oldActivityAttachments: Attachment[] = [],
|
||||
isFilesFieldMigrated: boolean,
|
||||
) => {
|
||||
const activityAttachmentsNameAndPaths =
|
||||
getActivityAttachmentPathsAndName(newActivityBody);
|
||||
@@ -17,7 +19,10 @@ export const getActivityAttachmentIdsAndNameToUpdate = (
|
||||
return activityAttachmentsNameAndPaths.reduce(
|
||||
(acc: Partial<Attachment>[], activity: AttachmentInfo) => {
|
||||
const foundActivity = oldActivityAttachments.find((attachment) =>
|
||||
compareUrls(attachment.fullPath, activity.path),
|
||||
compareUrls(
|
||||
getAttachmentUrl({ attachment, isFilesFieldMigrated }),
|
||||
activity.path,
|
||||
),
|
||||
);
|
||||
if (isDefined(foundActivity) && foundActivity.name !== activity.name) {
|
||||
acc.push({ id: foundActivity.id, name: activity.name });
|
||||
|
||||
+6
-1
@@ -1,11 +1,13 @@
|
||||
import { type Attachment } from '@/activities/files/types/Attachment';
|
||||
import { compareUrls } from '@/activities/utils/compareUrls';
|
||||
import { getActivityAttachmentPathsAndName } from '@/activities/utils/getActivityAttachmentPathsAndName';
|
||||
import { getAttachmentUrl } from '@/activities/utils/getAttachmentUrl';
|
||||
|
||||
export const getActivityAttachmentIdsToDelete = (
|
||||
newActivityBody: string,
|
||||
oldActivityAttachments: Attachment[] = [],
|
||||
oldActivityBody: string,
|
||||
isFilesFieldMigrated: boolean,
|
||||
) => {
|
||||
if (oldActivityAttachments.length === 0) return [];
|
||||
|
||||
@@ -27,7 +29,10 @@ export const getActivityAttachmentIdsToDelete = (
|
||||
return oldActivityAttachments
|
||||
.filter((attachment) =>
|
||||
pathsToDelete.some((pathToDelete) =>
|
||||
compareUrls(attachment.fullPath, pathToDelete),
|
||||
compareUrls(
|
||||
getAttachmentUrl({ attachment, isFilesFieldMigrated }),
|
||||
pathToDelete,
|
||||
),
|
||||
),
|
||||
)
|
||||
.map((attachment) => attachment.id);
|
||||
|
||||
+6
-1
@@ -1,10 +1,12 @@
|
||||
import { type Attachment } from '@/activities/files/types/Attachment';
|
||||
import { compareUrls } from '@/activities/utils/compareUrls';
|
||||
import { getActivityAttachmentPathsAndName } from '@/activities/utils/getActivityAttachmentPathsAndName';
|
||||
import { getAttachmentUrl } from '@/activities/utils/getAttachmentUrl';
|
||||
|
||||
export const getActivityAttachmentPathsToRestore = (
|
||||
newActivityBody: string,
|
||||
oldActivityAttachments: Attachment[],
|
||||
isFilesFieldMigrated: boolean,
|
||||
) => {
|
||||
const newActivityAttachmentPaths =
|
||||
getActivityAttachmentPathsAndName(newActivityBody);
|
||||
@@ -13,7 +15,10 @@ export const getActivityAttachmentPathsToRestore = (
|
||||
.filter(
|
||||
(newActivity) =>
|
||||
!oldActivityAttachments.some((attachment) =>
|
||||
compareUrls(newActivity.path, attachment.fullPath),
|
||||
compareUrls(
|
||||
newActivity.path,
|
||||
getAttachmentUrl({ attachment, isFilesFieldMigrated }),
|
||||
),
|
||||
),
|
||||
)
|
||||
.map((activity) => activity.path);
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
export const getAttachmentPath = (attachmentFullPath: string) => {
|
||||
if (attachmentFullPath.includes('/files-field/')) {
|
||||
return attachmentFullPath?.split('?')[0];
|
||||
}
|
||||
|
||||
if (!attachmentFullPath.includes('/files/')) {
|
||||
return attachmentFullPath?.split('?')[0];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { type Attachment } from '@/activities/files/types/Attachment';
|
||||
|
||||
export const getAttachmentUrl = ({
|
||||
attachment,
|
||||
isFilesFieldMigrated,
|
||||
}: {
|
||||
attachment: Attachment;
|
||||
isFilesFieldMigrated: boolean;
|
||||
}): string => {
|
||||
if (isFilesFieldMigrated) {
|
||||
//TODO : add minimumFile settings + set it for attachment.file files field + exception invariance check here
|
||||
return attachment.file?.[0]?.url as string;
|
||||
}
|
||||
|
||||
return attachment.fullPath as string;
|
||||
};
|
||||
Reference in New Issue
Block a user