feat: extend duplicate collection to personal workspace in SH (#4368)

This commit is contained in:
James George
2024-09-30 08:50:19 -07:00
committed by GitHub
parent 58857be650
commit 5e9f8743d4
12 changed files with 624 additions and 295 deletions

View File

@@ -6,5 +6,6 @@ subscription UserCollectionCreated {
id
title
type
data
}
}

View File

@@ -0,0 +1,15 @@
subscription UserCollectionDuplicated {
userCollectionDuplicated {
id
parentID
title
type
data
childCollections
requests {
id
request
collectionID
}
}
}

View File

@@ -4,67 +4,68 @@ import {
runMutation,
} from "@hoppscotch/common/helpers/backend/GQLClient"
import {
CreateGqlChildUserCollectionDocument,
CreateGqlChildUserCollectionMutation,
CreateGqlChildUserCollectionMutationVariables,
CreateGqlRootUserCollectionDocument,
CreateGqlRootUserCollectionMutation,
CreateGqlRootUserCollectionMutationVariables,
CreateGqlUserRequestDocument,
CreateGqlUserRequestMutation,
CreateGqlUserRequestMutationVariables,
CreateRestChildUserCollectionDocument,
CreateRestChildUserCollectionMutation,
CreateRestChildUserCollectionMutationVariables,
CreateRestRootUserCollectionDocument,
CreateRestRootUserCollectionMutation,
CreateRestRootUserCollectionMutationVariables,
CreateRestUserRequestDocument,
CreateRestUserRequestMutation,
CreateRestUserRequestMutationVariables,
CreateRestUserRequestDocument,
CreateRestChildUserCollectionMutation,
CreateRestChildUserCollectionMutationVariables,
CreateRestChildUserCollectionDocument,
DeleteUserCollectionDocument,
DeleteUserCollectionMutation,
DeleteUserCollectionMutationVariables,
DeleteUserCollectionDocument,
RenameUserCollectionMutation,
RenameUserCollectionMutationVariables,
RenameUserCollectionDocument,
MoveUserCollectionMutation,
MoveUserCollectionMutationVariables,
MoveUserCollectionDocument,
DeleteUserRequestDocument,
DeleteUserRequestMutation,
DeleteUserRequestMutationVariables,
DeleteUserRequestDocument,
ExportUserCollectionsToJsonDocument,
ExportUserCollectionsToJsonQuery,
ExportUserCollectionsToJsonQueryVariables,
GetGqlRootUserCollectionsDocument,
GetGqlRootUserCollectionsQuery,
GetGqlRootUserCollectionsQueryVariables,
GetUserRootCollectionsDocument,
GetUserRootCollectionsQuery,
GetUserRootCollectionsQueryVariables,
MoveUserCollectionDocument,
MoveUserCollectionMutation,
MoveUserCollectionMutationVariables,
MoveUserRequestDocument,
MoveUserRequestMutation,
MoveUserRequestMutationVariables,
UpdateUserCollectionOrderMutation,
UpdateUserCollectionOrderMutationVariables,
UpdateUserCollectionOrderDocument,
GetUserRootCollectionsQuery,
GetUserRootCollectionsQueryVariables,
GetUserRootCollectionsDocument,
UserCollectionCreatedDocument,
UserCollectionUpdatedDocument,
UserCollectionRemovedDocument,
UserCollectionMovedDocument,
UserCollectionOrderUpdatedDocument,
ExportUserCollectionsToJsonQuery,
ExportUserCollectionsToJsonQueryVariables,
ExportUserCollectionsToJsonDocument,
UserRequestCreatedDocument,
UserRequestUpdatedDocument,
UserRequestMovedDocument,
UserRequestDeletedDocument,
UpdateRestUserRequestMutation,
UpdateRestUserRequestMutationVariables,
UpdateRestUserRequestDocument,
CreateGqlRootUserCollectionMutation,
CreateGqlRootUserCollectionMutationVariables,
CreateGqlRootUserCollectionDocument,
CreateGqlUserRequestMutation,
CreateGqlUserRequestMutationVariables,
CreateGqlUserRequestDocument,
CreateGqlChildUserCollectionMutation,
CreateGqlChildUserCollectionMutationVariables,
CreateGqlChildUserCollectionDocument,
RenameUserCollectionDocument,
RenameUserCollectionMutation,
RenameUserCollectionMutationVariables,
ReqType,
UpdateGqlUserRequestDocument,
UpdateGqlUserRequestMutation,
UpdateGqlUserRequestMutationVariables,
UpdateGqlUserRequestDocument,
GetGqlRootUserCollectionsQuery,
GetGqlRootUserCollectionsQueryVariables,
GetGqlRootUserCollectionsDocument,
ReqType,
UpdateRestUserRequestDocument,
UpdateRestUserRequestMutation,
UpdateRestUserRequestMutationVariables,
UpdateUserCollectionOrderDocument,
UpdateUserCollectionOrderMutation,
UpdateUserCollectionOrderMutationVariables,
UserCollectionCreatedDocument,
UserCollectionDuplicatedDocument,
UserCollectionMovedDocument,
UserCollectionOrderUpdatedDocument,
UserCollectionRemovedDocument,
UserCollectionUpdatedDocument,
UserRequestCreatedDocument,
UserRequestDeletedDocument,
UserRequestMovedDocument,
UserRequestUpdatedDocument,
} from "../../api/generated/graphql"
export const createRESTRootUserCollection = (title: string) =>
@@ -292,6 +293,12 @@ export const runUserCollectionOrderUpdatedSubscription = () =>
variables: {},
})
export const runUserCollectionDuplicatedSubscription = () =>
runGQLSubscription({
query: UserCollectionDuplicatedDocument,
variables: {},
})
export const runUserRequestCreatedSubscription = () =>
runGQLSubscription({ query: UserRequestCreatedDocument, variables: {} })

View File

@@ -1,10 +1,11 @@
import { authEvents$, def as platformAuth } from "@platform/auth"
import { CollectionsPlatformDef } from "@hoppscotch/common/platform/collections"
import { authEvents$, def as platformAuth } from "@platform/auth"
import { runDispatchWithOutSyncing } from "../../lib/sync"
import {
exportUserCollectionsToJSON,
runUserCollectionCreatedSubscription,
runUserCollectionDuplicatedSubscription,
runUserCollectionMovedSubscription,
runUserCollectionOrderUpdatedSubscription,
runUserCollectionRemovedSubscription,
@@ -16,44 +17,51 @@ import {
} from "./collections.api"
import { collectionsSyncer, getStoreByCollectionType } from "./collections.sync"
import * as E from "fp-ts/Either"
import {
addRESTCollection,
setRESTCollections,
editRESTCollection,
removeRESTCollection,
moveRESTFolder,
updateRESTCollectionOrder,
saveRESTRequestAs,
navigateToFolderWithIndexPath,
editRESTRequest,
removeRESTRequest,
moveRESTRequest,
updateRESTRequestOrder,
addRESTFolder,
editRESTFolder,
removeRESTFolder,
addGraphqlFolder,
addGraphqlCollection,
editGraphqlFolder,
editGraphqlCollection,
removeGraphqlFolder,
removeGraphqlCollection,
saveGraphqlRequestAs,
editGraphqlRequest,
moveGraphqlRequest,
removeGraphqlRequest,
setGraphqlCollections,
restCollectionStore,
} from "@hoppscotch/common/newstore/collections"
import { runGQLSubscription } from "@hoppscotch/common/helpers/backend/GQLClient"
import {
addGraphqlCollection,
addGraphqlFolder,
addRESTCollection,
addRESTFolder,
editGraphqlCollection,
editGraphqlFolder,
editGraphqlRequest,
editRESTCollection,
editRESTFolder,
editRESTRequest,
moveGraphqlRequest,
moveRESTFolder,
moveRESTRequest,
navigateToFolderWithIndexPath,
removeGraphqlCollection,
removeGraphqlFolder,
removeGraphqlRequest,
removeRESTCollection,
removeRESTFolder,
removeRESTRequest,
restCollectionStore,
saveGraphqlRequestAs,
saveRESTRequestAs,
setGraphqlCollections,
setRESTCollections,
updateRESTCollectionOrder,
updateRESTRequestOrder,
} from "@hoppscotch/common/newstore/collections"
import {
GQLHeader,
HoppCollection,
HoppGQLRequest,
HoppRESTHeaders,
HoppRESTParam,
HoppRESTRequest,
} from "@hoppscotch/data"
import * as E from "fp-ts/Either"
import {
ReqType,
UserCollectionDuplicatedData,
UserRequest,
} from "../../api/generated/graphql"
import { gqlCollectionsSyncer } from "./gqlCollections.sync"
import { ReqType } from "../../api/generated/graphql"
function initCollectionsSync() {
const currentUser$ = platformAuth.getCurrentUserStream()
@@ -89,6 +97,7 @@ type ExportedUserCollectionREST = {
folders: ExportedUserCollectionREST[]
requests: Array<HoppRESTRequest & { id: string }>
name: string
data: string
}
type ExportedUserCollectionGQL = {
@@ -96,6 +105,16 @@ type ExportedUserCollectionGQL = {
folders: ExportedUserCollectionGQL[]
requests: Array<HoppGQLRequest & { id: string }>
name: string
data: string
}
function addDescriptionField(
candidate: HoppRESTHeaders | GQLHeader[] | HoppRESTParam[]
) {
return candidate.map((item) => ({
...item,
description: "description" in item ? item.description : "",
}))
}
function exportedCollectionToHoppCollection(
@@ -105,9 +124,17 @@ function exportedCollectionToHoppCollection(
if (collectionType == "REST") {
const restCollection = collection as ExportedUserCollectionREST
const data =
restCollection.data && restCollection.data !== "null"
? JSON.parse(restCollection.data)
: {
auth: { authType: "inherit", authActive: false },
headers: [],
}
return {
id: restCollection.id,
v: 1,
v: 4,
name: restCollection.name,
folders: restCollection.folders.map((folder) =>
exportedCollectionToHoppCollection(folder, collectionType)
@@ -133,42 +160,70 @@ function exportedCollectionToHoppCollection(
requestVariables,
responses,
} = request
const resolvedParams = addDescriptionField(params)
const resolvedHeaders = addDescriptionField(headers)
return {
v,
id,
name,
endpoint,
method,
params,
params: resolvedParams,
requestVariables,
auth,
headers,
headers: resolvedHeaders,
body,
preRequestScript,
testScript,
requestVariables,
responses,
}
}),
auth: data.auth,
headers: addDescriptionField(data.headers),
}
} else {
const gqlCollection = collection as ExportedUserCollectionGQL
const data =
gqlCollection.data && gqlCollection.data !== "null"
? JSON.parse(gqlCollection.data)
: {
auth: { authType: "inherit", authActive: false },
headers: [],
}
return {
id: gqlCollection.id,
v: 1,
v: 4,
name: gqlCollection.name,
folders: gqlCollection.folders.map((folder) =>
exportedCollectionToHoppCollection(folder, collectionType)
),
requests: gqlCollection.requests.map(
({ v, auth, headers, name, id }) => ({
requests: gqlCollection.requests.map((request) => {
const requestParsedResult = HoppGQLRequest.safeParse(request)
if (requestParsedResult.type === "ok") {
return requestParsedResult.value
}
const { v, auth, headers, name, id, query, url, variables } = request
const resolvedHeaders = addDescriptionField(headers)
return {
id,
v,
auth,
headers,
headers: resolvedHeaders,
name,
})
) as HoppGQLRequest[],
query,
url,
variables,
}
}),
auth: data.auth,
headers: addDescriptionField(data.headers),
}
}
}
@@ -178,7 +233,6 @@ async function loadUserCollections(collectionType: "REST" | "GQL") {
undefined,
collectionType == "REST" ? ReqType.Rest : ReqType.Gql
)
if (E.isRight(res)) {
const collectionsJSONString =
res.right.exportUserCollectionsToJSON.exportedCollection
@@ -187,7 +241,6 @@ async function loadUserCollections(collectionType: "REST" | "GQL") {
ExportedUserCollectionGQL | ExportedUserCollectionREST
>
).map((collection) => ({ v: 1, ...collection }))
runDispatchWithOutSyncing(() => {
collectionType == "REST"
? setRESTCollections(
@@ -221,6 +274,9 @@ function setupSubscriptions() {
const userCollectionMovedSub = setupUserCollectionMovedSubscription()
const userCollectionOrderUpdatedSub =
setupUserCollectionOrderUpdatedSubscription()
const userCollectionDuplicatedSub =
setupUserCollectionDuplicatedSubscription()
const userRequestCreatedSub = setupUserRequestCreatedSubscription()
const userRequestUpdatedSub = setupUserRequestUpdatedSubscription()
const userRequestDeletedSub = setupUserRequestDeletedSubscription()
@@ -232,6 +288,7 @@ function setupSubscriptions() {
userCollectionRemovedSub,
userCollectionMovedSub,
userCollectionOrderUpdatedSub,
userCollectionDuplicatedSub,
userRequestCreatedSub,
userRequestUpdatedSub,
userRequestDeletedSub,
@@ -302,19 +359,32 @@ function setupUserCollectionCreatedSubscription() {
})
} else {
// root collections won't have parentCollectionID
const data =
res.right.userCollectionCreated.data &&
res.right.userCollectionCreated.data != "null"
? JSON.parse(res.right.userCollectionCreated.data)
: {
auth: { authType: "inherit", authActive: false },
headers: [],
}
runDispatchWithOutSyncing(() => {
collectionType == "GQL"
? addGraphqlCollection({
name: res.right.userCollectionCreated.title,
folders: [],
requests: [],
v: 1,
v: 4,
auth: data.auth,
headers: addDescriptionField(data.headers),
})
: addRESTCollection({
name: res.right.userCollectionCreated.title,
folders: [],
requests: [],
v: 1,
v: 4,
auth: data.auth,
headers: addDescriptionField(data.headers),
})
const localIndex = collectionStore.value.state.length - 1
@@ -491,6 +561,147 @@ function setupUserCollectionOrderUpdatedSubscription() {
return userCollectionOrderUpdatedSub
}
function setupUserCollectionDuplicatedSubscription() {
const [userCollectionDuplicated$, userCollectionDuplicatedSub] =
runUserCollectionDuplicatedSubscription()
userCollectionDuplicated$.subscribe((res) => {
if (E.isRight(res)) {
const {
childCollections: childCollectionsJSONStr,
data,
id,
parentID: parentCollectionID,
requests: userRequests,
title: name,
type: collectionType,
} = res.right.userCollectionDuplicated
const { collectionStore } = getStoreByCollectionType(collectionType)
const parentCollectionPath =
parentCollectionID &&
getCollectionPathFromCollectionID(
parentCollectionID,
collectionStore.value.state
)
// Incoming data transformed to the respective internal representations
const { auth, headers } =
data && data != "null"
? JSON.parse(data)
: {
auth: { authType: "inherit", authActive: false },
headers: [],
}
const folders = transformDuplicatedCollections(childCollectionsJSONStr)
const requests = transformDuplicatedCollectionRequests(
userRequests as UserRequest[]
)
// New collection to be added to store with the transformed data
const effectiveDuplicatedCollection: HoppCollection = {
id,
name,
folders,
requests,
v: 4,
auth,
headers: addDescriptionField(headers),
}
// only folders will have parent collection id
if (parentCollectionID && parentCollectionPath) {
const collectionCreatedFromStoreIDSuffix = "-duplicate-collection"
const parentCollection = navigateToFolderWithIndexPath(
collectionStore.value.state,
parentCollectionPath
.split("/")
.map((pathIndex) => parseInt(pathIndex))
)
if (!parentCollection) {
return
}
// Grab the child collection inserted via store update with the ID suffix
const collectionInsertedViaStoreUpdateIdx =
parentCollection.folders.findIndex(({ id }) =>
id?.endsWith(collectionCreatedFromStoreIDSuffix)
)
if (collectionInsertedViaStoreUpdateIdx === -1) {
return
}
const collectionInsertedViaStoreUpdateIndexPath = `${parentCollectionPath}/${collectionInsertedViaStoreUpdateIdx}`
runDispatchWithOutSyncing(() => {
/**
* Step 1. Remove the collection inserted via store update with the ID suffix
* Step 2. Add the duplicated collection received from the GQL subscription
* Step 3. Update the duplicated collection with the relevant data
*/
if (collectionType === "GQL") {
removeGraphqlFolder(collectionInsertedViaStoreUpdateIndexPath)
addGraphqlFolder(name, parentCollectionPath)
editGraphqlFolder(
collectionInsertedViaStoreUpdateIndexPath,
effectiveDuplicatedCollection
)
} else {
removeRESTFolder(collectionInsertedViaStoreUpdateIndexPath)
addRESTFolder(name, parentCollectionPath)
editRESTFolder(
collectionInsertedViaStoreUpdateIndexPath,
effectiveDuplicatedCollection
)
}
})
} else {
// root collections won't have `parentCollectionID`
const collectionCreatedFromStoreIDSuffix = "-duplicate-collection"
// Grab the child collection inserted via store update with the ID suffix
const collectionInsertedViaStoreUpdateIdx =
collectionStore.value.state.findIndex(({ id }) =>
id?.endsWith(collectionCreatedFromStoreIDSuffix)
)
if (collectionInsertedViaStoreUpdateIdx === -1) {
return
}
runDispatchWithOutSyncing(() => {
/**
* Step 1. Remove the collection inserted via store update with the ID suffix
* Step 2. Add the duplicated collection received from the GQL subscription
*/
if (collectionType === "GQL") {
removeGraphqlCollection(collectionInsertedViaStoreUpdateIdx)
addGraphqlCollection(effectiveDuplicatedCollection)
} else {
removeRESTCollection(collectionInsertedViaStoreUpdateIdx)
addRESTCollection(effectiveDuplicatedCollection)
}
})
}
}
})
return userCollectionDuplicatedSub
}
function setupUserRequestCreatedSubscription() {
const [userRequestCreated$, userRequestCreatedSub] =
runUserRequestCreatedSubscription()
@@ -797,3 +1008,52 @@ function getRequestIndex(
return requestIndex
}
function transformDuplicatedCollections(
collectionsJSONStr: string
): HoppCollection[] {
const parsedCollections: UserCollectionDuplicatedData[] =
JSON.parse(collectionsJSONStr)
return parsedCollections.map(
({
childCollections: childCollectionsJSONStr,
data,
id,
requests: userRequests,
title: name,
}) => {
const { auth, headers } =
data && data !== "null"
? JSON.parse(data)
: { auth: { authType: "inherit", authActive: false }, headers: [] }
const folders = transformDuplicatedCollections(childCollectionsJSONStr)
const requests = transformDuplicatedCollectionRequests(userRequests)
return {
id,
name,
folders,
requests,
v: 4,
auth,
headers: addDescriptionField(headers),
}
}
)
}
function transformDuplicatedCollectionRequests(
requests: UserRequest[]
): HoppRESTRequest[] | HoppGQLRequest[] {
return requests.map(({ id, request }) => {
const parsedRequest = JSON.parse(request)
return {
...parsedRequest,
id,
}
})
}