diff --git a/packages/hoppscotch-selfhost-web/src/platform/collections/collections.mapper.ts b/packages/hoppscotch-selfhost-web/src/platform/collections/collections.mapper.ts deleted file mode 100644 index 3316426dc..000000000 --- a/packages/hoppscotch-selfhost-web/src/platform/collections/collections.mapper.ts +++ /dev/null @@ -1,456 +0,0 @@ -import { - graphqlCollectionStore, - navigateToFolderWithIndexPath, - restCollectionStore, -} from "@hoppscotch/common/newstore/collections" -import { createMapper } from "../../lib/sync/mapper" -import { - restCollectionsMapper, - collectionReorderOrMovingOperations, - restRequestsMapper, -} from "./collections.sync" -import { gqlCollectionsMapper, gqlRequestsMapper } from "./gqlCollections.sync" - -function reorderItems(array: unknown[], from: number, to: number) { - const item = array.splice(from, 1)[0] - if (from < to) { - array.splice(to - 1, 0, item) - } else { - array.splice(to, 0, item) - } -} - -type RequestType = "REST" | "GQL" - -export function moveCollectionInMapper( - folderPath: string, - destinationPath?: string, - collectionType: RequestType = "REST" -) { - const indexes = folderPath.split("/") - indexes.pop() - const collectionPath = indexes.join("/") - - const { collectionsMapper, requestsMapper, collectionStore } = - getMappersAndStoreByType(collectionType) - - // Store the backend id of the folder to move for adding it to the destinationPath - const collectionToMoveBackendID = - collectionsMapper.getBackendIDByLocalID(folderPath) - - // Remove the request from its current position - collectionsMapper.removeEntry(undefined, folderPath) - - // We are assuming moveRequestInMapper is called after the item is moved in the store, - // so we'll fetch the index of the last added item + 1 to add to the mapper - // but in the case of the same parent, the destinationPath will change - // eg: - // 0. Collection 0 - // 1. Collection 1 - // in the above example, if we move Collection 0 to Collection 1 ( folderPath: 0, destinationPath: 1 ), - // the effective index of Collection 1, when using navigateToFolderWithIndexPath will be 0 - // so we check if the moving is between same parent folders / collections and adds a workaround for this - const isSameParentPath = - getParentPathFromPath(folderPath) == getParentPathFromPath(destinationPath) - - let changedDestinationPath: string | undefined - - if (isSameParentPath) { - const lastFolderPathIndex = folderPath.split("/").pop() - const folderIndex = lastFolderPathIndex && parseInt(lastFolderPathIndex) - - const lastDestinationPathIndex = - destinationPath && destinationPath.split("/").pop() - const destinationIndex = - destinationPath && - lastDestinationPathIndex && - parseInt(lastDestinationPathIndex) - - if ( - (folderIndex == 0 || folderIndex) && - (destinationIndex == 0 || destinationIndex) && - folderIndex < destinationIndex - ) { - const destinationParentPath = getParentPathFromPath(destinationPath) - changedDestinationPath = destinationParentPath - ? `${destinationParentPath}/${destinationIndex - 1}` - : `${destinationIndex - 1}` - } - } - - const destinationFolder = - changedDestinationPath && - navigateToFolderWithIndexPath( - collectionStore.value.state, - changedDestinationPath.split("/").map((pathIndex) => parseInt(pathIndex)) - ) - - const destinationCollectionID = - destinationPath && collectionsMapper.getBackendIDByLocalID(destinationPath) - - if (destinationFolder && collectionToMoveBackendID) { - const destinationIndex = destinationFolder.folders.length - - const newPath = `${destinationPath}/${destinationIndex}` - collectionsMapper.addEntry(newPath, collectionToMoveBackendID) - - changeParentForAllChildrenFromMapper(folderPath, newPath, collectionType) - - collectionToMoveBackendID && - collectionReorderOrMovingOperations.push({ - sourceCollectionID: collectionToMoveBackendID, - destinationCollectionID, - reorderOperation: { - fromPath: folderPath, - toPath: `${changedDestinationPath}/${destinationIndex}`, - }, - }) - } - - // destinationPath won't be there, when moving to the root - if (!destinationPath && collectionToMoveBackendID) { - const destinationIndex = collectionStore.value.state.length - const newPath = `${destinationIndex}` - - collectionsMapper.addEntry(newPath, collectionToMoveBackendID) - - changeParentForAllChildrenFromMapper(folderPath, newPath, collectionType) - } - - reorderIndexesAfterEntryRemoval( - collectionPath, - collectionsMapper, - collectionType - ) - reorderIndexesAfterEntryRemoval( - collectionPath, - requestsMapper, - collectionType - ) -} - -export function moveRequestInMapper( - requestIndex: number, - path: string, - destinationPath: string, - requestType: RequestType -) { - const { collectionStore, requestsMapper } = - getMappersAndStoreByType(requestType) - - // Store the backend id of the request to move for adding it to the destinationPath - const requestToMoveBackendID = requestsMapper.getBackendIDByLocalID( - `${path}/${requestIndex}` - ) - - // Remove the request from its current position - requestsMapper.removeEntry(undefined, `${path}/${requestIndex}`) - reorderIndexesAfterEntryRemoval(path, requestsMapper, requestType) - - // We are assuming moveRequestInMapper is called after the item is moved in the store, - // so we'll fetch the index of the last added item + 1 to add to the mapper - const destinationFolder = navigateToFolderWithIndexPath( - collectionStore.value.state, - destinationPath.split("/").map((pathIndex) => parseInt(pathIndex)) - ) - - if (destinationFolder && requestToMoveBackendID) { - const destinationIndex = destinationFolder.requests.length - - requestsMapper.addEntry( - `${destinationPath}/${destinationIndex}`, - requestToMoveBackendID - ) - } -} - -// we allow reordering in the same parent collection right now -export function reorderRequestsMapper( - requestIndex: number, - path: string, - nextRequestIndex: number, - requestType: RequestType -) { - const { requestsMapper } = getMappersAndStoreByType(requestType) - - const directChildren = getDirectChildrenEntriesFromMapper( - path, - requestsMapper - ) - - reorderItems(directChildren, requestIndex, nextRequestIndex) - - directChildren.forEach((item, index) => { - item[1] && requestsMapper.addEntry(`${path}/${index}`, item[1]) - }) -} -// we allow reordering in the same parent collection right now - -export function reorderCollectionsInMapper( - collectionPath: string, - destinationCollectionPath: string, - requestType: RequestType -) { - const { requestsMapper, collectionsMapper } = - getMappersAndStoreByType(requestType) - - const indexes = collectionPath.split("/") - indexes.pop() - const parentCollectionPath = indexes.join("/") - - const directChildren = getDirectChildrenEntriesFromMapper( - parentCollectionPath, - collectionsMapper - ) - - const collectionIndex = collectionPath.split("/").pop() - const destinationIndex = destinationCollectionPath.split("/").pop() - - collectionIndex && - destinationIndex && - reorderItems( - directChildren, - parentCollectionPath - ? parseInt(collectionIndex) - : parseInt(collectionPath), - parentCollectionPath - ? parseInt(destinationIndex) - : parseInt(destinationCollectionPath) - ) - - const previousCollectionEntries: Record< - string, - [string, string | undefined][] - > = {} - - const previousRequestEntries: Record = - {} - - directChildren.forEach(([path, backendID], index) => { - const newPath = parentCollectionPath - ? `${parentCollectionPath}/${index}` - : `${index}` - - const indexes = path.split("/") - const childIndex = indexes.pop() - - if (childIndex && index != parseInt(childIndex)) { - backendID && collectionsMapper.addEntry(newPath, backendID) - - const existingCollectionsOnNewPath = getChildrenEntriesFromMapper( - newPath, - collectionsMapper - ) - - const existingRequestsOnNewPath = getChildrenEntriesFromMapper( - newPath, - requestsMapper - ) - - previousCollectionEntries[newPath] = existingCollectionsOnNewPath - previousRequestEntries[newPath] = existingRequestsOnNewPath - - removeAllChildCollectionsFromMapper(newPath, requestType) - removeAllChildRequestsFromMapper(newPath, requestType) - - if (path in previousCollectionEntries && path in previousRequestEntries) { - previousCollectionEntries[path].forEach(([previousPath, backendID]) => { - const pattern = new RegExp(`^(${path})\/`) - - const updatedPath = previousPath.replace(pattern, `${newPath}/`) - - backendID && collectionsMapper.addEntry(updatedPath, backendID) - }) - - previousRequestEntries[path].forEach(([previousPath, backendID]) => { - const pattern = new RegExp(`^(${path})\/`) - - const updatedPath = previousPath.replace(pattern, `${newPath}/`) - - backendID && requestsMapper.addEntry(updatedPath, backendID) - }) - } else { - changeParentForAllChildrenFromMapper(path, newPath, requestType) - } - } - }) -} - -export function removeAndReorderEntries( - localIndex: string, - collectionType: RequestType -) { - const { collectionsMapper, requestsMapper } = - getMappersAndStoreByType(collectionType) - - // get the collectionPath from the localIndex - const indexes = localIndex.split("/") - indexes.pop() - const collectionPath = indexes.join("/") - - collectionsMapper.removeEntry(undefined, localIndex) - - removeAllChildCollectionsFromMapper(localIndex, collectionType) - removeAllChildRequestsFromMapper(localIndex, collectionType) - - reorderIndexesAfterEntryRemoval( - collectionPath, - collectionsMapper, - collectionType - ) - reorderIndexesAfterEntryRemoval( - collectionPath, - requestsMapper, - collectionType - ) -} - -export function removeAllChildRequestsFromMapper( - collectionPath: string, - requestType: RequestType -) { - const { requestsMapper } = getMappersAndStoreByType(requestType) - - const childRequestMapperEntries = getChildrenEntriesFromMapper( - collectionPath, - requestsMapper - ) - - childRequestMapperEntries.forEach(([path]) => { - typeof path == "string" && requestsMapper.removeEntry(undefined, path) - }) -} - -export function removeAllChildCollectionsFromMapper( - collectionPath: string, - collectionType: RequestType -) { - const { collectionsMapper } = getMappersAndStoreByType(collectionType) - - const childCollectionMapperEntries = getChildrenEntriesFromMapper( - collectionPath, - collectionsMapper - ) - - childCollectionMapperEntries.forEach(([path]) => { - typeof path == "string" && collectionsMapper.removeEntry(undefined, path) - }) -} - -export function changeParentForAllChildrenFromMapper( - currentParentPath: string, - newParentPath: string, - collectionType: RequestType -) { - const { collectionsMapper, requestsMapper } = - getMappersAndStoreByType(collectionType) - - const childCollectionsMapperEntries = getChildrenEntriesFromMapper( - currentParentPath, - collectionsMapper - ) - - const childRequestsMapperEntries = getChildrenEntriesFromMapper( - currentParentPath, - requestsMapper - ) - - const pattern = new RegExp(`^(${currentParentPath})`) - - childCollectionsMapperEntries.forEach(([path, backendID]) => { - const newPath = - typeof path == "string" && path.replace(pattern, newParentPath) - - if (newPath && typeof backendID == "string") { - collectionsMapper.removeEntry(undefined, path) - collectionsMapper.addEntry(newPath, backendID) - } - }) - - childRequestsMapperEntries.forEach(([path, backendID]) => { - const newPath = - typeof path == "string" && path.replace(pattern, newParentPath) - - if (newPath && typeof backendID == "string") { - requestsMapper.removeEntry(undefined, path) - requestsMapper.addEntry(newPath, backendID) - } - }) -} - -export function getChildrenEntriesFromMapper( - path: string, - mapper: ReturnType> -) { - let mapperEntries = Array.from(mapper.getValue().entries()) - - // if there are no path( eg: "" ), all the entries are children, so return the entire mapperEntries without filtering - if (!path) return mapperEntries - - mapperEntries = mapperEntries.filter((entry) => { - const pattern = new RegExp(`^${path}\/(\\w+)\/?.*$`) - - return !!(typeof entry[0] == "string" && entry[0].match(pattern)) - }) - - return mapperEntries -} - -export function getDirectChildrenEntriesFromMapper( - path: string, - mapper: ReturnType> -) { - let mapperEntries = Array.from(mapper.getValue().entries()) - - mapperEntries = mapperEntries.filter((entry) => { - const pattern = new RegExp(path ? `^${path}\/\\d+$` : `^\\d+$`) - - return !!(typeof entry[0] == "string" && entry[0].match(pattern)) - }) - - return mapperEntries -} - -export function reorderIndexesAfterEntryRemoval( - pathToReorder: string, - mapper: ReturnType>, - requestType: RequestType -) { - const directChildren = getDirectChildrenEntriesFromMapper( - pathToReorder, - mapper - ) - - directChildren.forEach(([path, backendID], index) => { - const indexes = path.split("/").map((index) => parseInt(index)) - const childIndex = indexes.pop() - const collectionPath = indexes.join("/") - - if (childIndex != index && backendID) { - const newPath = collectionPath ? `${collectionPath}/${index}` : `${index}` - - mapper.removeEntry(undefined, path) - mapper.addEntry(newPath, backendID) - changeParentForAllChildrenFromMapper(path, newPath, requestType) - } - }) -} - -function getParentPathFromPath(path: string | undefined) { - const indexes = path ? path.split("/") : [] - indexes.pop() - - return indexes.join("/") -} - -export function getMappersAndStoreByType(type: "GQL" | "REST") { - const isGQL = type == "GQL" - - const collectionsMapper = isGQL ? gqlCollectionsMapper : restCollectionsMapper - - const requestsMapper = isGQL ? gqlRequestsMapper : restRequestsMapper - - const collectionStore = isGQL ? graphqlCollectionStore : restCollectionStore - - return { collectionsMapper, requestsMapper, collectionStore } -} diff --git a/packages/hoppscotch-selfhost-web/src/platform/collections/collections.platform.ts b/packages/hoppscotch-selfhost-web/src/platform/collections/collections.platform.ts index 118bc326d..59d3ce965 100644 --- a/packages/hoppscotch-selfhost-web/src/platform/collections/collections.platform.ts +++ b/packages/hoppscotch-selfhost-web/src/platform/collections/collections.platform.ts @@ -14,20 +14,7 @@ import { runUserRequestMovedSubscription, runUserRequestUpdatedSubscription, } from "./collections.api" -import { - collectionReorderOrMovingOperations, - collectionsSyncer, - restCollectionsOperations, - restCollectionsMapper, - restRequestsMapper, -} from "./collections.sync" -import { - moveCollectionInMapper, - removeAndReorderEntries, - reorderIndexesAfterEntryRemoval, - reorderCollectionsInMapper, - getMappersAndStoreByType, -} from "./collections.mapper" +import { collectionsSyncer, getStoreByCollectionType } from "./collections.sync" import * as E from "fp-ts/Either" import { @@ -57,6 +44,7 @@ import { moveGraphqlRequest, removeGraphqlRequest, setGraphqlCollections, + restCollectionStore, } from "@hoppscotch/common/newstore/collections" import { runGQLSubscription } from "@hoppscotch/common/helpers/backend/GQLClient" import { @@ -64,10 +52,7 @@ import { HoppGQLRequest, HoppRESTRequest, } from "@hoppscotch/data" -import { - gqlCollectionsOperations, - gqlCollectionsSyncer, -} from "./gqlCollections.sync" +import { gqlCollectionsSyncer } from "./gqlCollections.sync" import { ReqType } from "../../api/generated/graphql" function initCollectionsSync() { @@ -77,14 +62,14 @@ function initCollectionsSync() { gqlCollectionsSyncer.startStoreSync() - loadUserRootCollections("REST") - loadUserRootCollections("GQL") + loadUserCollections("REST") + loadUserCollections("GQL") // TODO: test & make sure the auth thing is working properly currentUser$.subscribe(async (user) => { if (user) { - loadUserRootCollections("REST") - loadUserRootCollections("GQL") + loadUserCollections("REST") + loadUserCollections("GQL") } }) @@ -121,6 +106,7 @@ function exportedCollectionToHoppCollection( const restCollection = collection as ExportedUserCollectionREST return { + id: restCollection.id, v: 1, name: restCollection.name, folders: restCollection.folders.map((folder) => @@ -128,6 +114,7 @@ function exportedCollectionToHoppCollection( ), requests: restCollection.requests.map( ({ + id, v, auth, body, @@ -139,6 +126,7 @@ function exportedCollectionToHoppCollection( preRequestScript, testScript, }) => ({ + id, v, auth, body, @@ -156,49 +144,26 @@ function exportedCollectionToHoppCollection( const gqlCollection = collection as ExportedUserCollectionGQL return { + id: gqlCollection.id, v: 1, name: gqlCollection.name, folders: gqlCollection.folders.map((folder) => exportedCollectionToHoppCollection(folder, collectionType) ), - requests: gqlCollection.requests.map(({ v, auth, headers, name }) => ({ - v, - auth, - headers, - name, - })) as HoppGQLRequest[], + requests: gqlCollection.requests.map( + ({ v, auth, headers, name, id }) => ({ + id, + v, + auth, + headers, + name, + }) + ) as HoppGQLRequest[], } } } -function addMapperEntriesForExportedCollection( - collection: ExportedUserCollectionREST | ExportedUserCollectionGQL, - localPath: string, - collectionType: "REST" | "GQL" -) { - const { collectionsMapper, requestsMapper } = - getMappersAndStoreByType(collectionType) - - if (collection.id) { - collectionsMapper.addEntry(localPath, collection.id) - - collection.folders.forEach((folder, index) => { - addMapperEntriesForExportedCollection( - folder, - `${localPath}/${index}`, - collectionType - ) - }) - - collection.requests.forEach((request, index) => { - const requestID = request.id - - requestID && requestsMapper.addEntry(`${localPath}/${index}`, requestID) - }) - } -} - -async function loadUserRootCollections(collectionType: "REST" | "GQL") { +async function loadUserCollections(collectionType: "REST" | "GQL") { const res = await exportUserCollectionsToJSON( undefined, collectionType == "REST" ? ReqType.Rest : ReqType.Gql @@ -233,14 +198,6 @@ async function loadUserRootCollections(collectionType: "REST" | "GQL") { ) as HoppCollection ) ) - - exportedCollections.forEach((collection, index) => - addMapperEntriesForExportedCollection( - collection, - `${index}`, - collectionType - ) - ) }) } } @@ -284,14 +241,14 @@ function setupUserCollectionCreatedSubscription() { if (E.isRight(res)) { const collectionType = res.right.userCollectionCreated.type - const { collectionsMapper, collectionStore } = - getMappersAndStoreByType(collectionType) + const { collectionStore } = getStoreByCollectionType(collectionType) const userCollectionBackendID = res.right.userCollectionCreated.id const parentCollectionID = res.right.userCollectionCreated.parent?.id - const userCollectionLocalID = collectionsMapper.getLocalIDByBackendID( - userCollectionBackendID + const userCollectionLocalID = getCollectionPathFromCollectionID( + userCollectionBackendID, + collectionStore.value.state ) // collection already exists in store ( this instance created it ) @@ -301,7 +258,10 @@ function setupUserCollectionCreatedSubscription() { const parentCollectionPath = parentCollectionID && - collectionsMapper.getLocalIDByBackendID(parentCollectionID) + getCollectionPathFromCollectionID( + parentCollectionID, + collectionStore.value.state + ) // only folders will have parent collection id if (parentCollectionID && parentCollectionPath) { @@ -325,10 +285,9 @@ function setupUserCollectionCreatedSubscription() { if (parentCollection) { const folderIndex = parentCollection.folders.length - 1 - collectionsMapper.addEntry( - `${parentCollectionPath}/${folderIndex}`, - userCollectionBackendID - ) + + const addedFolder = parentCollection.folders[folderIndex] + addedFolder.id = userCollectionBackendID } }) } else { @@ -349,7 +308,9 @@ function setupUserCollectionCreatedSubscription() { }) const localIndex = collectionStore.value.state.length - 1 - collectionsMapper.addEntry(`${localIndex}`, userCollectionBackendID) + + const addedCollection = collectionStore.value.state[localIndex] + addedCollection.id = userCollectionBackendID }) } } @@ -366,11 +327,13 @@ function setupUserCollectionUpdatedSubscription() { if (E.isRight(res)) { const collectionType = res.right.userCollectionUpdated.type - const { collectionsMapper } = getMappersAndStoreByType(collectionType) + const { collectionStore } = getStoreByCollectionType(collectionType) const updatedCollectionBackendID = res.right.userCollectionUpdated.id - const updatedCollectionLocalPath = - collectionsMapper.getLocalIDByBackendID(updatedCollectionBackendID) + const updatedCollectionLocalPath = getCollectionPathFromCollectionID( + updatedCollectionBackendID, + collectionStore.value.state + ) const isFolder = updatedCollectionLocalPath && @@ -415,37 +378,25 @@ function setupUserCollectionMovedSubscription() { if (E.isRight(res)) { const movedMetadata = res.right.userCollectionMoved - const sourcePath = restCollectionsMapper.getLocalIDByBackendID( - movedMetadata.id + const sourcePath = getCollectionPathFromCollectionID( + movedMetadata.id, + restCollectionStore.value.state ) let destinationPath: string | undefined if (movedMetadata.parent?.id) { - destinationPath = restCollectionsMapper.getLocalIDByBackendID( - movedMetadata.parent?.id - ) + destinationPath = + getCollectionPathFromCollectionID( + movedMetadata.parent?.id, + restCollectionStore.value.state + ) ?? undefined } - const hasAlreadyHappened = hasReorderingOrMovingAlreadyHappened( - { - sourceCollectionID: movedMetadata.id, - destinationCollectionID: movedMetadata.parent?.id, - sourcePath, - destinationPath, - }, - "MOVING" - ) - - if (!hasAlreadyHappened) { - sourcePath && - runDispatchWithOutSyncing(() => { - moveRESTFolder(sourcePath, destinationPath ?? null) - }) - - sourcePath && - moveCollectionInMapper(sourcePath, destinationPath, "REST") - } + sourcePath && + runDispatchWithOutSyncing(() => { + moveRESTFolder(sourcePath, destinationPath ?? null) + }) } }) @@ -461,28 +412,13 @@ function setupUserCollectionRemovedSubscription() { const removedCollectionBackendID = res.right.userCollectionRemoved.id const collectionType = res.right.userCollectionRemoved.type - const { collectionsMapper } = getMappersAndStoreByType(collectionType) + const { collectionStore } = getStoreByCollectionType(collectionType) - const collectionsOperations = - collectionType == "REST" - ? restCollectionsOperations - : gqlCollectionsOperations - - const removedCollectionLocalPath = - collectionsMapper.getLocalIDByBackendID(removedCollectionBackendID) - - // TODO: seperate operations for rest and gql - const isInOperations = !!collectionsOperations.find( - (operation) => - operation.type == "COLLECTION_REMOVED" && - operation.collectionBackendID == removedCollectionBackendID + const removedCollectionLocalPath = getCollectionPathFromCollectionID( + removedCollectionBackendID, + collectionStore.value.state ) - // the collection is already removed - if (!removedCollectionLocalPath || isInOperations) { - return - } - const isFolder = removedCollectionLocalPath && removedCollectionLocalPath.split("/").length > 1 @@ -502,9 +438,6 @@ function setupUserCollectionRemovedSubscription() { : removeGraphqlCollection(parseInt(removedCollectionLocalPath)) }) } - - removedCollectionLocalPath && - removeAndReorderEntries(removedCollectionLocalPath, collectionType) } }) @@ -523,40 +456,25 @@ function setupUserCollectionOrderUpdatedSubscription() { const sourceCollectionID = userCollection.id const destinationCollectionID = nextUserCollection?.id - const sourcePath = - restCollectionsMapper.getLocalIDByBackendID(sourceCollectionID) + const sourcePath = getCollectionPathFromCollectionID( + sourceCollectionID, + restCollectionStore.value.state + ) - let destinationPath: string | undefined + let destinationPath: string | null | undefined if (destinationCollectionID) { - destinationPath = restCollectionsMapper.getLocalIDByBackendID( - destinationCollectionID + destinationPath = getCollectionPathFromCollectionID( + destinationCollectionID, + restCollectionStore.value.state ) } - const hasAlreadyHappened = hasReorderingOrMovingAlreadyHappened( - { - sourceCollectionID, - destinationCollectionID, - sourcePath, - destinationPath, - }, - "REORDERING" - ) - - if (!hasAlreadyHappened) { - runDispatchWithOutSyncing(() => { - if ( - sourcePath && - destinationPath && - sourceCollectionID && - destinationCollectionID - ) { - updateRESTCollectionOrder(sourcePath, destinationPath) - reorderCollectionsInMapper(sourcePath, destinationPath, "REST") - } - }) - } + runDispatchWithOutSyncing(() => { + if (sourcePath) { + updateRESTCollectionOrder(sourcePath, destinationPath ?? null) + } + }) } }) @@ -575,18 +493,21 @@ function setupUserRequestCreatedSubscription() { const requestType = res.right.userRequestCreated.type - const { collectionsMapper, requestsMapper, collectionStore } = - getMappersAndStoreByType(requestType) + const { collectionStore } = getStoreByCollectionType(requestType) - const hasAlreadyHappened = - !!requestsMapper.getLocalIDByBackendID(requestID) + const hasAlreadyHappened = getRequestPathFromRequestID( + requestID, + collectionStore.value.state + ) - if (hasAlreadyHappened) { + if (!!hasAlreadyHappened) { return } - const collectionPath = - collectionsMapper.getLocalIDByBackendID(collectionID) + const collectionPath = getCollectionPathFromCollectionID( + collectionID, + collectionStore.value.state + ) if (collectionID && collectionPath) { runDispatchWithOutSyncing(() => { @@ -599,10 +520,11 @@ function setupUserRequestCreatedSubscription() { collectionPath.split("/").map((index) => parseInt(index)) ) - const requestPath = - target && `${collectionPath}/${target?.requests.length - 1}` + const targetRequest = target?.requests[target?.requests.length - 1] - requestPath && requestsMapper.addEntry(requestPath, requestID) + if (targetRequest) { + targetRequest.id = requestID + } }) } } @@ -619,31 +541,28 @@ function setupUserRequestUpdatedSubscription() { if (E.isRight(res)) { const requestType = res.right.userRequestUpdated.type - const { requestsMapper, collectionsMapper } = - getMappersAndStoreByType(requestType) + const { collectionStore } = getStoreByCollectionType(requestType) - const requestPath = requestsMapper.getLocalIDByBackendID( - res.right.userRequestUpdated.id + const requestPath = getRequestPathFromRequestID( + res.right.userRequestUpdated.id, + collectionStore.value.state ) - const indexes = requestPath?.split("/") - const requestIndex = indexes && indexes[indexes?.length - 1] - const requestParentPath = collectionsMapper.getLocalIDByBackendID( - res.right.userRequestUpdated.collectionID - ) + const collectionPath = requestPath?.collectionPath + const requestIndex = requestPath?.requestIndex - requestIndex && - requestParentPath && + ;(requestIndex || requestIndex == 0) && + collectionPath && runDispatchWithOutSyncing(() => { requestType == "REST" ? editRESTRequest( - requestParentPath, - parseInt(requestIndex), + collectionPath, + requestIndex, JSON.parse(res.right.userRequestUpdated.request) ) : editGraphqlRequest( - requestParentPath, - parseInt(requestIndex), + collectionPath, + requestIndex, JSON.parse(res.right.userRequestUpdated.request) ) }) @@ -659,40 +578,58 @@ function setupUserRequestMovedSubscription() { userRequestMoved$.subscribe((res) => { if (E.isRight(res)) { - const requestType = res.right.userRequestMoved.request.type + const { request, nextRequest } = res.right.userRequestMoved - const { collectionsMapper } = getMappersAndStoreByType(requestType) + const { + collectionID: destinationCollectionID, + id: sourceRequestID, + type: requestType, + } = request - const requestID = res.right.userRequestMoved.request.id - const requestIndex = getRequestIndexFromRequestID(requestID) + const { collectionStore } = getStoreByCollectionType(requestType) - const sourceCollectionPath = getCollectionPathFromRequestID(requestID) - - const destinationCollectionID = - res.right.userRequestMoved.request.collectionID - const destinationCollectionPath = collectionsMapper.getLocalIDByBackendID( - destinationCollectionID + const sourceRequestPath = getRequestPathFromRequestID( + sourceRequestID, + collectionStore.value.state ) - const nextRequest = res.right.userRequestMoved.nextRequest + const destinationCollectionPath = getCollectionPathFromCollectionID( + destinationCollectionID, + collectionStore.value.state + ) + + const destinationRequestIndex = destinationCollectionPath + ? (() => { + const requestsLength = navigateToFolderWithIndexPath( + collectionStore.value.state, + destinationCollectionPath + .split("/") + .map((index) => parseInt(index)) + )?.requests.length + + return requestsLength || requestsLength == 0 + ? requestsLength - 1 + : undefined + })() + : undefined // there is no nextRequest, so request is moved if ( - requestIndex && - sourceCollectionPath && + (destinationRequestIndex || destinationRequestIndex == 0) && destinationCollectionPath && + sourceRequestPath && !nextRequest ) { runDispatchWithOutSyncing(() => { requestType == "REST" ? moveRESTRequest( - sourceCollectionPath, - parseInt(requestIndex), + sourceRequestPath.collectionPath, + sourceRequestPath.requestIndex, destinationCollectionPath ) : moveGraphqlRequest( - sourceCollectionPath, - parseInt(requestIndex), + sourceRequestPath.collectionPath, + sourceRequestPath.requestIndex, destinationCollectionPath ) }) @@ -700,21 +637,37 @@ function setupUserRequestMovedSubscription() { // there is nextRequest, so request is reordered if ( - requestIndex && - sourceCollectionPath && + (destinationRequestIndex || destinationRequestIndex == 0) && destinationCollectionPath && nextRequest && // we don't have request reordering for graphql yet requestType == "REST" ) { - const nextRequestIndex = getRequestIndexFromRequestID(nextRequest.id) + const { collectionID: nextCollectionID, id: nextRequestID } = + nextRequest + + const nextCollectionPath = + getCollectionPathFromCollectionID( + nextCollectionID, + collectionStore.value.state + ) ?? undefined + + const nextRequestIndex = nextCollectionPath + ? getRequestIndex( + nextRequestID, + nextCollectionPath, + collectionStore.value.state + ) + : undefined nextRequestIndex && + nextCollectionPath && + sourceRequestPath && runDispatchWithOutSyncing(() => { updateRESTRequestOrder( - parseInt(requestIndex), - parseInt(nextRequestIndex), - destinationCollectionPath + sourceRequestPath?.requestIndex, + nextRequestIndex, + nextCollectionPath ) }) } @@ -732,33 +685,27 @@ function setupUserRequestDeletedSubscription() { if (E.isRight(res)) { const requestType = res.right.userRequestDeleted.type - const { requestsMapper, collectionsMapper } = - getMappersAndStoreByType(requestType) + const { collectionStore } = getStoreByCollectionType(requestType) - const deletedRequestPath = requestsMapper.getLocalIDByBackendID( - res.right.userRequestDeleted.id + const deletedRequestPath = getRequestPathFromRequestID( + res.right.userRequestDeleted.id, + collectionStore.value.state ) - const indexes = deletedRequestPath?.split("/") - const requestIndex = indexes && indexes[indexes?.length - 1] - const requestParentPath = collectionsMapper.getLocalIDByBackendID( - res.right.userRequestDeleted.collectionID - ) - - requestIndex && - requestParentPath && + ;(deletedRequestPath?.requestIndex || + deletedRequestPath?.requestIndex == 0) && + deletedRequestPath.collectionPath && runDispatchWithOutSyncing(() => { requestType == "REST" - ? removeRESTRequest(requestParentPath, parseInt(requestIndex)) - : removeGraphqlRequest(requestParentPath, parseInt(requestIndex)) + ? removeRESTRequest( + deletedRequestPath.collectionPath, + deletedRequestPath.requestIndex + ) + : removeGraphqlRequest( + deletedRequestPath.collectionPath, + deletedRequestPath.requestIndex + ) }) - - deletedRequestPath && - reorderIndexesAfterEntryRemoval( - deletedRequestPath, - requestsMapper, - requestType - ) } }) @@ -769,56 +716,74 @@ export const def: CollectionsPlatformDef = { initCollectionsSync, } -function getRequestIndexFromRequestID(requestID: string) { - const requestPath = restRequestsMapper.getLocalIDByBackendID(requestID) +function getCollectionPathFromCollectionID( + collectionID: string, + collections: HoppCollection[], + parentPath?: string +): string | null { + for (const collectionIndex in collections) { + if (collections[collectionIndex].id == collectionID) { + return parentPath + ? `${parentPath}/${collectionIndex}` + : `${collectionIndex}` + } else { + const collectionPath = getCollectionPathFromCollectionID( + collectionID, + collections[collectionIndex].folders, + parentPath ? `${parentPath}/${collectionIndex}` : `${collectionIndex}` + ) - /** - * requestPath is in the form collectionPath/requestIndex, - * so to get requestIndex we just split the requestPath with / and get the last element - */ - const requestPathIndexes = requestPath?.split("/") - const requestIndex = - requestPathIndexes && requestPathIndexes[requestPathIndexes?.length - 1] + if (collectionPath) return collectionPath + } + } + + return null +} + +function getRequestPathFromRequestID( + requestID: string, + collections: HoppCollection[], + parentPath?: string +): { collectionPath: string; requestIndex: number } | null { + for (const collectionIndex in collections) { + const requestIndex = collections[collectionIndex].requests.findIndex( + (request) => request.id == requestID + ) + + if (requestIndex != -1) { + return { + collectionPath: parentPath + ? `${parentPath}/${collectionIndex}` + : `${collectionIndex}`, + requestIndex, + } + } else { + const requestPath = getRequestPathFromRequestID( + requestID, + collections[collectionIndex].folders, + parentPath ? `${parentPath}/${collectionIndex}` : `${collectionIndex}` + ) + + if (requestPath) return requestPath + } + } + + return null +} + +function getRequestIndex( + requestID: string, + parentCollectionPath: string, + collections: HoppCollection[] +) { + const collection = navigateToFolderWithIndexPath( + collections, + parentCollectionPath?.split("/").map((index) => parseInt(index)) + ) + + const requestIndex = collection?.requests.findIndex( + (request) => request.id == requestID + ) return requestIndex } - -function getCollectionPathFromRequestID(requestID: string) { - const requestPath = restRequestsMapper.getLocalIDByBackendID(requestID) - const requestPathIndexes = requestPath?.split("/") - - // requestIndex will be the last element, remove it - requestPathIndexes?.pop() - - return requestPathIndexes?.join("/") -} - -function hasReorderingOrMovingAlreadyHappened( - incomingOperation: { - sourceCollectionID: string - destinationCollectionID: string | undefined - sourcePath: string | undefined - destinationPath: string | undefined - }, - type: "REORDERING" | "MOVING" -) { - const { - sourcePath, - sourceCollectionID, - destinationCollectionID, - destinationPath, - } = incomingOperation - - // TODO: implement this as a module - // Something like, SyncOperations.hasAlreadyHappened( type: "REORDER_COLLECTIONS", payload ) - return !!collectionReorderOrMovingOperations.find((reorderOperation) => - reorderOperation.sourceCollectionID == sourceCollectionID && - reorderOperation.destinationCollectionID == destinationCollectionID && - type == "MOVING" - ? reorderOperation.reorderOperation.fromPath == destinationPath - : reorderOperation.reorderOperation.fromPath == sourcePath && - type == "MOVING" - ? reorderOperation.reorderOperation.toPath == sourcePath - : reorderOperation.reorderOperation.toPath == destinationPath - ) -} diff --git a/packages/hoppscotch-selfhost-web/src/platform/collections/collections.sync.ts b/packages/hoppscotch-selfhost-web/src/platform/collections/collections.sync.ts index 321a91bcb..218cb2251 100644 --- a/packages/hoppscotch-selfhost-web/src/platform/collections/collections.sync.ts +++ b/packages/hoppscotch-selfhost-web/src/platform/collections/collections.sync.ts @@ -1,8 +1,7 @@ import { + graphqlCollectionStore, navigateToFolderWithIndexPath, - removeGraphqlCollection, - removeRESTCollection, - removeRESTRequest, + removeDuplicateRESTCollectionOrFolder, restCollectionStore, } from "@hoppscotch/common/newstore/collections" import { @@ -12,7 +11,7 @@ import { import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data" -import { getSyncInitFunction, runDispatchWithOutSyncing } from "../../lib/sync" +import { getSyncInitFunction } from "../../lib/sync" import { StoreSyncDefinitionOf } from "../../lib/sync" import { createMapper } from "../../lib/sync/mapper" @@ -30,15 +29,6 @@ import { } from "./collections.api" import * as E from "fp-ts/Either" -import { - removeAndReorderEntries, - moveCollectionInMapper, - reorderIndexesAfterEntryRemoval, - reorderCollectionsInMapper, - reorderRequestsMapper, - moveRequestInMapper, -} from "./collections.mapper" -import { gqlCollectionsMapper } from "./gqlCollections.sync" // restCollectionsMapper uses the collectionPath as the local identifier export const restCollectionsMapper = createMapper() @@ -61,7 +51,9 @@ const recursivelySyncCollections = async ( if (E.isRight(res)) { parentCollectionID = res.right.createRESTRootUserCollection.id - restCollectionsMapper.addEntry(collectionPath, parentCollectionID) + + collection.id = parentCollectionID + removeDuplicateRESTCollectionOrFolder(parentCollectionID, collectionPath) } else { parentCollectionID = undefined } @@ -74,13 +66,19 @@ const recursivelySyncCollections = async ( if (E.isRight(res)) { const childCollectionId = res.right.createRESTChildUserCollection.id - restCollectionsMapper.addEntry(collectionPath, childCollectionId) + + collection.id = childCollectionId + + removeDuplicateRESTCollectionOrFolder( + childCollectionId, + `${collectionPath}` + ) } } // create the requests if (parentCollectionID) { - collection.requests.forEach(async (request, index) => { + collection.requests.forEach(async (request) => { const res = parentCollectionID && (await createRESTUserRequest( @@ -91,7 +89,8 @@ const recursivelySyncCollections = async ( if (res && E.isRight(res)) { const requestId = res.right.createRESTUserRequest.id - restRequestsMapper.addEntry(`${collectionPath}/${index}`, requestId) + + request.id = requestId } }) } @@ -143,109 +142,101 @@ export const storeSyncDefinition: StoreSyncDefinitionOf< const lastCreatedCollectionIndex = restCollectionStore.value.state.length - 1 - await recursivelySyncCollections( - collection, - `${lastCreatedCollectionIndex}` - ) - - removeDuplicateCollectionsFromStore("REST") + recursivelySyncCollections(collection, `${lastCreatedCollectionIndex}`) }, - async removeCollection({ collectionIndex }) { - const backendIdentifier = restCollectionsMapper.getBackendIDByLocalID( - `${collectionIndex}` - ) - - if (backendIdentifier) { - restCollectionsOperations.push({ - collectionBackendID: backendIdentifier, - type: "COLLECTION_REMOVED", - status: "pending", - }) - await deleteUserCollection(backendIdentifier) - removeAndReorderEntries(`${collectionIndex}`, "REST") + async removeCollection({ collectionID }) { + if (collectionID) { + await deleteUserCollection(collectionID) } }, editCollection({ partialCollection: collection, collectionIndex }) { - const backendIdentifier = restCollectionsMapper.getBackendIDByLocalID( - `${collectionIndex}` - ) + const collectionID = navigateToFolderWithIndexPath( + restCollectionStore.value.state, + [collectionIndex] + )?.id - if (backendIdentifier && collection.name) { - renameUserCollection(backendIdentifier, collection.name) + if (collectionID && collection.name) { + renameUserCollection(collectionID, collection.name) } }, async addFolder({ name, path }) { - const parentCollectionBackendID = - restCollectionsMapper.getBackendIDByLocalID(path) + const parentCollection = navigateToFolderWithIndexPath( + restCollectionStore.value.state, + path.split("/").map((index) => parseInt(index)) + ) + + const parentCollectionBackendID = parentCollection?.id if (parentCollectionBackendID) { - // TODO: remove this replaceAll thing when updating the mapper + const foldersLength = parentCollection.folders.length + const res = await createRESTChildUserCollection( name, parentCollectionBackendID ) - // after the folder is created add the path of the folder with its backend id to the mapper if (E.isRight(res)) { - const folderBackendID = res.right.createRESTChildUserCollection.id - const parentCollection = navigateToFolderWithIndexPath( - restCollectionStore.value.state, - path.split("/").map((index) => parseInt(index)) - ) + const { id } = res.right.createRESTChildUserCollection - if (parentCollection && parentCollection.folders.length > 0) { - const folderIndex = parentCollection.folders.length - 1 - restCollectionsMapper.addEntry( - `${path}/${folderIndex}`, - folderBackendID + if (foldersLength) { + parentCollection.folders[foldersLength - 1].id = id + removeDuplicateRESTCollectionOrFolder( + id, + `${path}/${foldersLength - 1}` ) } } } }, editFolder({ folder, path }) { - const folderBackendId = restCollectionsMapper.getBackendIDByLocalID( - `${path}` - ) + const folderID = navigateToFolderWithIndexPath( + restCollectionStore.value.state, + path.split("/").map((index) => parseInt(index)) + )?.id const folderName = folder.name - if (folderBackendId && folderName) { - renameUserCollection(folderBackendId, folderName) + if (folderID && folderName) { + renameUserCollection(folderID, folderName) } }, - async removeFolder({ path }) { - const folderBackendId = restCollectionsMapper.getBackendIDByLocalID( - `${path}` - ) - - if (folderBackendId) { - await deleteUserCollection(folderBackendId) - removeAndReorderEntries(path, "REST") + async removeFolder({ folderID }) { + if (folderID) { + await deleteUserCollection(folderID) } }, async moveFolder({ destinationPath, path }) { - const sourceCollectionBackendID = - restCollectionsMapper.getBackendIDByLocalID(path) + const { newSourcePath, newDestinationPath } = getPathsAfterMoving( + path, + destinationPath ?? undefined + ) - const destinationCollectionBackendID = destinationPath - ? restCollectionsMapper.getBackendIDByLocalID(destinationPath) - : undefined + if (newSourcePath) { + const sourceCollectionID = navigateToFolderWithIndexPath( + restCollectionStore.value.state, + newSourcePath.split("/").map((index) => parseInt(index)) + )?.id - if (sourceCollectionBackendID) { - await moveUserCollection( - sourceCollectionBackendID, - destinationCollectionBackendID - ) + const destinationCollectionID = destinationPath + ? newDestinationPath && + navigateToFolderWithIndexPath( + restCollectionStore.value.state, + newDestinationPath.split("/").map((index) => parseInt(index)) + )?.id + : undefined - moveCollectionInMapper(path, destinationPath ?? undefined, "REST") + if (sourceCollectionID) { + await moveUserCollection(sourceCollectionID, destinationCollectionID) + } } }, editRequest({ path, requestIndex, requestNew }) { - const requestPath = `${path}/${requestIndex}` + const request = navigateToFolderWithIndexPath( + restCollectionStore.value.state, + path.split("/").map((index) => parseInt(index)) + )?.requests[requestIndex] - const requestBackendID = - restRequestsMapper.getBackendIDByLocalID(requestPath) + const requestBackendID = request?.id if (requestBackendID) { editUserRequest( @@ -256,63 +247,37 @@ export const storeSyncDefinition: StoreSyncDefinitionOf< } }, async saveRequestAs({ path, request }) { - const parentCollectionBackendID = - restCollectionsMapper.getBackendIDByLocalID(path) + const folder = navigateToFolderWithIndexPath( + restCollectionStore.value.state, + path.split("/").map((index) => parseInt(index)) + ) + + const parentCollectionBackendID = folder?.id if (parentCollectionBackendID) { + const newRequest = folder.requests[folder.requests.length - 1] + const res = await createRESTUserRequest( (request as HoppRESTRequest).name, JSON.stringify(request), parentCollectionBackendID ) - const existingPath = - E.isRight(res) && - restRequestsMapper.getLocalIDByBackendID( - res.right.createRESTUserRequest.id + if (E.isRight(res)) { + const { id } = res.right.createRESTUserRequest + + newRequest.id = id + removeDuplicateRESTCollectionOrFolder( + id, + `${path}/${folder.requests.length - 1}`, + "request" ) - - // remove the request if it is already existing ( can happen when the subscription fired before the mutation is resolved ) - if (existingPath) { - const indexes = existingPath.split("/") - const existingRequestIndex = indexes.pop() - const existingRequestParentPath = indexes.join("/") - - runDispatchWithOutSyncing(() => { - existingRequestIndex && - removeRESTRequest( - existingRequestParentPath, - parseInt(existingRequestIndex) - ) - }) - } - - const parentCollection = navigateToFolderWithIndexPath( - restCollectionStore.value.state, - path.split("/").map((index) => parseInt(index)) - ) - - if (parentCollection) { - const lastCreatedRequestIndex = parentCollection.requests.length - 1 - - if (E.isRight(res)) { - restRequestsMapper.addEntry( - `${path}/${lastCreatedRequestIndex}`, - res.right.createRESTUserRequest.id - ) - } } } }, - async removeRequest({ path, requestIndex }) { - const requestPath = `${path}/${requestIndex}` - const requestBackendID = - restRequestsMapper.getBackendIDByLocalID(requestPath) - - if (requestBackendID) { - await deleteUserRequest(requestBackendID) - restRequestsMapper.removeEntry(requestPath) - reorderIndexesAfterEntryRemoval(path, restRequestsMapper, "REST") + async removeRequest({ requestID }) { + if (requestID) { + await deleteUserRequest(requestID) } }, moveRequest({ destinationPath, path, requestIndex }) { @@ -331,47 +296,74 @@ export const storeSyncDefinition: StoreSyncDefinitionOf< requestIndex, destinationCollectionPath, destinationCollectionPath, - destinationRequestIndex + destinationRequestIndex ?? undefined ) }, async updateCollectionOrder({ collectionIndex: collectionPath, destinationCollectionIndex: destinationCollectionPath, }) { - const sourceBackendID = - restCollectionsMapper.getBackendIDByLocalID(collectionPath) + const collections = restCollectionStore.value.state - const destinationBackendID = restCollectionsMapper.getBackendIDByLocalID( - destinationCollectionPath - ) + const sourcePathIndexes = getParentPathIndexesFromPath(collectionPath) + const sourceCollectionIndex = getCollectionIndexFromPath(collectionPath) - if (sourceBackendID) { - collectionReorderOrMovingOperations.push({ - sourceCollectionID: sourceBackendID, - destinationCollectionID: destinationBackendID, - reorderOperation: { - fromPath: `${parseInt(destinationCollectionPath) - 1}`, - toPath: destinationCollectionPath, - }, - }) + const destinationCollectionIndex = !!destinationCollectionPath + ? getCollectionIndexFromPath(destinationCollectionPath) + : undefined - await updateUserCollectionOrder(sourceBackendID, destinationBackendID) + let updatedCollectionIndexs: + | [newSourceIndex: number, newDestinationIndex: number | undefined] + | undefined - const currentSourcePath = - restCollectionsMapper.getLocalIDByBackendID(sourceBackendID) - - const hasAlreadyHappened = !!( - currentSourcePath == `${parseInt(destinationCollectionPath) - 1}` + if ( + (sourceCollectionIndex || sourceCollectionIndex == 0) && + (destinationCollectionIndex || destinationCollectionIndex == 0) + ) { + updatedCollectionIndexs = getIndexesAfterReorder( + sourceCollectionIndex, + destinationCollectionIndex ) + } else if (sourceCollectionIndex || sourceCollectionIndex == 0) { + if (sourcePathIndexes.length == 0) { + // we're reordering root collections + updatedCollectionIndexs = [collections.length - 1, undefined] + } else { + const sourceCollection = navigateToFolderWithIndexPath(collections, [ + ...sourcePathIndexes, + ]) - if (!hasAlreadyHappened) { - reorderCollectionsInMapper( - collectionPath, - destinationCollectionPath, - "REST" - ) + if (sourceCollection && sourceCollection.folders.length > 0) { + updatedCollectionIndexs = [ + sourceCollection.folders.length - 1, + undefined, + ] + } } } + + const sourceCollectionID = + updatedCollectionIndexs && + navigateToFolderWithIndexPath(collections, [ + ...sourcePathIndexes, + updatedCollectionIndexs[0], + ])?.id + + const destinationCollectionID = + updatedCollectionIndexs && + (updatedCollectionIndexs[1] || updatedCollectionIndexs[1] == 0) + ? navigateToFolderWithIndexPath(collections, [ + ...sourcePathIndexes, + updatedCollectionIndexs[1], + ])?.id + : undefined + + if (sourceCollectionID) { + await updateUserCollectionOrder( + sourceCollectionID, + destinationCollectionID + ) + } }, } @@ -382,28 +374,51 @@ export const collectionsSyncer = getSyncInitFunction( getSettingSubject("syncCollections") ) -async function moveOrReorderRequests( +export async function moveOrReorderRequests( requestIndex: number, path: string, destinationPath: string, - nextRequestIndex?: number + nextRequestIndex?: number, + requestType: "REST" | "GQL" = "REST" ) { - const sourceCollectionBackendID = - restCollectionsMapper.getBackendIDByLocalID(path) - const destinationCollectionBackendID = - restCollectionsMapper.getBackendIDByLocalID(destinationPath) + const { collectionStore } = getStoreByCollectionType(requestType) - const requestBackendID = restRequestsMapper.getBackendIDByLocalID( - `${path}/${requestIndex}` + const sourceCollectionBackendID = navigateToFolderWithIndexPath( + collectionStore.value.state, + path.split("/").map((index) => parseInt(index)) + )?.id + + const destinationCollection = navigateToFolderWithIndexPath( + collectionStore.value.state, + destinationPath.split("/").map((index) => parseInt(index)) ) + const destinationCollectionBackendID = destinationCollection?.id + + let requestBackendID: string | undefined + let nextRequestBackendID: string | undefined // we only need this for reordering requests, not for moving requests if (nextRequestIndex) { - nextRequestBackendID = restRequestsMapper.getBackendIDByLocalID( - `${destinationPath}/${nextRequestIndex}` + // reordering + const [newRequestIndex, newDestinationIndex] = getIndexesAfterReorder( + requestIndex, + nextRequestIndex ) + + requestBackendID = + destinationCollection?.requests[newRequestIndex]?.id ?? undefined + + nextRequestBackendID = + destinationCollection?.requests[newDestinationIndex]?.id ?? undefined + } else { + // moving + const requests = destinationCollection?.requests + requestBackendID = + requests && requests.length > 0 + ? requests[requests.length - 1]?.id + : undefined } if ( @@ -417,46 +432,112 @@ async function moveOrReorderRequests( requestBackendID, nextRequestBackendID ) - - if (nextRequestBackendID && nextRequestIndex) { - reorderRequestsMapper(requestIndex, path, nextRequestIndex, "REST") - } else { - moveRequestInMapper(requestIndex, path, destinationPath, "REST") - } } } -export function removeDuplicateCollectionsFromStore( - collectionType: "REST" | "GQL" -) { - const collectionsMapper = - collectionType === "REST" ? restCollectionsMapper : gqlCollectionsMapper - - const mapperEntries = Array.from(collectionsMapper.getValue().entries()) - - const seenBackendIDs = new Set() - - const localIDsToRemove = new Set() - - mapperEntries.forEach(([localID, backendID]) => { - if (backendID && seenBackendIDs.has(backendID)) { - localIDsToRemove.add(localID) - } else { - backendID && seenBackendIDs.add(backendID) - } - }) - - localIDsToRemove.forEach((localID) => { - collectionType === "REST" - ? removeRESTCollection(parseInt(localID)) - : removeGraphqlCollection(parseInt(localID)) - - collectionsMapper.removeEntry(undefined, localID) - - const indexes = localID.split("/") - indexes.pop() - const parentPath = indexes.join("/") - - reorderIndexesAfterEntryRemoval(parentPath, collectionsMapper, "REST") - }) +function getParentPathIndexesFromPath(path: string) { + const indexes = path.split("/") + indexes.pop() + return indexes.map((index) => parseInt(index)) +} + +export function getCollectionIndexFromPath(collectionPath: string) { + const sourceCollectionIndexString = collectionPath.split("/").pop() + const sourceCollectionIndex = sourceCollectionIndexString + ? parseInt(sourceCollectionIndexString) + : undefined + + return sourceCollectionIndex +} + +/** + * the sync function is called after the reordering has happened on the store + * because of this we need to find the new source and destination indexes after the reordering + */ +function getIndexesAfterReorder( + oldSourceIndex: number, + oldDestinationIndex: number +): [newSourceIndex: number, newDestinationIndex: number] { + // Source Becomes Destination -1 + // Destination Remains Same + if (oldSourceIndex < oldDestinationIndex) { + return [oldDestinationIndex - 1, oldDestinationIndex] + } + + // Source Becomes The Destination + // Destintion Becomes Source + 1 + if (oldSourceIndex > oldDestinationIndex) { + return [oldDestinationIndex, oldDestinationIndex + 1] + } + + throw new Error("Source and Destination are the same") +} + +/** + * the sync function is called after moving a folder has happened on the store, + * because of this the source index given to the sync function is not the live one + * we need to find the new source index after the moving + */ +function getPathsAfterMoving(sourcePath: string, destinationPath?: string) { + if (!destinationPath) { + return { + newSourcePath: `${restCollectionStore.value.state.length - 1}`, + newDestinationPath: destinationPath, + } + } + + const sourceParentPath = getParentPathFromPath(sourcePath) + const destinationParentPath = getParentPathFromPath(destinationPath) + + const isSameParentPath = sourceParentPath === destinationParentPath + + let newDestinationPath: string + + if (isSameParentPath) { + const sourceIndex = getCollectionIndexFromPath(sourcePath) + const destinationIndex = getCollectionIndexFromPath(destinationPath) + + if ( + (sourceIndex || sourceIndex == 0) && + (destinationIndex || destinationIndex == 0) && + sourceIndex < destinationIndex + ) { + newDestinationPath = destinationParentPath + ? `${destinationParentPath}/${destinationIndex - 1}` + : `${destinationIndex - 1}` + } else { + newDestinationPath = destinationPath + } + } else { + newDestinationPath = destinationPath + } + + const destinationFolder = navigateToFolderWithIndexPath( + restCollectionStore.value.state, + newDestinationPath.split("/").map((index) => parseInt(index)) + ) + + const newSourcePath = destinationFolder + ? `${newDestinationPath}/${destinationFolder?.folders.length - 1}` + : undefined + + return { + newSourcePath, + newDestinationPath, + } +} + +function getParentPathFromPath(path: string | undefined) { + const indexes = path ? path.split("/") : [] + indexes.pop() + + return indexes.join("/") +} + +export function getStoreByCollectionType(type: "GQL" | "REST") { + const isGQL = type == "GQL" + + const collectionStore = isGQL ? graphqlCollectionStore : restCollectionStore + + return { collectionStore } } diff --git a/packages/hoppscotch-selfhost-web/src/platform/collections/gqlCollections.sync.ts b/packages/hoppscotch-selfhost-web/src/platform/collections/gqlCollections.sync.ts index 99a37867b..d65970dcc 100644 --- a/packages/hoppscotch-selfhost-web/src/platform/collections/gqlCollections.sync.ts +++ b/packages/hoppscotch-selfhost-web/src/platform/collections/gqlCollections.sync.ts @@ -1,7 +1,7 @@ import { graphqlCollectionStore, navigateToFolderWithIndexPath, - removeGraphqlRequest, + removeDuplicateGraphqlCollectionOrFolder, } from "@hoppscotch/common/newstore/collections" import { getSettingSubject, @@ -10,7 +10,7 @@ import { import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data" -import { getSyncInitFunction, runDispatchWithOutSyncing } from "../../lib/sync" +import { getSyncInitFunction } from "../../lib/sync" import { StoreSyncDefinitionOf } from "../../lib/sync" import { createMapper } from "../../lib/sync/mapper" @@ -21,18 +21,11 @@ import { deleteUserCollection, deleteUserRequest, editGQLUserRequest, - moveUserRequest, renameUserCollection, } from "./collections.api" import * as E from "fp-ts/Either" -import { - moveRequestInMapper, - removeAndReorderEntries, - reorderIndexesAfterEntryRemoval, - reorderRequestsMapper, -} from "./collections.mapper" -import { removeDuplicateCollectionsFromStore } from "./collections.sync" +import { moveOrReorderRequests } from "./collections.sync" // gqlCollectionsMapper uses the collectionPath as the local identifier export const gqlCollectionsMapper = createMapper() @@ -55,7 +48,12 @@ const recursivelySyncCollections = async ( if (E.isRight(res)) { parentCollectionID = res.right.createGQLRootUserCollection.id - gqlCollectionsMapper.addEntry(collectionPath, parentCollectionID) + + collection.id = parentCollectionID + removeDuplicateGraphqlCollectionOrFolder( + parentCollectionID, + collectionPath + ) } else { parentCollectionID = undefined } @@ -68,13 +66,19 @@ const recursivelySyncCollections = async ( if (E.isRight(res)) { const childCollectionId = res.right.createGQLChildUserCollection.id - gqlCollectionsMapper.addEntry(collectionPath, childCollectionId) + + collection.id = childCollectionId + + removeDuplicateGraphqlCollectionOrFolder( + childCollectionId, + `${collectionPath}` + ) } } // create the requests if (parentCollectionID) { - collection.requests.forEach(async (request, index) => { + collection.requests.forEach(async (request) => { const res = parentCollectionID && (await createGQLUserRequest( @@ -85,7 +89,8 @@ const recursivelySyncCollections = async ( if (res && E.isRight(res)) { const requestId = res.right.createGQLUserRequest.id - gqlRequestsMapper.addEntry(`${collectionPath}/${index}`, requestId) + + request.id = requestId } }) } @@ -141,86 +146,73 @@ export const storeSyncDefinition: StoreSyncDefinitionOf< collection, `${lastCreatedCollectionIndex}` ) - - removeDuplicateCollectionsFromStore("GQL") }, - async removeCollection({ collectionIndex }) { - const backendIdentifier = gqlCollectionsMapper.getBackendIDByLocalID( - `${collectionIndex}` - ) - - if (backendIdentifier) { - gqlCollectionsOperations.push({ - collectionBackendID: backendIdentifier, - type: "COLLECTION_REMOVED", - status: "pending", - }) - await deleteUserCollection(backendIdentifier) - removeAndReorderEntries(`${collectionIndex}`, "GQL") + async removeCollection({ collectionID }) { + if (collectionID) { + await deleteUserCollection(collectionID) } }, editCollection({ collection, collectionIndex }) { - const backendIdentifier = gqlCollectionsMapper.getBackendIDByLocalID( - `${collectionIndex}` - ) + const collectionID = navigateToFolderWithIndexPath( + graphqlCollectionStore.value.state, + [collectionIndex] + )?.id - if (backendIdentifier && collection.name) { - renameUserCollection(backendIdentifier, collection.name) + if (collectionID && collection.name) { + renameUserCollection(collectionID, collection.name) } }, async addFolder({ name, path }) { - const parentCollectionBackendID = - gqlCollectionsMapper.getBackendIDByLocalID(path) + const parentCollection = navigateToFolderWithIndexPath( + graphqlCollectionStore.value.state, + path.split("/").map((index) => parseInt(index)) + ) + + const parentCollectionBackendID = parentCollection?.id if (parentCollectionBackendID) { - // TODO: remove this replaceAll thing when updating the mapper + const foldersLength = parentCollection.folders.length + const res = await createGQLChildUserCollection( name, parentCollectionBackendID ) - // after the folder is created add the path of the folder with its backend id to the mapper if (E.isRight(res)) { - const folderBackendID = res.right.createGQLChildUserCollection.id - const parentCollection = navigateToFolderWithIndexPath( - graphqlCollectionStore.value.state, - path.split("/").map((index) => parseInt(index)) - ) + const { id } = res.right.createGQLChildUserCollection - if (parentCollection && parentCollection.folders.length > 0) { - const folderIndex = parentCollection.folders.length - 1 - gqlCollectionsMapper.addEntry( - `${path}/${folderIndex}`, - folderBackendID + if (foldersLength) { + parentCollection.folders[foldersLength - 1].id = id + removeDuplicateGraphqlCollectionOrFolder( + id, + `${path}/${foldersLength - 1}` ) } } } }, editFolder({ folder, path }) { - const folderBackendId = gqlCollectionsMapper.getBackendIDByLocalID( - `${path}` - ) + const folderBackendId = navigateToFolderWithIndexPath( + graphqlCollectionStore.value.state, + path.split("/").map((index) => parseInt(index)) + )?.id - if (folderBackendId) { + if (folderBackendId && folder.name) { renameUserCollection(folderBackendId, folder.name) } }, - async removeFolder({ path }) { - const folderBackendId = gqlCollectionsMapper.getBackendIDByLocalID( - `${path}` - ) - - if (folderBackendId) { - await deleteUserCollection(folderBackendId) - removeAndReorderEntries(path, "GQL") + async removeFolder({ folderID }) { + if (folderID) { + await deleteUserCollection(folderID) } }, editRequest({ path, requestIndex, requestNew }) { - const requestPath = `${path}/${requestIndex}` + const request = navigateToFolderWithIndexPath( + graphqlCollectionStore.value.state, + path.split("/").map((index) => parseInt(index)) + )?.requests[requestIndex] - const requestBackendID = - gqlRequestsMapper.getBackendIDByLocalID(requestPath) + const requestBackendID = request?.id if (requestBackendID) { editGQLUserRequest( @@ -231,67 +223,41 @@ export const storeSyncDefinition: StoreSyncDefinitionOf< } }, async saveRequestAs({ path, request }) { - const parentCollectionBackendID = - gqlCollectionsMapper.getBackendIDByLocalID(path) + const folder = navigateToFolderWithIndexPath( + graphqlCollectionStore.value.state, + path.split("/").map((index) => parseInt(index)) + ) + + const parentCollectionBackendID = folder?.id if (parentCollectionBackendID) { + const newRequest = folder.requests[folder.requests.length - 1] + const res = await createGQLUserRequest( (request as HoppRESTRequest).name, JSON.stringify(request), parentCollectionBackendID ) - const existingPath = - E.isRight(res) && - gqlRequestsMapper.getLocalIDByBackendID( - res.right.createGQLUserRequest.id + if (E.isRight(res)) { + const { id } = res.right.createGQLUserRequest + + newRequest.id = id + removeDuplicateGraphqlCollectionOrFolder( + id, + `${path}/${folder.requests.length - 1}`, + "request" ) - - // remove the request if it is already existing ( can happen when the subscription fired before the mutation is resolved ) - if (existingPath) { - const indexes = existingPath.split("/") - const existingRequestIndex = indexes.pop() - const existingRequestParentPath = indexes.join("/") - - runDispatchWithOutSyncing(() => { - existingRequestIndex && - removeGraphqlRequest( - existingRequestParentPath, - parseInt(existingRequestIndex) - ) - }) - } - - const parentCollection = navigateToFolderWithIndexPath( - graphqlCollectionStore.value.state, - path.split("/").map((index) => parseInt(index)) - ) - - if (parentCollection) { - const lastCreatedRequestIndex = parentCollection.requests.length - 1 - - if (E.isRight(res)) { - gqlRequestsMapper.addEntry( - `${path}/${lastCreatedRequestIndex}`, - res.right.createGQLUserRequest.id - ) - } } } }, - async removeRequest({ path, requestIndex }) { - const requestPath = `${path}/${requestIndex}` - const requestBackendID = - gqlRequestsMapper.getBackendIDByLocalID(requestPath) - - if (requestBackendID) { - await deleteUserRequest(requestBackendID) - gqlRequestsMapper.removeEntry(requestPath) - reorderIndexesAfterEntryRemoval(path, gqlRequestsMapper, "GQL") + async removeRequest({ requestID }) { + if (requestID) { + await deleteUserRequest(requestID) } }, moveRequest({ destinationPath, path, requestIndex }) { - moveOrReorderRequests(requestIndex, path, destinationPath) + moveOrReorderRequests(requestIndex, path, destinationPath, undefined, "GQL") }, } @@ -301,47 +267,3 @@ export const gqlCollectionsSyncer = getSyncInitFunction( () => settingsStore.value.syncCollections, getSettingSubject("syncCollections") ) - -async function moveOrReorderRequests( - requestIndex: number, - path: string, - destinationPath: string, - nextRequestIndex?: number -) { - const sourceCollectionBackendID = - gqlCollectionsMapper.getBackendIDByLocalID(path) - const destinationCollectionBackendID = - gqlCollectionsMapper.getBackendIDByLocalID(destinationPath) - - const requestBackendID = gqlRequestsMapper.getBackendIDByLocalID( - `${path}/${requestIndex}` - ) - - let nextRequestBackendID: string | undefined - - // we only need this for reordering requests, not for moving requests - if (nextRequestIndex) { - nextRequestBackendID = gqlRequestsMapper.getBackendIDByLocalID( - `${destinationPath}/${nextRequestIndex}` - ) - } - - if ( - sourceCollectionBackendID && - destinationCollectionBackendID && - requestBackendID - ) { - await moveUserRequest( - sourceCollectionBackendID, - destinationCollectionBackendID, - requestBackendID, - nextRequestBackendID - ) - - if (nextRequestBackendID && nextRequestIndex) { - reorderRequestsMapper(requestIndex, path, nextRequestIndex, "GQL") - } else { - moveRequestInMapper(requestIndex, path, destinationPath, "GQL") - } - } -} diff --git a/packages/hoppscotch-selfhost-web/src/platform/environments/environments.platform.ts b/packages/hoppscotch-selfhost-web/src/platform/environments/environments.platform.ts index 618d8ff98..8d5042228 100644 --- a/packages/hoppscotch-selfhost-web/src/platform/environments/environments.platform.ts +++ b/packages/hoppscotch-selfhost-web/src/platform/environments/environments.platform.ts @@ -192,12 +192,6 @@ function setupUserEnvironmentDeletedSubscription() { runDispatchWithOutSyncing(() => { deleteEnvironment(localIndex) }) - } else { - console.log("could not find the localIndex") - // TODO: - // handle order of events - // eg: update coming before create - // skipping for this release } } }) diff --git a/packages/hoppscotch-selfhost-web/src/platform/tabState/tabState.platform.ts b/packages/hoppscotch-selfhost-web/src/platform/tabState/tabState.platform.ts index dd2bb333e..30420da0d 100644 --- a/packages/hoppscotch-selfhost-web/src/platform/tabState/tabState.platform.ts +++ b/packages/hoppscotch-selfhost-web/src/platform/tabState/tabState.platform.ts @@ -26,7 +26,6 @@ async function loadTabStateFromSync(): Promise { return currentRESTSession ? JSON.parse(currentRESTSession) : null } else { - console.log(res) } return null diff --git a/packages/hoppscotch-selfhost-web/src/tests/collections.sync.spec.ts b/packages/hoppscotch-selfhost-web/src/tests/collections.sync.spec.ts deleted file mode 100644 index 5c75d45d8..000000000 --- a/packages/hoppscotch-selfhost-web/src/tests/collections.sync.spec.ts +++ /dev/null @@ -1,1516 +0,0 @@ -import { describe, it, beforeEach, expect } from "vitest" -import { - changeParentForAllChildrenFromMapper, - getChildrenEntriesFromMapper, - getDirectChildrenEntriesFromMapper, - moveCollectionInMapper, - moveRequestInMapper, - removeAllChildCollectionsFromMapper, - removeAllChildRequestsFromMapper, - removeAndReorderEntries, - reorderCollectionsInMapper, - reorderIndexesAfterEntryRemoval, - reorderRequestsMapper, -} from "@platform/collections/collections.mapper" - -import { - restCollectionsMapper, - restRequestsMapper, -} from "@platform/collections/collections.sync" -import { createMapper } from "@lib/sync/mapper" -import { - addRESTCollection, - // moveRESTFolder, - setRESTCollections, -} from "@hoppscotch/common/newstore/collections" - -import { HoppRESTRequest, makeCollection } from "@hoppscotch/data" - -const getEntriesArrayFromMapper = ( - mapper: ReturnType> -) => Array.from(mapper.getValue().entries()) - -describe("getChildrenEntriesFromMapper", () => { - beforeEach(cleanUpAndSeedMappers) - - it("getChildrenEntriesFromMapper - get children of root collection", () => { - const childrenCollections = getChildrenEntriesFromMapper( - "0", - restCollectionsMapper - ) - - const childrenRequests = getChildrenEntriesFromMapper( - "0", - restRequestsMapper - ) - - expect(childrenCollections).toMatchInlineSnapshot(` - [ - [ - "0/0", - "Folder 1", - ], - [ - "0/1", - "Folder 2", - ], - [ - "0/1/0", - "Folder 3", - ], - ] - `) - - expect(childrenRequests).toMatchInlineSnapshot(` - [ - [ - "0/0/0", - "Request 1", - ], - [ - "0/1/0/0", - "Request 2", - ], - [ - "0/0", - "Request 3", - ], - ] - `) - }) - - it("getChildrenEntriesFromMapper - get children of folder", () => { - const childrenCollections = getChildrenEntriesFromMapper( - "0/1", - restCollectionsMapper - ) - - const childrenRequests = getChildrenEntriesFromMapper( - "0/1", - restRequestsMapper - ) - - expect(childrenCollections).toMatchInlineSnapshot(` - [ - [ - "0/1/0", - "Folder 3", - ], - ] - `) - - expect(childrenRequests).toMatchInlineSnapshot(` - [ - [ - "0/1/0/0", - "Request 2", - ], - ] - `) - }) - - it("getChildrenEntriesFromMapper - get children when the path is empty aka rootlevel", () => { - const childrenCollections = getChildrenEntriesFromMapper( - "", - restCollectionsMapper - ) - - const childrenRequests = getChildrenEntriesFromMapper( - "", - restRequestsMapper - ) - - expect(childrenCollections).toMatchInlineSnapshot(` - [ - [ - "0", - "Collection 1", - ], - [ - "0/0", - "Folder 1", - ], - [ - "0/1", - "Folder 2", - ], - [ - "0/1/0", - "Folder 3", - ], - [ - "1", - "Collection 2", - ], - [ - "1/0", - "Folder 4", - ], - [ - "1/0/0", - "Folder 5", - ], - ] - `) - - expect(childrenRequests).toMatchInlineSnapshot(` - [ - [ - "0/0/0", - "Request 1", - ], - [ - "0/1/0/0", - "Request 2", - ], - [ - "0/0", - "Request 3", - ], - [ - "1/0/0/0", - "Request 4", - ], - [ - "1/0", - "Request 6", - ], - [ - "1/1", - "Request 7", - ], - ] - `) - }) -}) - -describe("getDirectChildrenEntriesFromMapper", () => { - beforeEach(cleanUpAndSeedMappers) - - it("getDirectChildrenEntriesFromMapper - get direct children of root collection", () => { - const childrenCollections = getDirectChildrenEntriesFromMapper( - "0", - restCollectionsMapper - ) - - const childrenRequests = getDirectChildrenEntriesFromMapper( - "0", - restRequestsMapper - ) - - expect(childrenCollections).toMatchInlineSnapshot(` - [ - [ - "0/0", - "Folder 1", - ], - [ - "0/1", - "Folder 2", - ], - ] - `) - - expect(childrenRequests).toMatchInlineSnapshot(` - [ - [ - "0/0", - "Request 3", - ], - ] - `) - }) - - it("getDirectChildrenEntriesFromMapper - get direct children of folder", () => { - const childrenCollections = getDirectChildrenEntriesFromMapper( - "0/1", - restCollectionsMapper - ) - - const childrenRequests = getDirectChildrenEntriesFromMapper( - "0/1", - restRequestsMapper - ) - - expect(childrenCollections).toMatchInlineSnapshot(` - [ - [ - "0/1/0", - "Folder 3", - ], - ] - `) - - expect(childrenRequests).toMatchInlineSnapshot("[]") - }) - - it("getDirectChildrenEntriesFromMapper - get direct children when the path is empty aka rootlevel", () => { - const childrenCollections = getDirectChildrenEntriesFromMapper( - "", - restCollectionsMapper - ) - - const childrenRequests = getDirectChildrenEntriesFromMapper( - "", - restRequestsMapper - ) - - expect(childrenCollections).toMatchInlineSnapshot(` - [ - [ - "0", - "Collection 1", - ], - [ - "1", - "Collection 2", - ], - ] - `) - - // we do not have root level requests, so this will always be empty - expect(childrenRequests).toMatchInlineSnapshot("[]") - }) -}) - -describe("removeAllChildCollectionsFromMapper", () => { - beforeEach(cleanUpAndSeedMappers) - - it("removeAllChildCollectionsFromMapper - remove all child collections of a root collection", () => { - removeAllChildCollectionsFromMapper("0", "REST") - - expect(getEntriesArrayFromMapper(restCollectionsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0", - "Collection 1", - ], - [ - "1", - "Collection 2", - ], - [ - "1/0", - "Folder 4", - ], - [ - "1/0/0", - "Folder 5", - ], - ] - `) - }) - - it("removeAllChildCollectionsFromMapper - remove all child collections of a folder", () => { - removeAllChildCollectionsFromMapper("1/0", "REST") - - expect(getEntriesArrayFromMapper(restCollectionsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0", - "Collection 1", - ], - [ - "0/0", - "Folder 1", - ], - [ - "0/1", - "Folder 2", - ], - [ - "0/1/0", - "Folder 3", - ], - [ - "1", - "Collection 2", - ], - [ - "1/0", - "Folder 4", - ], - ] - `) - }) - - it("removeAllChildCollectionsFromMapper - remove all child collections when the path is empty aka rootlevel", () => { - removeAllChildCollectionsFromMapper("", "REST") - - expect( - getEntriesArrayFromMapper(restCollectionsMapper) - ).toMatchInlineSnapshot("[]") - }) -}) - -describe("removeAllChildRequestsFromMapper", () => { - beforeEach(cleanUpAndSeedMappers) - - it("removeAllChildRequestsFromMapper - remove all child requests of a root collection", () => { - removeAllChildRequestsFromMapper("0", "REST") - - expect(getEntriesArrayFromMapper(restRequestsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "1/0/0/0", - "Request 4", - ], - [ - "1/0", - "Request 6", - ], - [ - "1/1", - "Request 7", - ], - ] - `) - }) - - it("removeAllChildRequestsFromMapper - remove all child requests of a folder", () => { - removeAllChildRequestsFromMapper("1/0", "REST") - - expect(getEntriesArrayFromMapper(restRequestsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0/0/0", - "Request 1", - ], - [ - "0/1/0/0", - "Request 2", - ], - [ - "0/0", - "Request 3", - ], - [ - "1/0", - "Request 6", - ], - [ - "1/1", - "Request 7", - ], - ] - `) - }) - - it("removeAllChildRequestsFromMapper - remove all child requests when the path is empty aka rootlevel", () => { - removeAllChildRequestsFromMapper("", "REST") - - expect(getEntriesArrayFromMapper(restRequestsMapper)).toMatchInlineSnapshot( - "[]" - ) - }) -}) - -describe("changeParentForAllChildrenFromMapper", () => { - beforeEach(cleanUpAndSeedMappers) - - it("changes parent for all children", () => { - restCollectionsMapper.removeEntry(undefined, "0") - removeAllChildCollectionsFromMapper("0", "REST") - removeAllChildRequestsFromMapper("0", "REST") - - // for our usecases we assume that the newParentPath does not exist - // so we are not caring about children that are already present in the newParentPath - // also remember this function does not rename the parent, in this case, parentCollection "1" will still be named 1, but it won't have any children - changeParentForAllChildrenFromMapper("1", "0", "REST") - - expect(getEntriesArrayFromMapper(restCollectionsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "1", - "Collection 2", - ], - [ - "0/0", - "Folder 4", - ], - [ - "0/0/0", - "Folder 5", - ], - ] - `) - - expect(getEntriesArrayFromMapper(restRequestsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0/0/0/0", - "Request 4", - ], - [ - "0/0", - "Request 6", - ], - [ - "0/1", - "Request 7", - ], - ] - `) - }) -}) - -describe("reorderIndexesAfterEntryRemoval", () => { - beforeEach(cleanUpAndSeedMappers) - - it("reorderIndexesAfterEntryRemoval - reorder a collection", () => { - restCollectionsMapper.removeEntry(undefined, "0/0") - removeAllChildCollectionsFromMapper("0/0", "REST") - removeAllChildRequestsFromMapper("0/0", "REST") - - reorderIndexesAfterEntryRemoval("0", restCollectionsMapper, "REST") - reorderIndexesAfterEntryRemoval("0", restRequestsMapper, "REST") - - expect(getEntriesArrayFromMapper(restCollectionsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0", - "Collection 1", - ], - [ - "1", - "Collection 2", - ], - [ - "1/0", - "Folder 4", - ], - [ - "1/0/0", - "Folder 5", - ], - [ - "0/0", - "Folder 2", - ], - [ - "0/0/0", - "Folder 3", - ], - ] - `) - - expect(getEntriesArrayFromMapper(restRequestsMapper)).toMatchInlineSnapshot( - ` - [ - [ - "0/0", - "Request 3", - ], - [ - "1/0/0/0", - "Request 4", - ], - [ - "1/0", - "Request 6", - ], - [ - "1/1", - "Request 7", - ], - [ - "0/0/0/0", - "Request 2", - ], - ] - ` - ) - }) - - it("reorderIndexesAfterEntryRemoval - reorder when the path is empty aka rootlevel", () => { - restCollectionsMapper.removeEntry(undefined, "0") - removeAllChildCollectionsFromMapper("0", "REST") - removeAllChildRequestsFromMapper("0", "REST") - - reorderIndexesAfterEntryRemoval("", restCollectionsMapper, "REST") - reorderIndexesAfterEntryRemoval("", restRequestsMapper, "REST") - - expect(getEntriesArrayFromMapper(restCollectionsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0", - "Collection 2", - ], - [ - "0/0", - "Folder 4", - ], - [ - "0/0/0", - "Folder 5", - ], - ] - `) - - expect(getEntriesArrayFromMapper(restRequestsMapper)).toMatchInlineSnapshot( - ` - [ - [ - "0/0/0/0", - "Request 4", - ], - [ - "0/0", - "Request 6", - ], - [ - "0/1", - "Request 7", - ], - ] - ` - ) - }) -}) - -describe("removeAndReorderEntries", () => { - beforeEach(cleanUpAndSeedMappers) - - it("removeAndReorderEntries - removing a collection", () => { - removeAndReorderEntries("0", "REST") - - expect(getEntriesArrayFromMapper(restCollectionsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0", - "Collection 2", - ], - [ - "0/0", - "Folder 4", - ], - [ - "0/0/0", - "Folder 5", - ], - ] - `) - - expect(getEntriesArrayFromMapper(restRequestsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0/0/0/0", - "Request 4", - ], - [ - "0/0", - "Request 6", - ], - [ - "0/1", - "Request 7", - ], - ] - `) - }) - - it("removeAndReorderEntries - removing a folder", () => { - removeAndReorderEntries("0/0", "REST") - - expect(getEntriesArrayFromMapper(restCollectionsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0", - "Collection 1", - ], - [ - "1", - "Collection 2", - ], - [ - "1/0", - "Folder 4", - ], - [ - "1/0/0", - "Folder 5", - ], - [ - "0/0", - "Folder 2", - ], - [ - "0/0/0", - "Folder 3", - ], - ] - `) - - expect(getEntriesArrayFromMapper(restRequestsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0/0", - "Request 3", - ], - [ - "1/0/0/0", - "Request 4", - ], - [ - "1/0", - "Request 6", - ], - [ - "1/1", - "Request 7", - ], - [ - "0/0/0/0", - "Request 2", - ], - ] - `) - }) -}) - -describe("moveRequestsInMapper", () => { - beforeEach(() => { - cleanUpAndSeedMappers() - // the moveRequestInMapper function uses the collections store - cleanUpAndSeedStore() - }) - - it("moveRequestsInMapper - move request from collection to collection", () => { - moveRequestInMapper(0, "1", "0", "REST") - - expect(getEntriesArrayFromMapper(restRequestsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0/0/0", - "Request 1", - ], - [ - "0/1/0/0", - "Request 2", - ], - [ - "0/0", - "Request 3", - ], - [ - "1/0/0/0", - "Request 4", - ], - [ - "1/0", - "Request 7", - ], - [ - "0/1", - "Request 6", - ], - ] - `) - }) - - it("moveRequestsInMapper - move request from folder to collection", () => { - moveRequestInMapper(0, "0/1/0", "1", "REST") - - expect(getEntriesArrayFromMapper(restRequestsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0/0/0", - "Request 1", - ], - [ - "0/0", - "Request 3", - ], - [ - "1/0/0/0", - "Request 4", - ], - [ - "1/0", - "Request 6", - ], - [ - "1/1", - "Request 7", - ], - [ - "1/2", - "Request 2", - ], - ] - `) - }) - - it("moveRequestsInMapper - move request from folder to folder", () => { - moveRequestInMapper(0, "0/1/0", "1/0/0", "REST") - - expect(getEntriesArrayFromMapper(restRequestsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0/0/0", - "Request 1", - ], - [ - "0/0", - "Request 3", - ], - [ - "1/0/0/0", - "Request 4", - ], - [ - "1/0", - "Request 6", - ], - [ - "1/1", - "Request 7", - ], - [ - "1/0/0/1", - "Request 2", - ], - ] - `) - }) -}) - -describe("moveCollectionInMapper", () => { - beforeEach(() => { - cleanUpAndSeedMappers() - // the moveCollectionInMapper function uses the collections store - cleanUpAndSeedStore() - }) - - it("moveCollectionInMapper - move folder form collection to collection", () => { - // moveRESTFolder("1/0/0", "0") - moveCollectionInMapper("1/0/0", "0", "REST") - - expect(getEntriesArrayFromMapper(restCollectionsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0", - "Collection 1", - ], - [ - "0/0", - "Folder 1", - ], - [ - "0/1", - "Folder 2", - ], - [ - "0/1/0", - "Folder 3", - ], - [ - "1", - "Collection 2", - ], - [ - "1/0", - "Folder 4", - ], - [ - "0/2", - "Folder 5", - ], - ] - `) - - expect(getEntriesArrayFromMapper(restRequestsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0/0/0", - "Request 1", - ], - [ - "0/1/0/0", - "Request 2", - ], - [ - "0/0", - "Request 3", - ], - [ - "1/0", - "Request 6", - ], - [ - "1/1", - "Request 7", - ], - [ - "0/2/0", - "Request 4", - ], - ] - `) - }) - - it("moveCollectionsInMapper - move folder from folder to folder", () => { - // moveRESTFolder("1/0/0", "0/1/0") - moveCollectionInMapper("1/0/0", "0/1/0", "REST") - - expect(getEntriesArrayFromMapper(restCollectionsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0", - "Collection 1", - ], - [ - "0/0", - "Folder 1", - ], - [ - "0/1", - "Folder 2", - ], - [ - "0/1/0", - "Folder 3", - ], - [ - "1", - "Collection 2", - ], - [ - "1/0", - "Folder 4", - ], - [ - "0/1/0/0", - "Folder 5", - ], - ] - `) - - expect(getEntriesArrayFromMapper(restRequestsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0/0/0", - "Request 1", - ], - [ - "0/1/0/0", - "Request 2", - ], - [ - "0/0", - "Request 3", - ], - [ - "1/0", - "Request 6", - ], - [ - "1/1", - "Request 7", - ], - [ - "0/1/0/0/0", - "Request 4", - ], - ] - `) - }) - - it("moveCollectionsInMapper - move folder in same collection to another folder in same collection", () => { - moveCollectionInMapper("0/1/0", "0/0", "REST") - - expect(getEntriesArrayFromMapper(restCollectionsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0", - "Collection 1", - ], - [ - "0/0", - "Folder 1", - ], - [ - "0/1", - "Folder 2", - ], - [ - "1", - "Collection 2", - ], - [ - "1/0", - "Folder 4", - ], - [ - "1/0/0", - "Folder 5", - ], - [ - "0/0/0", - "Folder 3", - ], - ] - `) - - expect(getEntriesArrayFromMapper(restRequestsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0/0/0", - "Request 1", - ], - [ - "0/0", - "Request 3", - ], - [ - "1/0/0/0", - "Request 4", - ], - [ - "1/0", - "Request 6", - ], - [ - "1/1", - "Request 7", - ], - [ - "0/0/0/0", - "Request 2", - ], - ] - `) - }) -}) - -describe("reorderRequestsMapper", () => { - beforeEach(() => { - cleanUpAndSeedMappers() - - // just adding some extra requests specifically for testing this function - restRequestsMapper.addEntry("1/2", "Requests 8") - restRequestsMapper.addEntry("1/3", "Requests 9") - restRequestsMapper.addEntry("1/4", "Requests 10") - }) - - it("reorderRequestsMapper - reorders a request up down", () => { - reorderRequestsMapper(0, "1", 3, "REST") - - expect(getEntriesArrayFromMapper(restRequestsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0/0/0", - "Request 1", - ], - [ - "0/1/0/0", - "Request 2", - ], - [ - "0/0", - "Request 3", - ], - [ - "1/0/0/0", - "Request 4", - ], - [ - "1/0", - "Request 7", - ], - [ - "1/1", - "Requests 8", - ], - [ - "1/2", - "Request 6", - ], - [ - "1/3", - "Requests 9", - ], - [ - "1/4", - "Requests 10", - ], - ] - `) - }) - - it("reorderRequestsMapper - reorders a request down up", () => { - reorderRequestsMapper(3, "1", 2, "REST") - - expect(getEntriesArrayFromMapper(restRequestsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0/0/0", - "Request 1", - ], - [ - "0/1/0/0", - "Request 2", - ], - [ - "0/0", - "Request 3", - ], - [ - "1/0/0/0", - "Request 4", - ], - [ - "1/0", - "Request 6", - ], - [ - "1/1", - "Request 7", - ], - [ - "1/2", - "Requests 9", - ], - [ - "1/3", - "Requests 8", - ], - [ - "1/4", - "Requests 10", - ], - ] - `) - }) -}) - -describe("reorderCollectionsMapper", () => { - beforeEach(() => { - cleanUpAndSeedMappers() - - // adding some extra collections specifically for testing this function - restCollectionsMapper.addEntry("2", "Collection 3") - restCollectionsMapper.addEntry("3", "Collection 4") - restCollectionsMapper.addEntry("4", "Collection 5") - restCollectionsMapper.addEntry("5", "Collection 6") - }) - - it("reorderCollectionsMapper - reorders a collection up down", () => { - reorderCollectionsInMapper("2", "5", "REST") - - expect(getEntriesArrayFromMapper(restCollectionsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0", - "Collection 1", - ], - [ - "0/0", - "Folder 1", - ], - [ - "0/1", - "Folder 2", - ], - [ - "0/1/0", - "Folder 3", - ], - [ - "1", - "Collection 2", - ], - [ - "1/0", - "Folder 4", - ], - [ - "1/0/0", - "Folder 5", - ], - [ - "2", - "Collection 4", - ], - [ - "3", - "Collection 5", - ], - [ - "4", - "Collection 3", - ], - [ - "5", - "Collection 6", - ], - ] - `) - - expect(getEntriesArrayFromMapper(restRequestsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0/0/0", - "Request 1", - ], - [ - "0/1/0/0", - "Request 2", - ], - [ - "0/0", - "Request 3", - ], - [ - "1/0/0/0", - "Request 4", - ], - [ - "1/0", - "Request 6", - ], - [ - "1/1", - "Request 7", - ], - ] - `) - }) - - it("reorderCollectionsMapper - reorders a folder up down", () => { - // add extra folders to test this case - restCollectionsMapper.addEntry("1/1", "Folder 6") - restCollectionsMapper.addEntry("1/1/0", "Child Folder 1") - restCollectionsMapper.addEntry("1/2", "Folder 7") - restCollectionsMapper.addEntry("1/2/0", "Child Folder 2") - restCollectionsMapper.addEntry("1/3", "Folder 8") - restCollectionsMapper.addEntry("1/3/0", "Child Folder 3") - restCollectionsMapper.addEntry("1/4", "Folder 9") - restCollectionsMapper.addEntry("1/4/0", "Child Folder 4") - - reorderCollectionsInMapper("1/2", "1/4", "REST") - - expect(getEntriesArrayFromMapper(restCollectionsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0", - "Collection 1", - ], - [ - "0/0", - "Folder 1", - ], - [ - "0/1", - "Folder 2", - ], - [ - "0/1/0", - "Folder 3", - ], - [ - "1", - "Collection 2", - ], - [ - "1/0", - "Folder 4", - ], - [ - "1/0/0", - "Folder 5", - ], - [ - "2", - "Collection 3", - ], - [ - "3", - "Collection 4", - ], - [ - "4", - "Collection 5", - ], - [ - "5", - "Collection 6", - ], - [ - "1/1", - "Folder 6", - ], - [ - "1/1/0", - "Child Folder 1", - ], - [ - "1/2", - "Folder 8", - ], - [ - "1/3", - "Folder 7", - ], - [ - "1/4", - "Folder 9", - ], - [ - "1/4/0", - "Child Folder 4", - ], - [ - "1/2/0", - "Child Folder 3", - ], - [ - "1/3/0", - "Child Folder 2", - ], - ] - `) - - expect(getEntriesArrayFromMapper(restRequestsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0/0/0", - "Request 1", - ], - [ - "0/1/0/0", - "Request 2", - ], - [ - "0/0", - "Request 3", - ], - [ - "1/0/0/0", - "Request 4", - ], - [ - "1/0", - "Request 6", - ], - [ - "1/1", - "Request 7", - ], - ] - `) - }) - - it("reorderRequestsMapper - reorders a collection down up", () => { - reorderCollectionsInMapper("3", "1", "REST") - - expect(getEntriesArrayFromMapper(restCollectionsMapper)) - .toMatchInlineSnapshot(` - [ - [ - "0", - "Collection 1", - ], - [ - "0/0", - "Folder 1", - ], - [ - "0/1", - "Folder 2", - ], - [ - "0/1/0", - "Folder 3", - ], - [ - "1", - "Collection 4", - ], - [ - "2", - "Collection 2", - ], - [ - "3", - "Collection 3", - ], - [ - "4", - "Collection 5", - ], - [ - "5", - "Collection 6", - ], - [ - "2/0", - "Folder 4", - ], - [ - "2/0/0", - "Folder 5", - ], - ] - `) - }) -}) - -function cleanUpAndSeedMappers() { - // remove all collections - Array.from(restCollectionsMapper.getValue().entries()).forEach(([path]) => { - restCollectionsMapper.removeEntry(undefined, path) - }) - - // remove all requests - Array.from(restRequestsMapper.getValue().entries()).forEach(([path]) => { - restRequestsMapper.removeEntry(undefined, path) - }) - - // populate sample collections and requests - /** - * 0. Collection 1 - * 0. Folder 1 - * 0. Request 1 - * 1. Folder 2 - * 0. Folder 3 - * 0.Request 2 - * 0. Request 3 - * 1. Collection 2 - * 0. Folder 4 - * 0. Folder 5 - * 0. Request 4 - * 0. Request 6 - * 1. Request 7 - */ - restCollectionsMapper.addEntry("0", "Collection 1") - - restCollectionsMapper.addEntry("0/0", "Folder 1") - restCollectionsMapper.addEntry("0/1", "Folder 2") - restCollectionsMapper.addEntry("0/1/0", "Folder 3") - - restRequestsMapper.addEntry("0/0/0", "Request 1") - restRequestsMapper.addEntry("0/1/0/0", "Request 2") - restRequestsMapper.addEntry("0/0", "Request 3") - restCollectionsMapper.addEntry("1", "Collection 2") - restCollectionsMapper.addEntry("1/0", "Folder 4") - restCollectionsMapper.addEntry("1/0/0", "Folder 5") - - restRequestsMapper.addEntry("1/0/0/0", "Request 4") - restRequestsMapper.addEntry("1/0", "Request 6") - restRequestsMapper.addEntry("1/1", "Request 7") -} - -function cleanUpAndSeedStore() { - // reset the store - setRESTCollections([]) - - /** - * * 0. Collection 1 - * 0. Folder 1 - * 0. Request 1 - * 1. Folder 2 - * 0. Folder 3 - * 0.Request 2 - * 0. Request 3 - * 1. Collection 2 - * 0. Folder 4 - * 0. Folder 5 - * 0. Request 4 - * 0. Request 6 - * 1. Request 7 - */ - - addRESTCollection( - makeCollection({ - name: "Collection 1", - folders: [ - makeCollection({ - name: "Folder 1", - folders: [], - requests: [makeEmptyRequest("Request 1")], - }), - makeCollection({ - name: "Folder 2", - folders: [ - makeCollection({ - name: "Folder 3", - folders: [], - requests: [makeEmptyRequest("Request 2")], - }), - ], - requests: [], - }), - ], - requests: [makeEmptyRequest("Request 3")], - }) - ) - - addRESTCollection( - makeCollection({ - name: "Collection 2", - folders: [ - makeCollection({ - name: "Folder 4", - folders: [ - makeCollection({ - name: "Folder 5", - folders: [], - requests: [makeEmptyRequest("Request 4")], - }), - ], - requests: [], - }), - ], - requests: [makeEmptyRequest("Request 6"), makeEmptyRequest("Request 7")], - }) - ) -} - -function makeEmptyRequest(name: string): HoppRESTRequest { - return { - name, - auth: { - authType: "none", - authActive: false, - }, - endpoint: "", - body: { - contentType: null, - body: null, - }, - headers: [], - params: [], - method: "GET", - preRequestScript: "", - v: "1", - testScript: "", - } -}