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

@@ -102,11 +102,7 @@
@keyup.r="requestAction?.$el.click()"
@keyup.n="folderAction?.$el.click()"
@keyup.e="edit?.$el.click()"
@keyup.d="
showDuplicateCollectionAction
? duplicateAction?.$el.click()
: null
"
@keyup.d="duplicateAction?.$el.click()"
@keyup.delete="deleteAction?.$el.click()"
@keyup.x="exportAction?.$el.click()"
@keyup.p="propertiesAction?.$el.click()"
@@ -150,7 +146,6 @@
"
/>
<HoppSmartItem
v-if="showDuplicateCollectionAction"
ref="duplicateAction"
:icon="IconCopy"
:label="t('action.duplicate')"
@@ -370,21 +365,6 @@ const collectionName = computed(() => {
return (props.data as TeamCollection).title
})
const showDuplicateCollectionAction = computed(() => {
// Show if the user is not logged in
if (!currentUser.value) {
return true
}
if (props.collectionsType === "team-collections") {
return true
}
// Duplicate collection action is disabled on SH until the issue with syncing is resolved
return !platform.platformFeatureFlags
.duplicateCollectionDisabledInPersonalWorkspace
})
watch(
() => [props.exportLoading, props.duplicateCollectionLoading],
([newExportLoadingVal, newDuplicateCollectionLoadingVal]) => {

View File

@@ -73,11 +73,7 @@
@keyup.r="requestAction.$el.click()"
@keyup.n="folderAction.$el.click()"
@keyup.e="edit.$el.click()"
@keyup.d="
showDuplicateCollectionAction
? duplicateAction.$el.click()
: null
"
@keyup.d="duplicateAction.$el.click()"
@keyup.delete="deleteAction.$el.click()"
@keyup.p="propertiesAction.$el.click()"
@keyup.escape="hide()"
@@ -123,7 +119,6 @@
"
/>
<HoppSmartItem
v-if="showDuplicateCollectionAction"
ref="duplicateAction"
:icon="IconCopy"
:label="t('action.duplicate')"
@@ -355,17 +350,6 @@ const collectionIcon = computed(() => {
return IconFolder
})
const showDuplicateCollectionAction = computed(() => {
// Show if the user is not logged in
if (!currentUser.value) {
return true
}
// Duplicate collection action is disabled on SH until the issue with syncing is resolved
return !platform.platformFeatureFlags
.duplicateCollectionDisabledInPersonalWorkspace
})
const pick = () => {
emit("select", {
pickedType: "gql-my-collection",

View File

@@ -70,11 +70,7 @@
@keyup.r="requestAction.$el.click()"
@keyup.n="folderAction.$el.click()"
@keyup.e="edit.$el.click()"
@keyup.d="
showDuplicateCollectionAction
? duplicateAction.$el.click()
: null
"
@keyup.d="duplicateAction.$el.click()"
@keyup.delete="deleteAction.$el.click()"
@keyup.p="propertiesAction.$el.click()"
@keyup.escape="hide()"
@@ -116,7 +112,6 @@
"
/>
<HoppSmartItem
v-if="showDuplicateCollectionAction"
ref="duplicateAction"
:icon="IconCopy"
:label="t('action.duplicate')"
@@ -319,17 +314,6 @@ const collectionIcon = computed(() => {
return IconFolder
})
const showDuplicateCollectionAction = computed(() => {
// Show if the user is not logged in
if (!currentUser.value) {
return true
}
// Duplicate collection action is disabled on SH until the issue with syncing is resolved
return !platform.platformFeatureFlags
.duplicateCollectionDisabledInPersonalWorkspace
})
const pick = () => {
emit("select", {
pickedType: "gql-my-folder",

View File

@@ -53,12 +53,6 @@ export type PlatformDef = {
* Whether to show the A/B testing workspace switcher click login flow or not
*/
workspaceSwitcherLogin?: Ref<boolean>
/**
* There's an active issue wrt syncing in personal workspace under SH while duplicating a collection
* This is a temporary flag to disable the same
*/
duplicateCollectionDisabledInPersonalWorkspace?: boolean
}
infra?: InfraPlatformDef
experiments?: ExperimentsPlatformDef

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,
}
})
}

View File

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

View File

@@ -40,7 +40,6 @@ createHoppApp("#app", {
platformFeatureFlags: {
exportAsGIST: false,
hasTelemetry: false,
duplicateCollectionDisabledInPersonalWorkspace: true,
},
infra: InfraPlatform,
})

View File

@@ -39,6 +39,7 @@ import {
UserCollectionRemovedDocument,
UserCollectionMovedDocument,
UserCollectionOrderUpdatedDocument,
UserCollectionDuplicatedDocument,
ExportUserCollectionsToJsonQuery,
ExportUserCollectionsToJsonQueryVariables,
ExportUserCollectionsToJsonDocument,
@@ -328,6 +329,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/auth.platform"
import { CollectionsPlatformDef } from "@hoppscotch/common/platform/collections"
import { authEvents$, def as platformAuth } from "@platform/auth/auth.platform"
import { runDispatchWithOutSyncing } from "../../lib/sync"
import {
exportUserCollectionsToJSON,
runUserCollectionCreatedSubscription,
runUserCollectionDuplicatedSubscription,
runUserCollectionMovedSubscription,
runUserCollectionOrderUpdatedSubscription,
runUserCollectionRemovedSubscription,
@@ -16,37 +17,36 @@ 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,
@@ -55,8 +55,13 @@ import {
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()
@@ -269,6 +274,9 @@ function setupSubscriptions() {
const userCollectionMovedSub = setupUserCollectionMovedSubscription()
const userCollectionOrderUpdatedSub =
setupUserCollectionOrderUpdatedSubscription()
const userCollectionDuplicatedSub =
setupUserCollectionDuplicatedSubscription()
const userRequestCreatedSub = setupUserRequestCreatedSubscription()
const userRequestUpdatedSub = setupUserRequestUpdatedSubscription()
const userRequestDeletedSub = setupUserRequestDeletedSubscription()
@@ -280,6 +288,7 @@ function setupSubscriptions() {
userCollectionRemovedSub,
userCollectionMovedSub,
userCollectionOrderUpdatedSub,
userCollectionDuplicatedSub,
userRequestCreatedSub,
userRequestUpdatedSub,
userRequestDeletedSub,
@@ -314,20 +323,6 @@ 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(
@@ -566,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: 3,
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()
@@ -873,104 +1009,51 @@ 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"
function transformDuplicatedCollections(
collectionsJSONStr: string
): HoppCollection[] {
const parsedCollections: UserCollectionDuplicatedData[] =
JSON.parse(collectionsJSONStr)
// Duplicating a child collection
if (parentCollectionID) {
// Get the index path for the parent collection
const parentCollectionPath = getCollectionPathFromCollectionID(
parentCollectionID,
collectionStore.value.state
)
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: [] }
if (!parentCollectionPath) {
// Indicates the collection received from the GQL subscription should be created in the store
return true
}
const folders = transformDuplicatedCollections(childCollectionsJSONStr)
const parentCollection = navigateToFolderWithIndexPath(
collectionStore.value.state,
parentCollectionPath.split("/").map((index) => parseInt(index))
)
const requests = transformDuplicatedCollectionRequests(userRequests)
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,
})
return {
id,
name,
folders,
requests,
v: 3,
auth,
headers: addDescriptionField(headers),
}
})
} 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
)
}
function transformDuplicatedCollectionRequests(
requests: UserRequest[]
): HoppRESTRequest[] | HoppGQLRequest[] {
return requests.map(({ id, request }) => {
const parsedRequest = JSON.parse(request)
return {
...parsedRequest,
id,
}
})
}