Browse Source

refactor: persist request handles under tab `saveContext`

Only the IDs (workspace, provider & request IDs) to restore the handle are stored under `localStorage` and the handle is restored back at runtime.
jamesgeorge007 10 months ago
parent
commit
af27177070

+ 1 - 0
packages/hoppscotch-common/src/components/collections/SaveRequest.vue

@@ -277,6 +277,7 @@ const saveRequestAs = async () => {
         workspaceID,
         providerID,
         requestID,
+        requestHandle,
       },
     }
 

+ 32 - 10
packages/hoppscotch-common/src/components/new-collections/rest/index.vue

@@ -517,7 +517,7 @@ import { platform } from "~/platform"
 import { NewWorkspaceService } from "~/services/new-workspace"
 import { HandleRef } from "~/services/new-workspace/handle"
 import { RESTCollectionViewRequest } from "~/services/new-workspace/view"
-import { Workspace } from "~/services/new-workspace/workspace"
+import { Workspace, WorkspaceRequest } from "~/services/new-workspace/workspace"
 import { RESTTabService } from "~/services/tab/rest"
 import IconImport from "~icons/lucide/folder-down"
 import IconHelpCircle from "~icons/lucide/help-circle"
@@ -778,9 +778,15 @@ const onRemoveRootCollection = async () => {
     if (
       tab.document.saveContext?.originLocation === "workspace-user-collection"
     ) {
-      const { requestID } = tab.document.saveContext
+      const requestHandle = tab.document.saveContext?.requestHandle as
+        | HandleRef<WorkspaceRequest>["value"]
+        | undefined
 
-      if (requestID.startsWith(collectionIndexPath)) {
+      if (requestHandle?.type === "invalid") {
+        continue
+      }
+
+      if (requestHandle!.data.requestID.startsWith(collectionIndexPath)) {
         tab.document.saveContext = null
         tab.document.isDirty = true
       }
@@ -867,6 +873,7 @@ const onAddRequest = async (requestName: string) => {
       workspaceID,
       providerID,
       requestID,
+      requestHandle,
     },
     inheritedProperties: {
       auth,
@@ -1055,15 +1062,22 @@ const onRemoveChildCollection = async () => {
     return
   }
 
+  // TODO: Tab holding a request under the collection should be aware of the parent collection invalidation and toggle the dirty state
   const activeTabs = tabs.getActiveTabs()
 
   for (const tab of activeTabs.value) {
     if (
       tab.document.saveContext?.originLocation === "workspace-user-collection"
     ) {
-      const { requestID } = tab.document.saveContext
+      const requestHandle = tab.document.saveContext?.requestHandle as
+        | HandleRef<WorkspaceRequest>["value"]
+        | undefined
+
+      if (requestHandle?.type === "invalid") {
+        continue
+      }
 
-      if (requestID.startsWith(parentCollectionIndexPath)) {
+      if (requestHandle!.data.requestID.startsWith(parentCollectionIndexPath)) {
         tab.document.saveContext = null
         tab.document.isDirty = true
       }
@@ -1209,9 +1223,7 @@ const selectRequest = async (requestIndexPath: string) => {
   // If there is a request with this save context, switch into it
   const possibleTab = tabs.getTabRefWithSaveContext({
     originLocation: "workspace-user-collection",
-    workspaceID,
-    providerID,
-    requestID,
+    requestHandle,
   })
 
   if (possibleTab) {
@@ -1226,6 +1238,7 @@ const selectRequest = async (requestIndexPath: string) => {
         workspaceID,
         providerID,
         requestID,
+        requestHandle,
       },
       inheritedProperties: {
         auth,
@@ -2177,9 +2190,18 @@ const isActiveRequest = (requestView: RESTCollectionViewRequest) => {
     return false
   }
 
-  const { requestID } = tabs.currentActiveTab.value.document.saveContext
+  // TODO: Investigate why requestHandle is available unwrapped here
+  const requestHandle = tabs.currentActiveTab.value.document.saveContext
+    .requestHandle as HandleRef<WorkspaceRequest>["value"] | undefined
+  if (!requestHandle) {
+    return false
+  }
+
+  if (requestHandle.type === "invalid") {
+    return false
+  }
 
-  return requestID === requestView.requestID
+  return requestHandle.data.requestID === requestView.requestID
 }
 
 const onSelectPick = (payload: Picked | null) => {

+ 7 - 0
packages/hoppscotch-common/src/helpers/rest/document.ts

@@ -3,6 +3,8 @@ import { HoppRESTResponse } from "../types/HoppRESTResponse"
 import { HoppTestResult } from "../types/HoppTestResult"
 import { RESTOptionTabs } from "~/components/http/RequestOptions.vue"
 import { HoppInheritedProperty } from "../types/HoppInheritedProperties"
+import { HandleRef } from "~/services/new-workspace/handle"
+import { WorkspaceRequest } from "~/services/new-workspace/workspace"
 
 export type HoppRESTSaveContext =
   | {
@@ -25,6 +27,11 @@ export type HoppRESTSaveContext =
        * Path to the request in the collection tree
        */
       requestID: string
+
+      /**
+       * Handle to the request open in the tab
+       */
+      requestHandle?: HandleRef<WorkspaceRequest>
     }
   | {
       /**

+ 1 - 0
packages/hoppscotch-common/src/services/persistence/validation-schemas/index.ts

@@ -498,6 +498,7 @@ const HoppRESTSaveContextSchema = z.nullable(
         workspaceID: z.string(),
         providerID: z.string(),
         requestID: z.string(),
+        requestHandle: z.optional(z.record(z.unknown())),
       })
       .strict(),
     z

+ 15 - 1
packages/hoppscotch-common/src/services/tab/rest.ts

@@ -3,7 +3,12 @@ import { computed } from "vue"
 import { getDefaultRESTRequest } from "~/helpers/rest/default"
 import { HoppRESTDocument, HoppRESTSaveContext } from "~/helpers/rest/document"
 import { TabService } from "./tab"
+<<<<<<< HEAD
 import { Container } from "dioc"
+=======
+import { HandleRef } from "../new-workspace/handle"
+import { WorkspaceRequest } from "../new-workspace/workspace"
+>>>>>>> 854ffa28 (refactor: persist request handles under tab `saveContext`)
 
 export class RESTTabService extends TabService<HoppRESTDocument> {
   public static readonly ID = "REST_TAB_SERVICE"
@@ -58,7 +63,16 @@ export class RESTTabService extends TabService<HoppRESTDocument> {
         ctx?.originLocation === "workspace-user-collection" &&
         tab.document.saveContext?.originLocation === "workspace-user-collection"
       ) {
-        if (isEqual(ctx, tab.document.saveContext)) {
+        if (
+          isEqual(
+            ctx.requestHandle?.value,
+
+            // TODO: Investigate why requestHandle gets unwrapped
+            tab.document.saveContext.requestHandle as
+              | HandleRef<WorkspaceRequest>["value"]
+              | undefined
+          )
+        ) {
           return this.getTabRef(tab.id)
         }
       }

+ 99 - 4
packages/hoppscotch-common/src/services/tab/tab.ts

@@ -1,5 +1,6 @@
 import { refWithControl } from "@vueuse/core"
 import { Service } from "dioc"
+import * as E from "fp-ts/Either"
 import { v4 as uuidV4 } from "uuid"
 import {
   ComputedRef,
@@ -10,16 +11,23 @@ import {
   shallowReadonly,
   watch,
 } from "vue"
+import { HoppRESTDocument } from "~/helpers/rest/document"
 import {
   HoppTab,
   PersistableTabState,
   TabService as TabServiceInterface,
 } from "."
 
+import { NewWorkspaceService } from "../new-workspace"
+import { HandleRef } from "../new-workspace/handle"
+import { WorkspaceRequest } from "../new-workspace/workspace"
+
 export abstract class TabService<Doc>
   extends Service
   implements TabServiceInterface<Doc>
 {
+  private workspaceService = this.bind(NewWorkspaceService)
+
   protected tabMap = reactive(new Map<string, HoppTab<Doc>>())
   protected tabOrdering = ref<string[]>(["test"])
 
@@ -82,15 +90,65 @@ export abstract class TabService<Doc>
     this.currentTabID.value = tabID
   }
 
-  public loadTabsFromPersistedState(data: PersistableTabState<Doc>): void {
+  public async loadTabsFromPersistedState(
+    data: PersistableTabState<Doc>
+  ): Promise<void> {
     if (data) {
       this.tabMap.clear()
       this.tabOrdering.value = []
 
       for (const doc of data.orderedDocs) {
+        let requestHandle: HandleRef<WorkspaceRequest> | null = null
+        let resolvedTabDoc = doc.doc
+
+        // TODO: Account for GQL
+        const { saveContext } = doc.doc as HoppRESTDocument
+
+        if (saveContext?.originLocation === "workspace-user-collection") {
+          const { providerID, requestID, workspaceID } = saveContext
+
+          if (!providerID || !workspaceID || !requestID) {
+            continue
+          }
+
+          const workspaceHandleResult =
+            await this.workspaceService.getWorkspaceHandle(
+              providerID!,
+              workspaceID!
+            )
+
+          if (E.isLeft(workspaceHandleResult)) {
+            continue
+          }
+
+          const workspaceHandle = workspaceHandleResult.right
+
+          if (workspaceHandle.value.type === "invalid") {
+            continue
+          }
+
+          const requestHandleResult =
+            await this.workspaceService.getRequestHandle(
+              workspaceHandle,
+              requestID!
+            )
+
+          if (E.isRight(requestHandleResult)) {
+            requestHandle = requestHandleResult.right
+
+            resolvedTabDoc = {
+              ...resolvedTabDoc,
+              saveContext: {
+                ...saveContext,
+                requestHandle,
+              },
+            }
+          }
+        }
+
         this.tabMap.set(doc.tabID, {
           id: doc.tabID,
-          document: doc.doc,
+          document: resolvedTabDoc,
         })
 
         this.tabOrdering.value.push(doc.tabID)
@@ -99,7 +157,6 @@ export abstract class TabService<Doc>
       this.setActiveTab(data.lastActiveTabID)
     }
   }
-
   public getActiveTabs(): Readonly<ComputedRef<HoppTab<Doc>[]>> {
     return shallowReadonly(
       computed(() => this.tabOrdering.value.map((x) => this.tabMap.get(x)!))
@@ -180,13 +237,51 @@ export abstract class TabService<Doc>
     this.currentTabID.value = tabID
   }
 
+  private getPersistedDocument(tabDoc: Doc): Doc {
+    const { saveContext } = tabDoc as HoppRESTDocument
+
+    if (saveContext?.originLocation !== "workspace-user-collection") {
+      return tabDoc
+    }
+
+    const { requestHandle } = saveContext
+
+    if (!requestHandle) {
+      return tabDoc
+    }
+
+    if (requestHandle.value.type === "invalid") {
+      // eslint-disable-next-line @typescript-eslint/no-unused-vars
+      const { requestHandle, ...rest } = saveContext
+
+      // Return the document without the handle
+      return {
+        ...tabDoc,
+        saveContext: rest,
+      }
+    }
+
+    const { providerID, workspaceID, requestID } = requestHandle.value.data
+
+    // Return the document without the handle
+    return {
+      ...tabDoc,
+      saveContext: {
+        originLocation: "workspace-user-collection",
+        requestID,
+        providerID,
+        workspaceID,
+      },
+    }
+  }
+
   public persistableTabState = computed<PersistableTabState<Doc>>(() => ({
     lastActiveTabID: this.currentTabID.value,
     orderedDocs: this.tabOrdering.value.map((tabID) => {
       const tab = this.tabMap.get(tabID)! // tab ordering is guaranteed to have value for this key
       return {
         tabID: tab.id,
-        doc: tab.document,
+        doc: this.getPersistedDocument(tab.document),
       }
     }),
   }))