diff --git a/packages/hoppscotch-common/src/components/collections/SaveRequest.vue b/packages/hoppscotch-common/src/components/collections/SaveRequest.vue index 3800123e4..6abc98bb6 100644 --- a/packages/hoppscotch-common/src/components/collections/SaveRequest.vue +++ b/packages/hoppscotch-common/src/components/collections/SaveRequest.vue @@ -277,6 +277,7 @@ const saveRequestAs = async () => { workspaceID, providerID, requestID, + requestHandle, }, } diff --git a/packages/hoppscotch-common/src/components/new-collections/rest/index.vue b/packages/hoppscotch-common/src/components/new-collections/rest/index.vue index a92e4d0f6..d5a3de324 100644 --- a/packages/hoppscotch-common/src/components/new-collections/rest/index.vue +++ b/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["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["value"] + | undefined - if (requestID.startsWith(parentCollectionIndexPath)) { + if (requestHandle?.type === "invalid") { + continue + } + + 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["value"] | undefined + if (!requestHandle) { + return false + } - return requestID === requestView.requestID + if (requestHandle.type === "invalid") { + return false + } + + return requestHandle.data.requestID === requestView.requestID } const onSelectPick = (payload: Picked | null) => { diff --git a/packages/hoppscotch-common/src/helpers/rest/document.ts b/packages/hoppscotch-common/src/helpers/rest/document.ts index bc48cb0c1..a751d54d8 100644 --- a/packages/hoppscotch-common/src/helpers/rest/document.ts +++ b/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 } | { /** diff --git a/packages/hoppscotch-common/src/services/persistence/validation-schemas/index.ts b/packages/hoppscotch-common/src/services/persistence/validation-schemas/index.ts index 94f613713..168f6f265 100644 --- a/packages/hoppscotch-common/src/services/persistence/validation-schemas/index.ts +++ b/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 diff --git a/packages/hoppscotch-common/src/services/tab/rest.ts b/packages/hoppscotch-common/src/services/tab/rest.ts index 93081de66..198cdd754 100644 --- a/packages/hoppscotch-common/src/services/tab/rest.ts +++ b/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 { public static readonly ID = "REST_TAB_SERVICE" @@ -58,7 +63,16 @@ export class RESTTabService extends TabService { 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["value"] + | undefined + ) + ) { return this.getTabRef(tab.id) } } diff --git a/packages/hoppscotch-common/src/services/tab/tab.ts b/packages/hoppscotch-common/src/services/tab/tab.ts index cf3699040..5683b989c 100644 --- a/packages/hoppscotch-common/src/services/tab/tab.ts +++ b/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 extends Service implements TabServiceInterface { + private workspaceService = this.bind(NewWorkspaceService) + protected tabMap = reactive(new Map>()) protected tabOrdering = ref(["test"]) @@ -82,15 +90,65 @@ export abstract class TabService this.currentTabID.value = tabID } - public loadTabsFromPersistedState(data: PersistableTabState): void { + public async loadTabsFromPersistedState( + data: PersistableTabState + ): Promise { if (data) { this.tabMap.clear() this.tabOrdering.value = [] for (const doc of data.orderedDocs) { + let requestHandle: HandleRef | 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 this.setActiveTab(data.lastActiveTabID) } } - public getActiveTabs(): Readonly[]>> { return shallowReadonly( computed(() => this.tabOrdering.value.map((x) => this.tabMap.get(x)!)) @@ -180,13 +237,51 @@ export abstract class TabService 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>(() => ({ 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), } }), }))