From 7628f9f5f858a3d09be80ec6c69708aa13e112f9 Mon Sep 17 00:00:00 2001 From: Alexander <98586297+Alexander-Kezik@users.noreply.github.com> Date: Wed, 3 Jul 2024 19:27:02 +0200 Subject: [PATCH] fix(chat): fix external entities export (Issue #1434) (#1720) --- .gitignore | 2 + .../conversationHistory/importPrompt.ts | 3 +- .../src/tests/chatExportImport.test.ts | 7 +- .../src/tests/promptExportImport.test.ts | 13 +- .../conversations/conversations.selectors.ts | 8 -- .../store/import-export/importExport.epics.ts | 85 ++---------- .../import-export/importExport.reducers.ts | 3 - apps/chat/src/utils/app/import-export.ts | 123 +----------------- 8 files changed, 30 insertions(+), 214 deletions(-) diff --git a/.gitignore b/.gitignore index 4c777080da..2b5467e7e2 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,8 @@ pnpm-lock.yaml /apps/chat-e2e/allure-overlay-results/ /apps/chat-e2e/auth/ /apps/chat-e2e/html-report/ +/apps/chat-e2e/chat-html-report/ +/apps/chat-e2e/overlay-html-report/ /apps/chat-e2e/src/testData/export/ /apps/chat-e2e/.env.local diff --git a/apps/chat-e2e/src/testData/conversationHistory/importPrompt.ts b/apps/chat-e2e/src/testData/conversationHistory/importPrompt.ts index e456519975..6d77dfb083 100644 --- a/apps/chat-e2e/src/testData/conversationHistory/importPrompt.ts +++ b/apps/chat-e2e/src/testData/conversationHistory/importPrompt.ts @@ -1,5 +1,4 @@ import { Prompt } from '@/chat/types/prompt'; -import { ImportPromtsResponse } from '@/chat/utils/app/import-export'; import { FolderPrompt } from '@/src/testData'; import { UploadDownloadData } from '@/src/ui/pages'; import { ItemUtil } from '@/src/utils'; @@ -25,7 +24,7 @@ export class ImportPrompt { importedPrompt.id = ItemUtil.getApiPromptId(importedPrompt); } - const folderPromptToImport: ImportPromtsResponse = { + const folderPromptToImport = { prompts: [importedPrompt], folders: importedFolder ? [importedFolder.folders] : [], isError: false, diff --git a/apps/chat-e2e/src/tests/chatExportImport.test.ts b/apps/chat-e2e/src/tests/chatExportImport.test.ts index 3ded843bdc..5cfa7ed75c 100644 --- a/apps/chat-e2e/src/tests/chatExportImport.test.ts +++ b/apps/chat-e2e/src/tests/chatExportImport.test.ts @@ -191,7 +191,6 @@ dialTest( async () => { await dialHomePage.openHomePage(); await dialHomePage.waitForPageLoaded(); - await chatBar.createNewFolder(); exportedData = await dialHomePage.downloadData(() => chatBar.exportButton.click(), ); @@ -199,7 +198,7 @@ dialTest( ); await dialTest.step( - 'Delete all conversations and folders, re-import again and verify they are displayed', + 'Delete all conversations and folders, re-import again and verify that all entities except empty folders are displayed', async () => { await chatBar.deleteAllEntities(); await confirmationDialog.confirm({ triggeredHttpMethod: 'DELETE' }); @@ -211,9 +210,9 @@ dialTest( folderConversations.getFolderByName( ExpectedConstants.newFolderWithIndexTitle(1), ), - ExpectedMessages.folderExpanded, + ExpectedMessages.folderIsNotVisible, ) - .toBeVisible(); + .toBeHidden(); await expect .soft( conversations.getConversationByName(conversationOutsideFolder.name), diff --git a/apps/chat-e2e/src/tests/promptExportImport.test.ts b/apps/chat-e2e/src/tests/promptExportImport.test.ts index f071ba8983..004d9434c6 100644 --- a/apps/chat-e2e/src/tests/promptExportImport.test.ts +++ b/apps/chat-e2e/src/tests/promptExportImport.test.ts @@ -89,7 +89,7 @@ dialTest( ); await dialTest.step( - 'Delete all prompts and folders, re-import again and verify they are displayed', + 'Delete all prompts and folders, re-import again and verify that all entities except empty folders are displayed', async () => { await promptBar.deleteAllEntities(); await confirmationDialog.confirm({ triggeredHttpMethod: 'DELETE' }); @@ -102,9 +102,14 @@ dialTest( await folderPrompts .getFolderByName(promptsInsideFolder.folders.name) .waitFor(); - await folderPrompts - .getFolderByName(ExpectedConstants.newFolderWithIndexTitle(1)) - .waitFor(); + await expect + .soft( + folderPrompts.getFolderByName( + ExpectedConstants.newFolderWithIndexTitle(1), + ), + ExpectedMessages.folderIsNotVisible, + ) + .toBeHidden(); await prompts.getPromptByName(promptOutsideFolder.name).waitFor(); diff --git a/apps/chat/src/store/conversations/conversations.selectors.ts b/apps/chat/src/store/conversations/conversations.selectors.ts index cc94556d00..74f137b0b9 100644 --- a/apps/chat/src/store/conversations/conversations.selectors.ts +++ b/apps/chat/src/store/conversations/conversations.selectors.ts @@ -53,14 +53,6 @@ export const selectConversations = createSelector( (state) => state.conversations, ); -export const selectExternalConversations = createSelector( - [(state: RootState) => state, selectConversations], - (state, conversations) => - conversations.filter((conversation) => - isEntityOrParentsExternal(state, conversation, FeatureType.Chat), - ), -); - export const selectPublishedOrSharedByMeConversations = createSelector( [selectConversations], (conversations) => conversations.filter((c) => c.isShared || c.isPublished), diff --git a/apps/chat/src/store/import-export/importExport.epics.ts b/apps/chat/src/store/import-export/importExport.epics.ts index e1e1830f02..d8fe7a2eb3 100644 --- a/apps/chat/src/store/import-export/importExport.epics.ts +++ b/apps/chat/src/store/import-export/importExport.epics.ts @@ -24,7 +24,6 @@ import { AnyAction } from '@reduxjs/toolkit'; import { combineEpics } from 'redux-observable'; import { - combineEntities, filterOnlyMyEntities, isImportEntityNameOnSameLevelUnique, } from '@/src/utils/app/common'; @@ -49,9 +48,7 @@ import { } from '@/src/utils/app/folders'; import { getFileRootId } from '@/src/utils/app/id'; import { - cleanConversationsFolders, cleanData, - cleanPromptsFolders, exportConversation, exportConversations, exportPrompt, @@ -176,26 +173,17 @@ const exportConversationsEpic: AppEpic = (action$, state$) => switchMap( () => ConversationService.getConversations(undefined, true), //listing of all entities ), - switchMap((conversationsListing) => { - const onlyMyConversationsListing = - filterOnlyMyEntities(conversationsListing); - const foldersIds = uniq( - onlyMyConversationsListing.map((info) => info.folderId), - ); - //calculate all folders; - const foldersWithConversation = getFoldersFromIds( + switchMap((conversations) => { + const foldersIds = uniq(conversations.map((info) => info.folderId)); + const folders = getFoldersFromIds( uniq(foldersIds.flatMap((id) => getParentFolderIdsFromFolderId(id))), FolderType.Chat, ); - const allFolders = ConversationsSelectors.selectFolders(state$.value); - - const folders = combineEntities(foldersWithConversation, allFolders); - return forkJoin({ //get all conversations from api conversations: zip( - onlyMyConversationsListing.map((info) => + conversations.map((info) => ConversationService.getConversation(info), ), ), @@ -260,26 +248,16 @@ const exportPromptsEpic: AppEpic = (action$, state$) => //listing of all entities PromptService.getPrompts(undefined, true), ), - switchMap((promptsListing) => { - const onlyMyPromptsListing = filterOnlyMyEntities(promptsListing); - const foldersIds = uniq( - onlyMyPromptsListing.map((info) => info.folderId), - ); - //calculate all folders; - const foldersWithPrompts = getFoldersFromIds( + switchMap((prompts) => { + const foldersIds = uniq(prompts.map((info) => info.folderId)); + const folders = getFoldersFromIds( uniq(foldersIds.flatMap((id) => getParentFolderIdsFromFolderId(id))), FolderType.Prompt, ); - const allFolders = PromptsSelectors.selectFolders(state$.value); - - const folders = combineEntities(foldersWithPrompts, allFolders); - return forkJoin({ //get all prompts from api - prompts: zip( - onlyMyPromptsListing.map((info) => PromptService.getPrompt(info)), - ), + prompts: zip(prompts.map((info) => PromptService.getPrompt(info))), folders: of(folders), }); }), @@ -389,17 +367,10 @@ const importConversationsEpic: AppEpic = (action$) => entities: conversationsListing, }); }); - const emptyFolders = folders.filter( - (folder) => - !preparedConversations.some( - (conv) => conv.folderId === folder.id, - ), - ); if (!existedImportNamesConversations.length) { return of( ImportExportActions.uploadImportedConversations({ itemsToUpload: nonExistedImportNamesConversations, - folders: emptyFolders, }), ); } @@ -423,7 +394,6 @@ const importConversationsEpic: AppEpic = (action$) => of( ImportExportActions.uploadImportedConversations({ itemsToUpload: nonExistedImportNamesConversations, - folders: emptyFolders, }), ), ); @@ -525,16 +495,10 @@ const importPromptsEpic: AppEpic = (action$) => }, ); - const emptyFolders = promptsHistory.folders.filter( - (folder) => - !preparedPrompts.some((conv) => conv.folderId === folder.id), - ); - if (!existedImportNamesPrompts.length) { return of( ImportExportActions.uploadImportedPrompts({ itemsToUpload: nonExistedImportNamesPrompts, - folders: emptyFolders, }), ); } @@ -555,12 +519,6 @@ const importPromptsEpic: AppEpic = (action$) => featureType: FeatureType.Prompt, }), ), - of( - ImportExportActions.uploadImportedPrompts({ - itemsToUpload: nonExistedImportNamesPrompts, - folders: emptyFolders, - }), - ), ); }), catchError(() => of(ImportExportActions.importPromptsFail())), @@ -599,25 +557,11 @@ const uploadImportedConversationsEpic: AppEpic = (action$, state$) => FolderType.Chat, ); - const cleanFolders = cleanConversationsFolders( - payload.folders ?? [], - ); - - const newFolders = combineEntities( - conversationsFolders, - cleanFolders, - ); - const firstImportedConversation = uploadedConversations[0]; const uploadedConversationsFoldersIds = uniq( uploadedConversations.map((info) => info.folderId), ); - - const importedFoldersIds = cleanFolders.map( - (folder) => folder.id, - ); - const openedFolderIds = UISelectors.selectOpenedFoldersIds( state$.value, FeatureType.Chat, @@ -635,7 +579,7 @@ const uploadImportedConversationsEpic: AppEpic = (action$, state$) => of( ConversationsActions.importConversationsSuccess({ conversations: conversationsListing, - folders: newFolders, + folders: conversationsFolders, }), ), of( @@ -647,7 +591,7 @@ const uploadImportedConversationsEpic: AppEpic = (action$, state$) => UIActions.setOpenedFoldersIds({ openedFolderIds: uniq([ ...uploadedConversationsFoldersIds, - ...importedFoldersIds, + ...conversationsFolders.map((folder) => folder.id), ...openedFolderIds, ]), featureType: FeatureType.Chat, @@ -684,7 +628,8 @@ const uploadImportedPromptsEpic: AppEpic = (action$, state$) => action$.pipe( filter(ImportExportActions.uploadImportedPrompts.match), switchMap(({ payload }) => { - const { itemsToUpload, folders } = payload; + const { itemsToUpload } = payload; + return from(PromptService.setPrompts(itemsToUpload)).pipe( toArray(), switchMap(() => { @@ -707,10 +652,6 @@ const uploadImportedPromptsEpic: AppEpic = (action$, state$) => FolderType.Prompt, ); - const cleanFolders = cleanPromptsFolders(folders ?? []); - - const newFolders = combineEntities(promptsFolders, cleanFolders); - const numberOfRunningOperations = ImportExportSelectors.selectNumberOfRunningOperations( state$.value, @@ -723,7 +664,7 @@ const uploadImportedPromptsEpic: AppEpic = (action$, state$) => of( PromptsActions.importPromptsSuccess({ prompts: promptsListing, - folders: newFolders, + folders: promptsFolders, }), ), diff --git a/apps/chat/src/store/import-export/importExport.reducers.ts b/apps/chat/src/store/import-export/importExport.reducers.ts index 3e302bf27f..30da1c9938 100644 --- a/apps/chat/src/store/import-export/importExport.reducers.ts +++ b/apps/chat/src/store/import-export/importExport.reducers.ts @@ -3,7 +3,6 @@ import { PayloadAction, createSelector, createSlice } from '@reduxjs/toolkit'; import { Conversation, ConversationInfo } from '@/src/types/chat'; import { FeatureType, UploadStatus } from '@/src/types/common'; import { DialFile } from '@/src/types/files'; -import { FolderInterface } from '@/src/types/folder'; import { LatestExportFormat, Operation, @@ -152,7 +151,6 @@ export const importExportSlice = createSlice({ state, _action: PayloadAction<{ itemsToUpload: Conversation[]; - folders?: FolderInterface[]; }>, ) => { state.numberOfRunningOperations = state.numberOfRunningOperations + 1; @@ -169,7 +167,6 @@ export const importExportSlice = createSlice({ state, _action: PayloadAction<{ itemsToUpload: Prompt[]; - folders?: FolderInterface[]; }>, ) => { state.numberOfRunningOperations = state.numberOfRunningOperations + 1; diff --git a/apps/chat/src/utils/app/import-export.ts b/apps/chat/src/utils/app/import-export.ts index b5c7924035..b90efd9a1d 100644 --- a/apps/chat/src/utils/app/import-export.ts +++ b/apps/chat/src/utils/app/import-export.ts @@ -1,5 +1,4 @@ -import { Attachment, Conversation, ConversationInfo } from '@/src/types/chat'; -import { FeatureType } from '@/src/types/common'; +import { Attachment, Conversation } from '@/src/types/chat'; import { FolderInterface, FolderType } from '@/src/types/folder'; import { ExportFormatV1, @@ -20,10 +19,9 @@ import { PLOTLY_CONTENT_TYPE } from '@/src/constants/chat'; import { ApiUtils } from '../server/api'; import { cleanConversationHistory } from './clean'; -import { combineEntities, prepareEntityName } from './common'; import { constructPath, triggerDownload } from './file'; import { splitEntityId } from './folders'; -import { getConversationRootId, getRootId } from './id'; +import { getConversationRootId } from './id'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export function isExportFormatV1(obj: any): obj is ExportFormatV1 { @@ -60,51 +58,6 @@ export interface CleanDataResponse extends LatestExportFormat { isError: boolean; } -export function cleanFolders({ - folders, - featureType, - folderType, -}: { - folders: FolderInterface[]; - folderType: FolderType; - featureType: FeatureType; -}) { - return (folders || []).map((folder) => { - const parentFolder = folders.find((parentFolder) => { - return parentFolder.id === folder.folderId; - }); - const newFolderId = constructPath( - getRootId({ featureType }), - parentFolder?.name, - ); - - const newName = prepareEntityName(folder.name, { - trimEndDotsRequired: true, - }); - const newId = constructPath(newFolderId, newName); - return { - id: newId, - name: newName, - type: folderType, - folderId: newFolderId, - }; - }); -} - -export const cleanConversationsFolders = (folders: FolderInterface[]) => - cleanFolders({ - folders, - folderType: FolderType.Chat, - featureType: FeatureType.Chat, - }); - -export const cleanPromptsFolders = (folders: FolderInterface[]) => - cleanFolders({ - folders, - folderType: FolderType.Prompt, - featureType: FeatureType.Prompt, - }); - export function cleanData(data: SupportedExportFormats): CleanDataResponse { if (isExportFormatV1(data)) { const cleanHistoryData: LatestExportFormat = { @@ -302,78 +255,6 @@ export const exportPrompt = ( triggerDownloadPrompt(data, appName); }; -export interface ImportConversationsResponse { - history: ConversationInfo[]; - folders: FolderInterface[]; - isError: boolean; -} - -export const importConversations = ( - importedData: SupportedExportFormats, - { - currentConversations, - currentFolders, - }: { - currentConversations: ConversationInfo[]; - currentFolders: FolderInterface[]; - }, -): ImportConversationsResponse => { - const { history, folders, isError } = cleanData(importedData); - - const newHistory: ConversationInfo[] = combineEntities( - currentConversations, - history, - ); - - const newFolders: FolderInterface[] = combineEntities( - currentFolders, - folders, - ).filter((folder) => folder.type === FolderType.Chat); - - return { - history: newHistory, - folders: newFolders, - isError, - }; -}; - -export interface ImportPromtsResponse { - prompts: Prompt[]; - folders: FolderInterface[]; - isError: boolean; -} - -export const importPrompts = ( - importedData: PromptsHistory, - { - currentPrompts, - currentFolders, - }: { - currentPrompts: Prompt[]; - currentFolders: FolderInterface[]; - }, -): ImportPromtsResponse => { - if (!isPromptsFormat(importedData)) { - return { - prompts: currentPrompts, - folders: currentFolders, - isError: true, - }; - } - - const newPrompts: Prompt[] = combineEntities( - currentPrompts, - importedData.prompts, - ); - - const newFolders: FolderInterface[] = combineEntities( - currentFolders, - cleanPromptsFolders(importedData.folders), - ).filter((folder) => folder.type === FolderType.Prompt); - - return { prompts: newPrompts, folders: newFolders, isError: false }; -}; - export const updateAttachment = ({ oldAttachment, uploadedAttachments,