feat: duplicate REST/GraphQL collections (#4211)
Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com> Co-authored-by: nivedin <nivedinp@gmail.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
mutation DuplicateUserCollection($collectionID: String!, $reqType: ReqType!) {
|
||||
duplicateUserCollection(collectionID: $collectionID, reqType: $reqType)
|
||||
}
|
||||
@@ -40,6 +40,7 @@ createHoppApp("#app", {
|
||||
platformFeatureFlags: {
|
||||
exportAsGIST: false,
|
||||
hasTelemetry: false,
|
||||
duplicateCollectionDisabledInPersonalWorkspace: true,
|
||||
},
|
||||
infra: InfraPlatform,
|
||||
})
|
||||
|
||||
@@ -68,6 +68,9 @@ import {
|
||||
UpdateUserCollectionMutation,
|
||||
UpdateUserCollectionMutationVariables,
|
||||
UpdateUserCollectionDocument,
|
||||
DuplicateUserCollectionDocument,
|
||||
DuplicateUserCollectionMutation,
|
||||
DuplicateUserCollectionMutationVariables,
|
||||
} from "../../api/generated/graphql"
|
||||
|
||||
export const createRESTRootUserCollection = (title: string, data?: string) =>
|
||||
@@ -193,6 +196,19 @@ export const moveUserCollection = (
|
||||
destCollectionID: destinationCollectionID,
|
||||
})()
|
||||
|
||||
export const duplicateUserCollection = (
|
||||
collectionID: string,
|
||||
reqType: ReqType
|
||||
) =>
|
||||
runMutation<
|
||||
DuplicateUserCollectionMutation,
|
||||
DuplicateUserCollectionMutationVariables,
|
||||
""
|
||||
>(DuplicateUserCollectionDocument, {
|
||||
collectionID,
|
||||
reqType,
|
||||
})()
|
||||
|
||||
export const editUserRequest = (
|
||||
requestID: string,
|
||||
title: string,
|
||||
|
||||
@@ -284,6 +284,20 @@ function setupUserCollectionCreatedSubscription() {
|
||||
return
|
||||
}
|
||||
|
||||
// While duplicating a collection, the new entry added to the store has an ID with a suffix to be updated after the backend ID is received from the GQL subscription
|
||||
// This is to prevent the new entry from being added to the store again when the GQL subscription
|
||||
// The boolean return value indicates if the GQL subscription was fired because of a duplicate collection action and whether the collection should be added to the store
|
||||
const shouldCreateCollection = issueBackendIDToDuplicatedCollection(
|
||||
collectionStore,
|
||||
collectionType,
|
||||
userCollectionBackendID,
|
||||
parentCollectionID
|
||||
)
|
||||
|
||||
if (!shouldCreateCollection) {
|
||||
return
|
||||
}
|
||||
|
||||
const parentCollectionPath =
|
||||
parentCollectionID &&
|
||||
getCollectionPathFromCollectionID(
|
||||
@@ -828,3 +842,105 @@ function getRequestIndex(
|
||||
|
||||
return requestIndex
|
||||
}
|
||||
|
||||
function issueBackendIDToDuplicatedCollection(
|
||||
collectionStore: ReturnType<
|
||||
typeof getStoreByCollectionType
|
||||
>["collectionStore"],
|
||||
collectionType: ReqType,
|
||||
userCollectionBackendID: string,
|
||||
parentCollectionID?: string
|
||||
): boolean {
|
||||
// Collection added to store via duplicating is set an ID with a suffix to be updated after the backend ID is received from the GQL subscription
|
||||
const collectionCreatedFromStoreIDSuffix = "-duplicate-collection"
|
||||
|
||||
// Duplicating a child collection
|
||||
if (parentCollectionID) {
|
||||
// Get the index path for the parent collection
|
||||
const parentCollectionPath = getCollectionPathFromCollectionID(
|
||||
parentCollectionID,
|
||||
collectionStore.value.state
|
||||
)
|
||||
|
||||
if (!parentCollectionPath) {
|
||||
// Indicates the collection received from the GQL subscription should be created in the store
|
||||
return true
|
||||
}
|
||||
|
||||
const parentCollection = navigateToFolderWithIndexPath(
|
||||
collectionStore.value.state,
|
||||
parentCollectionPath.split("/").map((index) => parseInt(index))
|
||||
)
|
||||
|
||||
if (!parentCollection) {
|
||||
// Indicates the collection received from the GQL subscription should be created in the store
|
||||
return true
|
||||
}
|
||||
|
||||
// Grab the child collection inserted via store update with the ID suffix
|
||||
const collectionInsertedViaStoreUpdateIdx =
|
||||
parentCollection.folders.findIndex(({ id }) =>
|
||||
id?.endsWith(collectionCreatedFromStoreIDSuffix)
|
||||
)
|
||||
|
||||
// No entry indicates the GQL subscription was fired not because of a duplicate collection action
|
||||
if (collectionInsertedViaStoreUpdateIdx === -1) {
|
||||
// Indicates the collection received from the GQL subscription should be created in the store
|
||||
return true
|
||||
}
|
||||
const collectionInsertedViaStoreUpdate =
|
||||
parentCollection.folders[collectionInsertedViaStoreUpdateIdx]
|
||||
|
||||
const childCollectionPath = `${parentCollectionPath}/${collectionInsertedViaStoreUpdateIdx}`
|
||||
|
||||
// Update the ID for the child collection already existing in store with the backend ID
|
||||
runDispatchWithOutSyncing(() => {
|
||||
if (collectionType == ReqType.Rest) {
|
||||
editRESTFolder(childCollectionPath, {
|
||||
...collectionInsertedViaStoreUpdate,
|
||||
id: userCollectionBackendID,
|
||||
})
|
||||
} else {
|
||||
editGraphqlFolder(childCollectionPath, {
|
||||
...collectionInsertedViaStoreUpdate,
|
||||
id: userCollectionBackendID,
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Duplicating a root collection
|
||||
|
||||
// Grab the collection inserted via store update with the ID suffix
|
||||
const collectionInsertedViaStoreUpdateIdx =
|
||||
collectionStore.value.state.findIndex(({ id }) =>
|
||||
id?.endsWith(collectionCreatedFromStoreIDSuffix)
|
||||
)
|
||||
|
||||
// No entry indicates the GQL subscription was fired not because of a duplicate collection action
|
||||
if (collectionInsertedViaStoreUpdateIdx === -1) {
|
||||
// Indicates the collection received from the GQL subscription should be created in the store
|
||||
return true
|
||||
}
|
||||
|
||||
const collectionInsertedViaStoreUpdate =
|
||||
collectionStore.value.state[collectionInsertedViaStoreUpdateIdx]
|
||||
|
||||
// Update the ID for the collection already existing in store with the backend ID
|
||||
runDispatchWithOutSyncing(() => {
|
||||
if (collectionType == ReqType.Rest) {
|
||||
editRESTCollection(collectionInsertedViaStoreUpdateIdx, {
|
||||
...collectionInsertedViaStoreUpdate,
|
||||
id: userCollectionBackendID,
|
||||
})
|
||||
} else {
|
||||
editGraphqlCollection(collectionInsertedViaStoreUpdateIdx, {
|
||||
...collectionInsertedViaStoreUpdate,
|
||||
id: userCollectionBackendID,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Prevent adding the collection received from GQL subscription to the store
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -11,9 +11,7 @@ import {
|
||||
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||
|
||||
import { getSyncInitFunction } from "../../lib/sync"
|
||||
|
||||
import { StoreSyncDefinitionOf } from "../../lib/sync"
|
||||
import { getSyncInitFunction, StoreSyncDefinitionOf } from "../../lib/sync"
|
||||
import { createMapper } from "../../lib/sync/mapper"
|
||||
import {
|
||||
createRESTChildUserCollection,
|
||||
@@ -21,6 +19,7 @@ import {
|
||||
createRESTUserRequest,
|
||||
deleteUserCollection,
|
||||
deleteUserRequest,
|
||||
duplicateUserCollection,
|
||||
editUserRequest,
|
||||
moveUserCollection,
|
||||
moveUserRequest,
|
||||
@@ -29,6 +28,7 @@ import {
|
||||
} from "./collections.api"
|
||||
|
||||
import * as E from "fp-ts/Either"
|
||||
import { ReqType } from "../../api/generated/graphql"
|
||||
|
||||
// restCollectionsMapper uses the collectionPath as the local identifier
|
||||
export const restCollectionsMapper = createMapper<string, string>()
|
||||
@@ -280,6 +280,11 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
|
||||
}
|
||||
}
|
||||
},
|
||||
async duplicateCollection({ collectionSyncID }) {
|
||||
if (collectionSyncID) {
|
||||
await duplicateUserCollection(collectionSyncID, ReqType.Rest)
|
||||
}
|
||||
},
|
||||
editRequest({ path, requestIndex, requestNew }) {
|
||||
const request = navigateToFolderWithIndexPath(
|
||||
restCollectionStore.value.state,
|
||||
|
||||
@@ -20,11 +20,13 @@ import {
|
||||
createGQLUserRequest,
|
||||
deleteUserCollection,
|
||||
deleteUserRequest,
|
||||
duplicateUserCollection,
|
||||
editGQLUserRequest,
|
||||
updateUserCollection,
|
||||
} from "./collections.api"
|
||||
|
||||
import * as E from "fp-ts/Either"
|
||||
import { ReqType } from "../../api/generated/graphql"
|
||||
import { moveOrReorderRequests } from "./collections.sync"
|
||||
|
||||
// gqlCollectionsMapper uses the collectionPath as the local identifier
|
||||
@@ -261,6 +263,11 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
|
||||
await deleteUserCollection(folderID)
|
||||
}
|
||||
},
|
||||
async duplicateCollection({ collectionSyncID }) {
|
||||
if (collectionSyncID) {
|
||||
await duplicateUserCollection(collectionSyncID, ReqType.Gql)
|
||||
}
|
||||
},
|
||||
editRequest({ path, requestIndex, requestNew }) {
|
||||
const request = navigateToFolderWithIndexPath(
|
||||
graphqlCollectionStore.value.state,
|
||||
|
||||
Reference in New Issue
Block a user