refactor: refactor collection to not use mapper (#80)

This commit is contained in:
Akash K
2023-04-11 15:09:32 +05:30
committed by GitHub
parent 5d1337f15d
commit c353d60ddc
7 changed files with 593 additions and 2604 deletions

View File

@@ -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<string, [string, string | undefined][]> =
{}
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<typeof createMapper<string, string>>
) {
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<typeof createMapper<string, string>>
) {
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<typeof createMapper<string, string>>,
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 }
}

View File

@@ -14,20 +14,7 @@ import {
runUserRequestMovedSubscription, runUserRequestMovedSubscription,
runUserRequestUpdatedSubscription, runUserRequestUpdatedSubscription,
} from "./collections.api" } from "./collections.api"
import { import { collectionsSyncer, getStoreByCollectionType } from "./collections.sync"
collectionReorderOrMovingOperations,
collectionsSyncer,
restCollectionsOperations,
restCollectionsMapper,
restRequestsMapper,
} from "./collections.sync"
import {
moveCollectionInMapper,
removeAndReorderEntries,
reorderIndexesAfterEntryRemoval,
reorderCollectionsInMapper,
getMappersAndStoreByType,
} from "./collections.mapper"
import * as E from "fp-ts/Either" import * as E from "fp-ts/Either"
import { import {
@@ -57,6 +44,7 @@ import {
moveGraphqlRequest, moveGraphqlRequest,
removeGraphqlRequest, removeGraphqlRequest,
setGraphqlCollections, setGraphqlCollections,
restCollectionStore,
} from "@hoppscotch/common/newstore/collections" } from "@hoppscotch/common/newstore/collections"
import { runGQLSubscription } from "@hoppscotch/common/helpers/backend/GQLClient" import { runGQLSubscription } from "@hoppscotch/common/helpers/backend/GQLClient"
import { import {
@@ -64,10 +52,7 @@ import {
HoppGQLRequest, HoppGQLRequest,
HoppRESTRequest, HoppRESTRequest,
} from "@hoppscotch/data" } from "@hoppscotch/data"
import { import { gqlCollectionsSyncer } from "./gqlCollections.sync"
gqlCollectionsOperations,
gqlCollectionsSyncer,
} from "./gqlCollections.sync"
import { ReqType } from "../../api/generated/graphql" import { ReqType } from "../../api/generated/graphql"
function initCollectionsSync() { function initCollectionsSync() {
@@ -77,14 +62,14 @@ function initCollectionsSync() {
gqlCollectionsSyncer.startStoreSync() gqlCollectionsSyncer.startStoreSync()
loadUserRootCollections("REST") loadUserCollections("REST")
loadUserRootCollections("GQL") loadUserCollections("GQL")
// TODO: test & make sure the auth thing is working properly // TODO: test & make sure the auth thing is working properly
currentUser$.subscribe(async (user) => { currentUser$.subscribe(async (user) => {
if (user) { if (user) {
loadUserRootCollections("REST") loadUserCollections("REST")
loadUserRootCollections("GQL") loadUserCollections("GQL")
} }
}) })
@@ -121,6 +106,7 @@ function exportedCollectionToHoppCollection(
const restCollection = collection as ExportedUserCollectionREST const restCollection = collection as ExportedUserCollectionREST
return { return {
id: restCollection.id,
v: 1, v: 1,
name: restCollection.name, name: restCollection.name,
folders: restCollection.folders.map((folder) => folders: restCollection.folders.map((folder) =>
@@ -128,6 +114,7 @@ function exportedCollectionToHoppCollection(
), ),
requests: restCollection.requests.map( requests: restCollection.requests.map(
({ ({
id,
v, v,
auth, auth,
body, body,
@@ -139,6 +126,7 @@ function exportedCollectionToHoppCollection(
preRequestScript, preRequestScript,
testScript, testScript,
}) => ({ }) => ({
id,
v, v,
auth, auth,
body, body,
@@ -156,49 +144,26 @@ function exportedCollectionToHoppCollection(
const gqlCollection = collection as ExportedUserCollectionGQL const gqlCollection = collection as ExportedUserCollectionGQL
return { return {
id: gqlCollection.id,
v: 1, v: 1,
name: gqlCollection.name, name: gqlCollection.name,
folders: gqlCollection.folders.map((folder) => folders: gqlCollection.folders.map((folder) =>
exportedCollectionToHoppCollection(folder, collectionType) exportedCollectionToHoppCollection(folder, collectionType)
), ),
requests: gqlCollection.requests.map(({ v, auth, headers, name }) => ({ requests: gqlCollection.requests.map(
v, ({ v, auth, headers, name, id }) => ({
auth, id,
headers, v,
name, auth,
})) as HoppGQLRequest[], headers,
name,
})
) as HoppGQLRequest[],
} }
} }
} }
function addMapperEntriesForExportedCollection( async function loadUserCollections(collectionType: "REST" | "GQL") {
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") {
const res = await exportUserCollectionsToJSON( const res = await exportUserCollectionsToJSON(
undefined, undefined,
collectionType == "REST" ? ReqType.Rest : ReqType.Gql collectionType == "REST" ? ReqType.Rest : ReqType.Gql
@@ -233,14 +198,6 @@ async function loadUserRootCollections(collectionType: "REST" | "GQL") {
) as HoppCollection<HoppGQLRequest> ) as HoppCollection<HoppGQLRequest>
) )
) )
exportedCollections.forEach((collection, index) =>
addMapperEntriesForExportedCollection(
collection,
`${index}`,
collectionType
)
)
}) })
} }
} }
@@ -284,14 +241,14 @@ function setupUserCollectionCreatedSubscription() {
if (E.isRight(res)) { if (E.isRight(res)) {
const collectionType = res.right.userCollectionCreated.type const collectionType = res.right.userCollectionCreated.type
const { collectionsMapper, collectionStore } = const { collectionStore } = getStoreByCollectionType(collectionType)
getMappersAndStoreByType(collectionType)
const userCollectionBackendID = res.right.userCollectionCreated.id const userCollectionBackendID = res.right.userCollectionCreated.id
const parentCollectionID = res.right.userCollectionCreated.parent?.id const parentCollectionID = res.right.userCollectionCreated.parent?.id
const userCollectionLocalID = collectionsMapper.getLocalIDByBackendID( const userCollectionLocalID = getCollectionPathFromCollectionID(
userCollectionBackendID userCollectionBackendID,
collectionStore.value.state
) )
// collection already exists in store ( this instance created it ) // collection already exists in store ( this instance created it )
@@ -301,7 +258,10 @@ function setupUserCollectionCreatedSubscription() {
const parentCollectionPath = const parentCollectionPath =
parentCollectionID && parentCollectionID &&
collectionsMapper.getLocalIDByBackendID(parentCollectionID) getCollectionPathFromCollectionID(
parentCollectionID,
collectionStore.value.state
)
// only folders will have parent collection id // only folders will have parent collection id
if (parentCollectionID && parentCollectionPath) { if (parentCollectionID && parentCollectionPath) {
@@ -325,10 +285,9 @@ function setupUserCollectionCreatedSubscription() {
if (parentCollection) { if (parentCollection) {
const folderIndex = parentCollection.folders.length - 1 const folderIndex = parentCollection.folders.length - 1
collectionsMapper.addEntry(
`${parentCollectionPath}/${folderIndex}`, const addedFolder = parentCollection.folders[folderIndex]
userCollectionBackendID addedFolder.id = userCollectionBackendID
)
} }
}) })
} else { } else {
@@ -349,7 +308,9 @@ function setupUserCollectionCreatedSubscription() {
}) })
const localIndex = collectionStore.value.state.length - 1 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)) { if (E.isRight(res)) {
const collectionType = res.right.userCollectionUpdated.type const collectionType = res.right.userCollectionUpdated.type
const { collectionsMapper } = getMappersAndStoreByType(collectionType) const { collectionStore } = getStoreByCollectionType(collectionType)
const updatedCollectionBackendID = res.right.userCollectionUpdated.id const updatedCollectionBackendID = res.right.userCollectionUpdated.id
const updatedCollectionLocalPath = const updatedCollectionLocalPath = getCollectionPathFromCollectionID(
collectionsMapper.getLocalIDByBackendID(updatedCollectionBackendID) updatedCollectionBackendID,
collectionStore.value.state
)
const isFolder = const isFolder =
updatedCollectionLocalPath && updatedCollectionLocalPath &&
@@ -415,37 +378,25 @@ function setupUserCollectionMovedSubscription() {
if (E.isRight(res)) { if (E.isRight(res)) {
const movedMetadata = res.right.userCollectionMoved const movedMetadata = res.right.userCollectionMoved
const sourcePath = restCollectionsMapper.getLocalIDByBackendID( const sourcePath = getCollectionPathFromCollectionID(
movedMetadata.id movedMetadata.id,
restCollectionStore.value.state
) )
let destinationPath: string | undefined let destinationPath: string | undefined
if (movedMetadata.parent?.id) { if (movedMetadata.parent?.id) {
destinationPath = restCollectionsMapper.getLocalIDByBackendID( destinationPath =
movedMetadata.parent?.id getCollectionPathFromCollectionID(
) movedMetadata.parent?.id,
restCollectionStore.value.state
) ?? undefined
} }
const hasAlreadyHappened = hasReorderingOrMovingAlreadyHappened( sourcePath &&
{ runDispatchWithOutSyncing(() => {
sourceCollectionID: movedMetadata.id, moveRESTFolder(sourcePath, destinationPath ?? null)
destinationCollectionID: movedMetadata.parent?.id, })
sourcePath,
destinationPath,
},
"MOVING"
)
if (!hasAlreadyHappened) {
sourcePath &&
runDispatchWithOutSyncing(() => {
moveRESTFolder(sourcePath, destinationPath ?? null)
})
sourcePath &&
moveCollectionInMapper(sourcePath, destinationPath, "REST")
}
} }
}) })
@@ -461,28 +412,13 @@ function setupUserCollectionRemovedSubscription() {
const removedCollectionBackendID = res.right.userCollectionRemoved.id const removedCollectionBackendID = res.right.userCollectionRemoved.id
const collectionType = res.right.userCollectionRemoved.type const collectionType = res.right.userCollectionRemoved.type
const { collectionsMapper } = getMappersAndStoreByType(collectionType) const { collectionStore } = getStoreByCollectionType(collectionType)
const collectionsOperations = const removedCollectionLocalPath = getCollectionPathFromCollectionID(
collectionType == "REST" removedCollectionBackendID,
? restCollectionsOperations collectionStore.value.state
: 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
) )
// the collection is already removed
if (!removedCollectionLocalPath || isInOperations) {
return
}
const isFolder = const isFolder =
removedCollectionLocalPath && removedCollectionLocalPath &&
removedCollectionLocalPath.split("/").length > 1 removedCollectionLocalPath.split("/").length > 1
@@ -502,9 +438,6 @@ function setupUserCollectionRemovedSubscription() {
: removeGraphqlCollection(parseInt(removedCollectionLocalPath)) : removeGraphqlCollection(parseInt(removedCollectionLocalPath))
}) })
} }
removedCollectionLocalPath &&
removeAndReorderEntries(removedCollectionLocalPath, collectionType)
} }
}) })
@@ -523,40 +456,25 @@ function setupUserCollectionOrderUpdatedSubscription() {
const sourceCollectionID = userCollection.id const sourceCollectionID = userCollection.id
const destinationCollectionID = nextUserCollection?.id const destinationCollectionID = nextUserCollection?.id
const sourcePath = const sourcePath = getCollectionPathFromCollectionID(
restCollectionsMapper.getLocalIDByBackendID(sourceCollectionID) sourceCollectionID,
restCollectionStore.value.state
)
let destinationPath: string | undefined let destinationPath: string | null | undefined
if (destinationCollectionID) { if (destinationCollectionID) {
destinationPath = restCollectionsMapper.getLocalIDByBackendID( destinationPath = getCollectionPathFromCollectionID(
destinationCollectionID destinationCollectionID,
restCollectionStore.value.state
) )
} }
const hasAlreadyHappened = hasReorderingOrMovingAlreadyHappened( runDispatchWithOutSyncing(() => {
{ if (sourcePath) {
sourceCollectionID, updateRESTCollectionOrder(sourcePath, destinationPath ?? null)
destinationCollectionID, }
sourcePath, })
destinationPath,
},
"REORDERING"
)
if (!hasAlreadyHappened) {
runDispatchWithOutSyncing(() => {
if (
sourcePath &&
destinationPath &&
sourceCollectionID &&
destinationCollectionID
) {
updateRESTCollectionOrder(sourcePath, destinationPath)
reorderCollectionsInMapper(sourcePath, destinationPath, "REST")
}
})
}
} }
}) })
@@ -575,18 +493,21 @@ function setupUserRequestCreatedSubscription() {
const requestType = res.right.userRequestCreated.type const requestType = res.right.userRequestCreated.type
const { collectionsMapper, requestsMapper, collectionStore } = const { collectionStore } = getStoreByCollectionType(requestType)
getMappersAndStoreByType(requestType)
const hasAlreadyHappened = const hasAlreadyHappened = getRequestPathFromRequestID(
!!requestsMapper.getLocalIDByBackendID(requestID) requestID,
collectionStore.value.state
)
if (hasAlreadyHappened) { if (!!hasAlreadyHappened) {
return return
} }
const collectionPath = const collectionPath = getCollectionPathFromCollectionID(
collectionsMapper.getLocalIDByBackendID(collectionID) collectionID,
collectionStore.value.state
)
if (collectionID && collectionPath) { if (collectionID && collectionPath) {
runDispatchWithOutSyncing(() => { runDispatchWithOutSyncing(() => {
@@ -599,10 +520,11 @@ function setupUserRequestCreatedSubscription() {
collectionPath.split("/").map((index) => parseInt(index)) collectionPath.split("/").map((index) => parseInt(index))
) )
const requestPath = const targetRequest = target?.requests[target?.requests.length - 1]
target && `${collectionPath}/${target?.requests.length - 1}`
requestPath && requestsMapper.addEntry(requestPath, requestID) if (targetRequest) {
targetRequest.id = requestID
}
}) })
} }
} }
@@ -619,31 +541,28 @@ function setupUserRequestUpdatedSubscription() {
if (E.isRight(res)) { if (E.isRight(res)) {
const requestType = res.right.userRequestUpdated.type const requestType = res.right.userRequestUpdated.type
const { requestsMapper, collectionsMapper } = const { collectionStore } = getStoreByCollectionType(requestType)
getMappersAndStoreByType(requestType)
const requestPath = requestsMapper.getLocalIDByBackendID( const requestPath = getRequestPathFromRequestID(
res.right.userRequestUpdated.id res.right.userRequestUpdated.id,
collectionStore.value.state
) )
const indexes = requestPath?.split("/") const collectionPath = requestPath?.collectionPath
const requestIndex = indexes && indexes[indexes?.length - 1] const requestIndex = requestPath?.requestIndex
const requestParentPath = collectionsMapper.getLocalIDByBackendID(
res.right.userRequestUpdated.collectionID
)
requestIndex && ;(requestIndex || requestIndex == 0) &&
requestParentPath && collectionPath &&
runDispatchWithOutSyncing(() => { runDispatchWithOutSyncing(() => {
requestType == "REST" requestType == "REST"
? editRESTRequest( ? editRESTRequest(
requestParentPath, collectionPath,
parseInt(requestIndex), requestIndex,
JSON.parse(res.right.userRequestUpdated.request) JSON.parse(res.right.userRequestUpdated.request)
) )
: editGraphqlRequest( : editGraphqlRequest(
requestParentPath, collectionPath,
parseInt(requestIndex), requestIndex,
JSON.parse(res.right.userRequestUpdated.request) JSON.parse(res.right.userRequestUpdated.request)
) )
}) })
@@ -659,40 +578,58 @@ function setupUserRequestMovedSubscription() {
userRequestMoved$.subscribe((res) => { userRequestMoved$.subscribe((res) => {
if (E.isRight(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 { collectionStore } = getStoreByCollectionType(requestType)
const requestIndex = getRequestIndexFromRequestID(requestID)
const sourceCollectionPath = getCollectionPathFromRequestID(requestID) const sourceRequestPath = getRequestPathFromRequestID(
sourceRequestID,
const destinationCollectionID = collectionStore.value.state
res.right.userRequestMoved.request.collectionID
const destinationCollectionPath = collectionsMapper.getLocalIDByBackendID(
destinationCollectionID
) )
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 // there is no nextRequest, so request is moved
if ( if (
requestIndex && (destinationRequestIndex || destinationRequestIndex == 0) &&
sourceCollectionPath &&
destinationCollectionPath && destinationCollectionPath &&
sourceRequestPath &&
!nextRequest !nextRequest
) { ) {
runDispatchWithOutSyncing(() => { runDispatchWithOutSyncing(() => {
requestType == "REST" requestType == "REST"
? moveRESTRequest( ? moveRESTRequest(
sourceCollectionPath, sourceRequestPath.collectionPath,
parseInt(requestIndex), sourceRequestPath.requestIndex,
destinationCollectionPath destinationCollectionPath
) )
: moveGraphqlRequest( : moveGraphqlRequest(
sourceCollectionPath, sourceRequestPath.collectionPath,
parseInt(requestIndex), sourceRequestPath.requestIndex,
destinationCollectionPath destinationCollectionPath
) )
}) })
@@ -700,21 +637,37 @@ function setupUserRequestMovedSubscription() {
// there is nextRequest, so request is reordered // there is nextRequest, so request is reordered
if ( if (
requestIndex && (destinationRequestIndex || destinationRequestIndex == 0) &&
sourceCollectionPath &&
destinationCollectionPath && destinationCollectionPath &&
nextRequest && nextRequest &&
// we don't have request reordering for graphql yet // we don't have request reordering for graphql yet
requestType == "REST" 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 && nextRequestIndex &&
nextCollectionPath &&
sourceRequestPath &&
runDispatchWithOutSyncing(() => { runDispatchWithOutSyncing(() => {
updateRESTRequestOrder( updateRESTRequestOrder(
parseInt(requestIndex), sourceRequestPath?.requestIndex,
parseInt(nextRequestIndex), nextRequestIndex,
destinationCollectionPath nextCollectionPath
) )
}) })
} }
@@ -732,33 +685,27 @@ function setupUserRequestDeletedSubscription() {
if (E.isRight(res)) { if (E.isRight(res)) {
const requestType = res.right.userRequestDeleted.type const requestType = res.right.userRequestDeleted.type
const { requestsMapper, collectionsMapper } = const { collectionStore } = getStoreByCollectionType(requestType)
getMappersAndStoreByType(requestType)
const deletedRequestPath = requestsMapper.getLocalIDByBackendID( const deletedRequestPath = getRequestPathFromRequestID(
res.right.userRequestDeleted.id res.right.userRequestDeleted.id,
collectionStore.value.state
) )
const indexes = deletedRequestPath?.split("/") ;(deletedRequestPath?.requestIndex ||
const requestIndex = indexes && indexes[indexes?.length - 1] deletedRequestPath?.requestIndex == 0) &&
const requestParentPath = collectionsMapper.getLocalIDByBackendID( deletedRequestPath.collectionPath &&
res.right.userRequestDeleted.collectionID
)
requestIndex &&
requestParentPath &&
runDispatchWithOutSyncing(() => { runDispatchWithOutSyncing(() => {
requestType == "REST" requestType == "REST"
? removeRESTRequest(requestParentPath, parseInt(requestIndex)) ? removeRESTRequest(
: removeGraphqlRequest(requestParentPath, parseInt(requestIndex)) deletedRequestPath.collectionPath,
deletedRequestPath.requestIndex
)
: removeGraphqlRequest(
deletedRequestPath.collectionPath,
deletedRequestPath.requestIndex
)
}) })
deletedRequestPath &&
reorderIndexesAfterEntryRemoval(
deletedRequestPath,
requestsMapper,
requestType
)
} }
}) })
@@ -769,56 +716,74 @@ export const def: CollectionsPlatformDef = {
initCollectionsSync, initCollectionsSync,
} }
function getRequestIndexFromRequestID(requestID: string) { function getCollectionPathFromCollectionID(
const requestPath = restRequestsMapper.getLocalIDByBackendID(requestID) collectionID: string,
collections: HoppCollection<HoppRESTRequest | HoppGQLRequest>[],
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}`
)
/** if (collectionPath) return collectionPath
* 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("/") return null
const requestIndex = }
requestPathIndexes && requestPathIndexes[requestPathIndexes?.length - 1]
function getRequestPathFromRequestID(
requestID: string,
collections: HoppCollection<HoppRESTRequest | HoppGQLRequest>[],
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<HoppRESTRequest | HoppGQLRequest>[]
) {
const collection = navigateToFolderWithIndexPath(
collections,
parentCollectionPath?.split("/").map((index) => parseInt(index))
)
const requestIndex = collection?.requests.findIndex(
(request) => request.id == requestID
)
return requestIndex 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
)
}

View File

@@ -1,8 +1,7 @@
import { import {
graphqlCollectionStore,
navigateToFolderWithIndexPath, navigateToFolderWithIndexPath,
removeGraphqlCollection, removeDuplicateRESTCollectionOrFolder,
removeRESTCollection,
removeRESTRequest,
restCollectionStore, restCollectionStore,
} from "@hoppscotch/common/newstore/collections" } from "@hoppscotch/common/newstore/collections"
import { import {
@@ -12,7 +11,7 @@ import {
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data" import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
import { getSyncInitFunction, runDispatchWithOutSyncing } from "../../lib/sync" import { getSyncInitFunction } from "../../lib/sync"
import { StoreSyncDefinitionOf } from "../../lib/sync" import { StoreSyncDefinitionOf } from "../../lib/sync"
import { createMapper } from "../../lib/sync/mapper" import { createMapper } from "../../lib/sync/mapper"
@@ -30,15 +29,6 @@ import {
} from "./collections.api" } from "./collections.api"
import * as E from "fp-ts/Either" 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 // restCollectionsMapper uses the collectionPath as the local identifier
export const restCollectionsMapper = createMapper<string, string>() export const restCollectionsMapper = createMapper<string, string>()
@@ -61,7 +51,9 @@ const recursivelySyncCollections = async (
if (E.isRight(res)) { if (E.isRight(res)) {
parentCollectionID = res.right.createRESTRootUserCollection.id parentCollectionID = res.right.createRESTRootUserCollection.id
restCollectionsMapper.addEntry(collectionPath, parentCollectionID)
collection.id = parentCollectionID
removeDuplicateRESTCollectionOrFolder(parentCollectionID, collectionPath)
} else { } else {
parentCollectionID = undefined parentCollectionID = undefined
} }
@@ -74,13 +66,19 @@ const recursivelySyncCollections = async (
if (E.isRight(res)) { if (E.isRight(res)) {
const childCollectionId = res.right.createRESTChildUserCollection.id const childCollectionId = res.right.createRESTChildUserCollection.id
restCollectionsMapper.addEntry(collectionPath, childCollectionId)
collection.id = childCollectionId
removeDuplicateRESTCollectionOrFolder(
childCollectionId,
`${collectionPath}`
)
} }
} }
// create the requests // create the requests
if (parentCollectionID) { if (parentCollectionID) {
collection.requests.forEach(async (request, index) => { collection.requests.forEach(async (request) => {
const res = const res =
parentCollectionID && parentCollectionID &&
(await createRESTUserRequest( (await createRESTUserRequest(
@@ -91,7 +89,8 @@ const recursivelySyncCollections = async (
if (res && E.isRight(res)) { if (res && E.isRight(res)) {
const requestId = res.right.createRESTUserRequest.id const requestId = res.right.createRESTUserRequest.id
restRequestsMapper.addEntry(`${collectionPath}/${index}`, requestId)
request.id = requestId
} }
}) })
} }
@@ -143,109 +142,101 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
const lastCreatedCollectionIndex = const lastCreatedCollectionIndex =
restCollectionStore.value.state.length - 1 restCollectionStore.value.state.length - 1
await recursivelySyncCollections( recursivelySyncCollections(collection, `${lastCreatedCollectionIndex}`)
collection,
`${lastCreatedCollectionIndex}`
)
removeDuplicateCollectionsFromStore("REST")
}, },
async removeCollection({ collectionIndex }) { async removeCollection({ collectionID }) {
const backendIdentifier = restCollectionsMapper.getBackendIDByLocalID( if (collectionID) {
`${collectionIndex}` await deleteUserCollection(collectionID)
)
if (backendIdentifier) {
restCollectionsOperations.push({
collectionBackendID: backendIdentifier,
type: "COLLECTION_REMOVED",
status: "pending",
})
await deleteUserCollection(backendIdentifier)
removeAndReorderEntries(`${collectionIndex}`, "REST")
} }
}, },
editCollection({ partialCollection: collection, collectionIndex }) { editCollection({ partialCollection: collection, collectionIndex }) {
const backendIdentifier = restCollectionsMapper.getBackendIDByLocalID( const collectionID = navigateToFolderWithIndexPath(
`${collectionIndex}` restCollectionStore.value.state,
) [collectionIndex]
)?.id
if (backendIdentifier && collection.name) { if (collectionID && collection.name) {
renameUserCollection(backendIdentifier, collection.name) renameUserCollection(collectionID, collection.name)
} }
}, },
async addFolder({ name, path }) { async addFolder({ name, path }) {
const parentCollectionBackendID = const parentCollection = navigateToFolderWithIndexPath(
restCollectionsMapper.getBackendIDByLocalID(path) restCollectionStore.value.state,
path.split("/").map((index) => parseInt(index))
)
const parentCollectionBackendID = parentCollection?.id
if (parentCollectionBackendID) { if (parentCollectionBackendID) {
// TODO: remove this replaceAll thing when updating the mapper const foldersLength = parentCollection.folders.length
const res = await createRESTChildUserCollection( const res = await createRESTChildUserCollection(
name, name,
parentCollectionBackendID parentCollectionBackendID
) )
// after the folder is created add the path of the folder with its backend id to the mapper
if (E.isRight(res)) { if (E.isRight(res)) {
const folderBackendID = res.right.createRESTChildUserCollection.id const { id } = res.right.createRESTChildUserCollection
const parentCollection = navigateToFolderWithIndexPath(
restCollectionStore.value.state,
path.split("/").map((index) => parseInt(index))
)
if (parentCollection && parentCollection.folders.length > 0) { if (foldersLength) {
const folderIndex = parentCollection.folders.length - 1 parentCollection.folders[foldersLength - 1].id = id
restCollectionsMapper.addEntry( removeDuplicateRESTCollectionOrFolder(
`${path}/${folderIndex}`, id,
folderBackendID `${path}/${foldersLength - 1}`
) )
} }
} }
} }
}, },
editFolder({ folder, path }) { editFolder({ folder, path }) {
const folderBackendId = restCollectionsMapper.getBackendIDByLocalID( const folderID = navigateToFolderWithIndexPath(
`${path}` restCollectionStore.value.state,
) path.split("/").map((index) => parseInt(index))
)?.id
const folderName = folder.name const folderName = folder.name
if (folderBackendId && folderName) { if (folderID && folderName) {
renameUserCollection(folderBackendId, folderName) renameUserCollection(folderID, folderName)
} }
}, },
async removeFolder({ path }) { async removeFolder({ folderID }) {
const folderBackendId = restCollectionsMapper.getBackendIDByLocalID( if (folderID) {
`${path}` await deleteUserCollection(folderID)
)
if (folderBackendId) {
await deleteUserCollection(folderBackendId)
removeAndReorderEntries(path, "REST")
} }
}, },
async moveFolder({ destinationPath, path }) { async moveFolder({ destinationPath, path }) {
const sourceCollectionBackendID = const { newSourcePath, newDestinationPath } = getPathsAfterMoving(
restCollectionsMapper.getBackendIDByLocalID(path) path,
destinationPath ?? undefined
)
const destinationCollectionBackendID = destinationPath if (newSourcePath) {
? restCollectionsMapper.getBackendIDByLocalID(destinationPath) const sourceCollectionID = navigateToFolderWithIndexPath(
: undefined restCollectionStore.value.state,
newSourcePath.split("/").map((index) => parseInt(index))
)?.id
if (sourceCollectionBackendID) { const destinationCollectionID = destinationPath
await moveUserCollection( ? newDestinationPath &&
sourceCollectionBackendID, navigateToFolderWithIndexPath(
destinationCollectionBackendID 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 }) { editRequest({ path, requestIndex, requestNew }) {
const requestPath = `${path}/${requestIndex}` const request = navigateToFolderWithIndexPath(
restCollectionStore.value.state,
path.split("/").map((index) => parseInt(index))
)?.requests[requestIndex]
const requestBackendID = const requestBackendID = request?.id
restRequestsMapper.getBackendIDByLocalID(requestPath)
if (requestBackendID) { if (requestBackendID) {
editUserRequest( editUserRequest(
@@ -256,63 +247,37 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
} }
}, },
async saveRequestAs({ path, request }) { async saveRequestAs({ path, request }) {
const parentCollectionBackendID = const folder = navigateToFolderWithIndexPath(
restCollectionsMapper.getBackendIDByLocalID(path) restCollectionStore.value.state,
path.split("/").map((index) => parseInt(index))
)
const parentCollectionBackendID = folder?.id
if (parentCollectionBackendID) { if (parentCollectionBackendID) {
const newRequest = folder.requests[folder.requests.length - 1]
const res = await createRESTUserRequest( const res = await createRESTUserRequest(
(request as HoppRESTRequest).name, (request as HoppRESTRequest).name,
JSON.stringify(request), JSON.stringify(request),
parentCollectionBackendID parentCollectionBackendID
) )
const existingPath = if (E.isRight(res)) {
E.isRight(res) && const { id } = res.right.createRESTUserRequest
restRequestsMapper.getLocalIDByBackendID(
res.right.createRESTUserRequest.id 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 }) { async removeRequest({ requestID }) {
const requestPath = `${path}/${requestIndex}` if (requestID) {
const requestBackendID = await deleteUserRequest(requestID)
restRequestsMapper.getBackendIDByLocalID(requestPath)
if (requestBackendID) {
await deleteUserRequest(requestBackendID)
restRequestsMapper.removeEntry(requestPath)
reorderIndexesAfterEntryRemoval(path, restRequestsMapper, "REST")
} }
}, },
moveRequest({ destinationPath, path, requestIndex }) { moveRequest({ destinationPath, path, requestIndex }) {
@@ -331,47 +296,74 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
requestIndex, requestIndex,
destinationCollectionPath, destinationCollectionPath,
destinationCollectionPath, destinationCollectionPath,
destinationRequestIndex destinationRequestIndex ?? undefined
) )
}, },
async updateCollectionOrder({ async updateCollectionOrder({
collectionIndex: collectionPath, collectionIndex: collectionPath,
destinationCollectionIndex: destinationCollectionPath, destinationCollectionIndex: destinationCollectionPath,
}) { }) {
const sourceBackendID = const collections = restCollectionStore.value.state
restCollectionsMapper.getBackendIDByLocalID(collectionPath)
const destinationBackendID = restCollectionsMapper.getBackendIDByLocalID( const sourcePathIndexes = getParentPathIndexesFromPath(collectionPath)
destinationCollectionPath const sourceCollectionIndex = getCollectionIndexFromPath(collectionPath)
)
if (sourceBackendID) { const destinationCollectionIndex = !!destinationCollectionPath
collectionReorderOrMovingOperations.push({ ? getCollectionIndexFromPath(destinationCollectionPath)
sourceCollectionID: sourceBackendID, : undefined
destinationCollectionID: destinationBackendID,
reorderOperation: {
fromPath: `${parseInt(destinationCollectionPath) - 1}`,
toPath: destinationCollectionPath,
},
})
await updateUserCollectionOrder(sourceBackendID, destinationBackendID) let updatedCollectionIndexs:
| [newSourceIndex: number, newDestinationIndex: number | undefined]
| undefined
const currentSourcePath = if (
restCollectionsMapper.getLocalIDByBackendID(sourceBackendID) (sourceCollectionIndex || sourceCollectionIndex == 0) &&
(destinationCollectionIndex || destinationCollectionIndex == 0)
const hasAlreadyHappened = !!( ) {
currentSourcePath == `${parseInt(destinationCollectionPath) - 1}` 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) { if (sourceCollection && sourceCollection.folders.length > 0) {
reorderCollectionsInMapper( updatedCollectionIndexs = [
collectionPath, sourceCollection.folders.length - 1,
destinationCollectionPath, undefined,
"REST" ]
) }
} }
} }
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") getSettingSubject("syncCollections")
) )
async function moveOrReorderRequests( export async function moveOrReorderRequests(
requestIndex: number, requestIndex: number,
path: string, path: string,
destinationPath: string, destinationPath: string,
nextRequestIndex?: number nextRequestIndex?: number,
requestType: "REST" | "GQL" = "REST"
) { ) {
const sourceCollectionBackendID = const { collectionStore } = getStoreByCollectionType(requestType)
restCollectionsMapper.getBackendIDByLocalID(path)
const destinationCollectionBackendID =
restCollectionsMapper.getBackendIDByLocalID(destinationPath)
const requestBackendID = restRequestsMapper.getBackendIDByLocalID( const sourceCollectionBackendID = navigateToFolderWithIndexPath(
`${path}/${requestIndex}` 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 let nextRequestBackendID: string | undefined
// we only need this for reordering requests, not for moving requests // we only need this for reordering requests, not for moving requests
if (nextRequestIndex) { if (nextRequestIndex) {
nextRequestBackendID = restRequestsMapper.getBackendIDByLocalID( // reordering
`${destinationPath}/${nextRequestIndex}` 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 ( if (
@@ -417,46 +432,112 @@ async function moveOrReorderRequests(
requestBackendID, requestBackendID,
nextRequestBackendID nextRequestBackendID
) )
if (nextRequestBackendID && nextRequestIndex) {
reorderRequestsMapper(requestIndex, path, nextRequestIndex, "REST")
} else {
moveRequestInMapper(requestIndex, path, destinationPath, "REST")
}
} }
} }
export function removeDuplicateCollectionsFromStore( function getParentPathIndexesFromPath(path: string) {
collectionType: "REST" | "GQL" const indexes = path.split("/")
) { indexes.pop()
const collectionsMapper = return indexes.map((index) => parseInt(index))
collectionType === "REST" ? restCollectionsMapper : gqlCollectionsMapper }
const mapperEntries = Array.from(collectionsMapper.getValue().entries()) export function getCollectionIndexFromPath(collectionPath: string) {
const sourceCollectionIndexString = collectionPath.split("/").pop()
const seenBackendIDs = new Set<string>() const sourceCollectionIndex = sourceCollectionIndexString
? parseInt(sourceCollectionIndexString)
const localIDsToRemove = new Set<string>() : undefined
mapperEntries.forEach(([localID, backendID]) => { return sourceCollectionIndex
if (backendID && seenBackendIDs.has(backendID)) { }
localIDsToRemove.add(localID)
} else { /**
backendID && seenBackendIDs.add(backendID) * 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(
localIDsToRemove.forEach((localID) => { oldSourceIndex: number,
collectionType === "REST" oldDestinationIndex: number
? removeRESTCollection(parseInt(localID)) ): [newSourceIndex: number, newDestinationIndex: number] {
: removeGraphqlCollection(parseInt(localID)) // Source Becomes Destination -1
// Destination Remains Same
collectionsMapper.removeEntry(undefined, localID) if (oldSourceIndex < oldDestinationIndex) {
return [oldDestinationIndex - 1, oldDestinationIndex]
const indexes = localID.split("/") }
indexes.pop()
const parentPath = indexes.join("/") // Source Becomes The Destination
// Destintion Becomes Source + 1
reorderIndexesAfterEntryRemoval(parentPath, collectionsMapper, "REST") 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 }
} }

View File

@@ -1,7 +1,7 @@
import { import {
graphqlCollectionStore, graphqlCollectionStore,
navigateToFolderWithIndexPath, navigateToFolderWithIndexPath,
removeGraphqlRequest, removeDuplicateGraphqlCollectionOrFolder,
} from "@hoppscotch/common/newstore/collections" } from "@hoppscotch/common/newstore/collections"
import { import {
getSettingSubject, getSettingSubject,
@@ -10,7 +10,7 @@ import {
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data" import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
import { getSyncInitFunction, runDispatchWithOutSyncing } from "../../lib/sync" import { getSyncInitFunction } from "../../lib/sync"
import { StoreSyncDefinitionOf } from "../../lib/sync" import { StoreSyncDefinitionOf } from "../../lib/sync"
import { createMapper } from "../../lib/sync/mapper" import { createMapper } from "../../lib/sync/mapper"
@@ -21,18 +21,11 @@ import {
deleteUserCollection, deleteUserCollection,
deleteUserRequest, deleteUserRequest,
editGQLUserRequest, editGQLUserRequest,
moveUserRequest,
renameUserCollection, renameUserCollection,
} from "./collections.api" } from "./collections.api"
import * as E from "fp-ts/Either" import * as E from "fp-ts/Either"
import { import { moveOrReorderRequests } from "./collections.sync"
moveRequestInMapper,
removeAndReorderEntries,
reorderIndexesAfterEntryRemoval,
reorderRequestsMapper,
} from "./collections.mapper"
import { removeDuplicateCollectionsFromStore } from "./collections.sync"
// gqlCollectionsMapper uses the collectionPath as the local identifier // gqlCollectionsMapper uses the collectionPath as the local identifier
export const gqlCollectionsMapper = createMapper<string, string>() export const gqlCollectionsMapper = createMapper<string, string>()
@@ -55,7 +48,12 @@ const recursivelySyncCollections = async (
if (E.isRight(res)) { if (E.isRight(res)) {
parentCollectionID = res.right.createGQLRootUserCollection.id parentCollectionID = res.right.createGQLRootUserCollection.id
gqlCollectionsMapper.addEntry(collectionPath, parentCollectionID)
collection.id = parentCollectionID
removeDuplicateGraphqlCollectionOrFolder(
parentCollectionID,
collectionPath
)
} else { } else {
parentCollectionID = undefined parentCollectionID = undefined
} }
@@ -68,13 +66,19 @@ const recursivelySyncCollections = async (
if (E.isRight(res)) { if (E.isRight(res)) {
const childCollectionId = res.right.createGQLChildUserCollection.id const childCollectionId = res.right.createGQLChildUserCollection.id
gqlCollectionsMapper.addEntry(collectionPath, childCollectionId)
collection.id = childCollectionId
removeDuplicateGraphqlCollectionOrFolder(
childCollectionId,
`${collectionPath}`
)
} }
} }
// create the requests // create the requests
if (parentCollectionID) { if (parentCollectionID) {
collection.requests.forEach(async (request, index) => { collection.requests.forEach(async (request) => {
const res = const res =
parentCollectionID && parentCollectionID &&
(await createGQLUserRequest( (await createGQLUserRequest(
@@ -85,7 +89,8 @@ const recursivelySyncCollections = async (
if (res && E.isRight(res)) { if (res && E.isRight(res)) {
const requestId = res.right.createGQLUserRequest.id const requestId = res.right.createGQLUserRequest.id
gqlRequestsMapper.addEntry(`${collectionPath}/${index}`, requestId)
request.id = requestId
} }
}) })
} }
@@ -141,86 +146,73 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
collection, collection,
`${lastCreatedCollectionIndex}` `${lastCreatedCollectionIndex}`
) )
removeDuplicateCollectionsFromStore("GQL")
}, },
async removeCollection({ collectionIndex }) { async removeCollection({ collectionID }) {
const backendIdentifier = gqlCollectionsMapper.getBackendIDByLocalID( if (collectionID) {
`${collectionIndex}` await deleteUserCollection(collectionID)
)
if (backendIdentifier) {
gqlCollectionsOperations.push({
collectionBackendID: backendIdentifier,
type: "COLLECTION_REMOVED",
status: "pending",
})
await deleteUserCollection(backendIdentifier)
removeAndReorderEntries(`${collectionIndex}`, "GQL")
} }
}, },
editCollection({ collection, collectionIndex }) { editCollection({ collection, collectionIndex }) {
const backendIdentifier = gqlCollectionsMapper.getBackendIDByLocalID( const collectionID = navigateToFolderWithIndexPath(
`${collectionIndex}` graphqlCollectionStore.value.state,
) [collectionIndex]
)?.id
if (backendIdentifier && collection.name) { if (collectionID && collection.name) {
renameUserCollection(backendIdentifier, collection.name) renameUserCollection(collectionID, collection.name)
} }
}, },
async addFolder({ name, path }) { async addFolder({ name, path }) {
const parentCollectionBackendID = const parentCollection = navigateToFolderWithIndexPath(
gqlCollectionsMapper.getBackendIDByLocalID(path) graphqlCollectionStore.value.state,
path.split("/").map((index) => parseInt(index))
)
const parentCollectionBackendID = parentCollection?.id
if (parentCollectionBackendID) { if (parentCollectionBackendID) {
// TODO: remove this replaceAll thing when updating the mapper const foldersLength = parentCollection.folders.length
const res = await createGQLChildUserCollection( const res = await createGQLChildUserCollection(
name, name,
parentCollectionBackendID parentCollectionBackendID
) )
// after the folder is created add the path of the folder with its backend id to the mapper
if (E.isRight(res)) { if (E.isRight(res)) {
const folderBackendID = res.right.createGQLChildUserCollection.id const { id } = res.right.createGQLChildUserCollection
const parentCollection = navigateToFolderWithIndexPath(
graphqlCollectionStore.value.state,
path.split("/").map((index) => parseInt(index))
)
if (parentCollection && parentCollection.folders.length > 0) { if (foldersLength) {
const folderIndex = parentCollection.folders.length - 1 parentCollection.folders[foldersLength - 1].id = id
gqlCollectionsMapper.addEntry( removeDuplicateGraphqlCollectionOrFolder(
`${path}/${folderIndex}`, id,
folderBackendID `${path}/${foldersLength - 1}`
) )
} }
} }
} }
}, },
editFolder({ folder, path }) { editFolder({ folder, path }) {
const folderBackendId = gqlCollectionsMapper.getBackendIDByLocalID( const folderBackendId = navigateToFolderWithIndexPath(
`${path}` graphqlCollectionStore.value.state,
) path.split("/").map((index) => parseInt(index))
)?.id
if (folderBackendId) { if (folderBackendId && folder.name) {
renameUserCollection(folderBackendId, folder.name) renameUserCollection(folderBackendId, folder.name)
} }
}, },
async removeFolder({ path }) { async removeFolder({ folderID }) {
const folderBackendId = gqlCollectionsMapper.getBackendIDByLocalID( if (folderID) {
`${path}` await deleteUserCollection(folderID)
)
if (folderBackendId) {
await deleteUserCollection(folderBackendId)
removeAndReorderEntries(path, "GQL")
} }
}, },
editRequest({ path, requestIndex, requestNew }) { editRequest({ path, requestIndex, requestNew }) {
const requestPath = `${path}/${requestIndex}` const request = navigateToFolderWithIndexPath(
graphqlCollectionStore.value.state,
path.split("/").map((index) => parseInt(index))
)?.requests[requestIndex]
const requestBackendID = const requestBackendID = request?.id
gqlRequestsMapper.getBackendIDByLocalID(requestPath)
if (requestBackendID) { if (requestBackendID) {
editGQLUserRequest( editGQLUserRequest(
@@ -231,67 +223,41 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
} }
}, },
async saveRequestAs({ path, request }) { async saveRequestAs({ path, request }) {
const parentCollectionBackendID = const folder = navigateToFolderWithIndexPath(
gqlCollectionsMapper.getBackendIDByLocalID(path) graphqlCollectionStore.value.state,
path.split("/").map((index) => parseInt(index))
)
const parentCollectionBackendID = folder?.id
if (parentCollectionBackendID) { if (parentCollectionBackendID) {
const newRequest = folder.requests[folder.requests.length - 1]
const res = await createGQLUserRequest( const res = await createGQLUserRequest(
(request as HoppRESTRequest).name, (request as HoppRESTRequest).name,
JSON.stringify(request), JSON.stringify(request),
parentCollectionBackendID parentCollectionBackendID
) )
const existingPath = if (E.isRight(res)) {
E.isRight(res) && const { id } = res.right.createGQLUserRequest
gqlRequestsMapper.getLocalIDByBackendID(
res.right.createGQLUserRequest.id 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 }) { async removeRequest({ requestID }) {
const requestPath = `${path}/${requestIndex}` if (requestID) {
const requestBackendID = await deleteUserRequest(requestID)
gqlRequestsMapper.getBackendIDByLocalID(requestPath)
if (requestBackendID) {
await deleteUserRequest(requestBackendID)
gqlRequestsMapper.removeEntry(requestPath)
reorderIndexesAfterEntryRemoval(path, gqlRequestsMapper, "GQL")
} }
}, },
moveRequest({ destinationPath, path, requestIndex }) { 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, () => settingsStore.value.syncCollections,
getSettingSubject("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")
}
}
}

View File

@@ -192,12 +192,6 @@ function setupUserEnvironmentDeletedSubscription() {
runDispatchWithOutSyncing(() => { runDispatchWithOutSyncing(() => {
deleteEnvironment(localIndex) deleteEnvironment(localIndex)
}) })
} else {
console.log("could not find the localIndex")
// TODO:
// handle order of events
// eg: update coming before create
// skipping for this release
} }
} }
}) })

View File

@@ -26,7 +26,6 @@ async function loadTabStateFromSync(): Promise<PersistableRESTTabState | null> {
return currentRESTSession ? JSON.parse(currentRESTSession) : null return currentRESTSession ? JSON.parse(currentRESTSession) : null
} else { } else {
console.log(res)
} }
return null return null