feat: tab service added (#3367)

This commit is contained in:
Anwarul Islam
2023-10-11 18:51:07 +06:00
committed by GitHub
parent 51510566bc
commit ba31cdabea
60 changed files with 1112 additions and 841 deletions

View File

@@ -29,8 +29,9 @@ import {
setGlobalEnvVariables,
updateEnvironment,
} from "~/newstore/environments"
import { HoppRESTTab } from "./rest/tab"
import { Ref } from "vue"
import { HoppTab } from "~/services/tab"
import { HoppRESTDocument } from "./rest/document"
const getTestableBody = (
res: HoppRESTResponse & { type: "success" | "fail" }
@@ -69,7 +70,7 @@ export const executedResponses$ = new Subject<
>()
export function runRESTRequest$(
tab: Ref<HoppRESTTab>
tab: Ref<HoppTab<HoppRESTDocument>>
): [
() => void,
Promise<
@@ -127,7 +128,7 @@ export function runRESTRequest$(
)()
if (E.isRight(runResult)) {
tab.value.testResults = translateToSandboxTestResults(
tab.value.document.testResults = translateToSandboxTestResults(
runResult.right
)
@@ -163,7 +164,7 @@ export function runRESTRequest$(
)()
}
} else {
tab.value.testResults = {
tab.value.document.testResults = {
description: "",
expectResults: [],
tests: [],

View File

@@ -1,9 +1,10 @@
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
import { getTabsRefTo } from "../rest/tab"
import { getAffectedIndexes } from "./affectedIndex"
import { GetSingleRequestDocument } from "../backend/graphql"
import { runGQLQuery } from "../backend/GQLClient"
import * as E from "fp-ts/Either"
import { getService } from "~/modules/dioc"
import { RESTTabService } from "~/services/tab/rest"
/**
* Resolve save context on reorder
@@ -56,7 +57,9 @@ export function resolveSaveContextOnCollectionReorder(
}
}
const tabs = getTabsRefTo((tab) => {
const tabService = getService(RESTTabService)
const tabs = tabService.getTabsRefTo((tab) => {
return (
tab.document.saveContext?.originLocation === "user-collection" &&
affectedPaths.has(tab.document.saveContext.folderPath)
@@ -84,7 +87,8 @@ export function updateSaveContextForAffectedRequests(
oldFolderPath: string,
newFolderPath: string
) {
const tabs = getTabsRefTo((tab) => {
const tabService = getService(RESTTabService)
const tabs = tabService.getTabsRefTo((tab) => {
return (
tab.document.saveContext?.originLocation === "user-collection" &&
tab.document.saveContext.folderPath.startsWith(oldFolderPath)
@@ -105,7 +109,8 @@ export function updateSaveContextForAffectedRequests(
}
function resetSaveContextForAffectedRequests(folderPath: string) {
const tabs = getTabsRefTo((tab) => {
const tabService = getService(RESTTabService)
const tabs = tabService.getTabsRefTo((tab) => {
return (
tab.document.saveContext?.originLocation === "user-collection" &&
tab.document.saveContext.folderPath.startsWith(folderPath)
@@ -124,7 +129,8 @@ function resetSaveContextForAffectedRequests(folderPath: string) {
*/
export async function resetTeamRequestsContext() {
const tabs = getTabsRefTo((tab) => {
const tabService = getService(RESTTabService)
const tabs = tabService.getTabsRefTo((tab) => {
return tab.document.saveContext?.originLocation === "team-collection"
})

View File

@@ -1,6 +1,7 @@
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
import { getTabsRefTo } from "../rest/tab"
import { getAffectedIndexes } from "./affectedIndex"
import { RESTTabService } from "~/services/tab/rest"
import { getService } from "~/modules/dioc"
/**
* Resolve save context on reorder
@@ -32,7 +33,8 @@ export function resolveSaveContextOnRequestReorder(payload: {
// if (newIndex === -1) remove it from the map because it will be deleted
if (newIndex === -1) affectedIndexes.delete(lastIndex)
const tabs = getTabsRefTo((tab) => {
const tabService = getService(RESTTabService)
const tabs = tabService.getTabsRefTo((tab) => {
return (
tab.document.saveContext?.originLocation === "user-collection" &&
tab.document.saveContext.folderPath === folderPath &&

View File

@@ -1,4 +1,6 @@
import { HoppGQLRequest } from "@hoppscotch/data"
import { GQLResponseEvent } from "./connection"
import { GQLOptionTabs } from "~/components/graphql/RequestOptions.vue"
export type HoppGQLSaveContext =
| {
@@ -55,4 +57,20 @@ export type HoppGQLDocument = {
* This contains where the request is originated from basically.
*/
saveContext?: HoppGQLSaveContext
/**
* The response as it is in the document
* (if any)
*/
response?: GQLResponseEvent[] | null
/**
* Response tab preference for the current tab's document
*/
responseTabPreference?: string
/**
* Options tab preference for the current tab's document
*/
optionTabPreference?: GQLOptionTabs
}

View File

@@ -1,226 +0,0 @@
import { refWithControl } from "@vueuse/core"
import { isEqual } from "lodash-es"
import { v4 as uuidV4 } from "uuid"
import { computed, reactive, ref, shallowReadonly, watch } from "vue"
import { HoppTestResult } from "../types/HoppTestResult"
import { GQLResponseEvent } from "./connection"
import { getDefaultGQLRequest } from "./default"
import { HoppGQLDocument, HoppGQLSaveContext } from "./document"
export type HoppGQLTab = {
id: string
document: HoppGQLDocument
response?: GQLResponseEvent[] | null
testResults?: HoppTestResult | null
}
export type PersistableGQLTabState = {
lastActiveTabID: string
orderedDocs: Array<{
tabID: string
doc: HoppGQLDocument
}>
}
export const currentTabID = refWithControl("test", {
onBeforeChange(newTabID) {
if (!newTabID || !tabMap.has(newTabID)) {
console.warn(
`Tried to set current tab id to an invalid value. (value: ${newTabID})`
)
// Don't allow change
return false
}
},
})
const tabMap = reactive(
new Map<string, HoppGQLTab>([
[
"test",
{
id: "test",
document: {
request: getDefaultGQLRequest(),
isDirty: false,
},
},
],
])
)
const tabOrdering = ref<string[]>(["test"])
watch(
tabOrdering,
(newOrdering) => {
if (!currentTabID.value || !newOrdering.includes(currentTabID.value)) {
currentTabID.value = newOrdering[newOrdering.length - 1] // newOrdering should always be non-empty
}
},
{ deep: true }
)
export const persistableTabState = computed<PersistableGQLTabState>(() => ({
lastActiveTabID: currentTabID.value,
orderedDocs: tabOrdering.value.map((tabID) => {
const tab = tabMap.get(tabID)! // tab ordering is guaranteed to have value for this key
return {
tabID: tab.id,
doc: tab.document,
}
}),
}))
export const currentActiveTab = computed(() => tabMap.get(currentTabID.value)!) // Guaranteed to not be undefined
// TODO: Mark this unknown and do validations
export function loadTabsFromPersistedState(data: PersistableGQLTabState) {
if (data) {
tabMap.clear()
tabOrdering.value = []
for (const doc of data.orderedDocs) {
tabMap.set(doc.tabID, {
id: doc.tabID,
document: doc.doc,
})
tabOrdering.value.push(doc.tabID)
}
currentTabID.value = data.lastActiveTabID
}
}
/**
* Returns all the active Tab IDs in order
*/
export function getActiveTabs() {
return shallowReadonly(
computed(() => tabOrdering.value.map((x) => tabMap.get(x)!))
)
}
export function getTabRef(tabID: string) {
return computed({
get() {
const result = tabMap.get(tabID)
if (result === undefined) throw new Error(`Invalid tab id: ${tabID}`)
return result
},
set(value) {
return tabMap.set(tabID, value)
},
})
}
function generateNewTabID() {
while (true) {
const id = uuidV4()
if (!tabMap.has(id)) return id
}
}
export function updateTab(tabUpdate: HoppGQLTab) {
if (!tabMap.has(tabUpdate.id)) {
console.warn(
`Cannot update tab as tab with that tab id does not exist (id: ${tabUpdate.id})`
)
}
tabMap.set(tabUpdate.id, tabUpdate)
}
export function createNewTab(document: HoppGQLDocument, switchToIt = true) {
const id = generateNewTabID()
const tab: HoppGQLTab = { id, document }
tabMap.set(id, tab)
tabOrdering.value.push(id)
if (switchToIt) {
currentTabID.value = id
}
return tab
}
export function updateTabOrdering(fromIndex: number, toIndex: number) {
tabOrdering.value.splice(
toIndex,
0,
tabOrdering.value.splice(fromIndex, 1)[0]
)
}
export function closeTab(tabID: string) {
if (!tabMap.has(tabID)) {
console.warn(`Tried to close a tab which does not exist (tab id: ${tabID})`)
return
}
if (tabOrdering.value.length === 1) {
console.warn(
`Tried to close the only tab open, which is not allowed. (tab id: ${tabID})`
)
return
}
tabOrdering.value.splice(tabOrdering.value.indexOf(tabID), 1)
tabMap.delete(tabID)
}
export function getTabRefWithSaveContext(ctx: HoppGQLSaveContext) {
for (const tab of tabMap.values()) {
// For `team-collection` request id can be considered unique
if (ctx && ctx.originLocation === "team-collection") {
if (
tab.document.saveContext?.originLocation === "team-collection" &&
tab.document.saveContext.requestID === ctx.requestID
) {
return getTabRef(tab.id)
}
} else if (isEqual(ctx, tab.document.saveContext)) return getTabRef(tab.id)
}
return null
}
export function getTabsRefTo(func: (tab: HoppGQLTab) => boolean) {
return Array.from(tabMap.values())
.filter(func)
.map((tab) => getTabRef(tab.id))
}
export function closeOtherTabs(tabID: string) {
if (!tabMap.has(tabID)) {
console.warn(
`The tab to close other tabs does not exist (tab id: ${tabID})`
)
return
}
tabOrdering.value = [tabID]
tabMap.forEach((_, id) => {
if (id !== tabID) tabMap.delete(id)
})
currentTabID.value = tabID
}
export function getDirtyTabsCount() {
let count = 0
for (const tab of tabMap.values()) {
if (tab.document.isDirty) count++
}
return count
}

View File

@@ -1,4 +1,7 @@
import { HoppRESTRequest } from "@hoppscotch/data"
import { HoppRESTResponse } from "../types/HoppRESTResponse"
import { HoppTestResult } from "../types/HoppTestResult"
import { RESTOptionTabs } from "~/components/http/RequestOptions.vue"
export type HoppRESTSaveContext =
| {
@@ -55,4 +58,26 @@ export type HoppRESTDocument = {
* This contains where the request is originated from basically.
*/
saveContext?: HoppRESTSaveContext
/**
* The response as it is in the document
* (if any)
*/
response?: HoppRESTResponse | null
/**
* The test results as it is in the document
* (if any)
*/
testResults?: HoppTestResult | null
/**
* Response tab preference for the current tab's document
*/
responseTabPreference?: string
/**
* Options tab preference for the current tab's document
*/
optionTabPreference?: RESTOptionTabs
}

View File

@@ -1,234 +0,0 @@
import { v4 as uuidV4 } from "uuid"
import { isEqual } from "lodash-es"
import { reactive, watch, computed, ref, shallowReadonly } from "vue"
import { HoppRESTDocument, HoppRESTSaveContext } from "./document"
import { refWithControl } from "@vueuse/core"
import { HoppRESTResponse } from "../types/HoppRESTResponse"
import { getDefaultRESTRequest } from "./default"
import { HoppTestResult } from "../types/HoppTestResult"
import { platform } from "~/platform"
import { nextTick } from "vue"
export type HoppRESTTab = {
id: string
document: HoppRESTDocument
response?: HoppRESTResponse | null
testResults?: HoppTestResult | null
}
export type PersistableRESTTabState = {
lastActiveTabID: string
orderedDocs: Array<{
tabID: string
doc: HoppRESTDocument
}>
}
export const currentTabID = refWithControl("test", {
onBeforeChange(newTabID) {
if (!newTabID || !tabMap.has(newTabID)) {
console.warn(
`Tried to set current tab id to an invalid value. (value: ${newTabID})`
)
// Don't allow change
return false
}
},
})
const tabMap = reactive(
new Map<string, HoppRESTTab>([
[
"test",
{
id: "test",
document: {
request: getDefaultRESTRequest(),
isDirty: false,
},
},
],
])
)
const tabOrdering = ref<string[]>(["test"])
watch(
tabOrdering,
(newOrdering) => {
if (!currentTabID.value || !newOrdering.includes(currentTabID.value)) {
currentTabID.value = newOrdering[newOrdering.length - 1] // newOrdering should always be non-empty
}
},
{ deep: true }
)
export const persistableTabState = computed<PersistableRESTTabState>(() => ({
lastActiveTabID: currentTabID.value,
orderedDocs: tabOrdering.value.map((tabID) => {
const tab = tabMap.get(tabID)! // tab ordering is guaranteed to have value for this key
return {
tabID: tab.id,
doc: tab.document,
}
}),
}))
export const currentActiveTab = computed(() => tabMap.get(currentTabID.value)!) // Guaranteed to not be undefined
// TODO: Mark this unknown and do validations
export function loadTabsFromPersistedState(data: PersistableRESTTabState) {
if (data) {
tabMap.clear()
tabOrdering.value = []
for (const doc of data.orderedDocs) {
tabMap.set(doc.tabID, {
id: doc.tabID,
document: doc.doc,
})
tabOrdering.value.push(doc.tabID)
}
currentTabID.value = data.lastActiveTabID
}
}
/**
* Returns all the active Tab IDs in order
*/
export function getActiveTabs() {
return shallowReadonly(
computed(() => tabOrdering.value.map((x) => tabMap.get(x)!))
)
}
export function getTabRef(tabID: string) {
return computed({
get() {
const result = tabMap.get(tabID)
if (result === undefined) throw new Error(`Invalid tab id: ${tabID}`)
return result
},
set(value) {
return tabMap.set(tabID, value)
},
})
}
function generateNewTabID() {
while (true) {
const id = uuidV4()
if (!tabMap.has(id)) return id
}
}
export function updateTab(tabUpdate: HoppRESTTab) {
if (!tabMap.has(tabUpdate.id)) {
console.warn(
`Cannot update tab as tab with that tab id does not exist (id: ${tabUpdate.id})`
)
}
tabMap.set(tabUpdate.id, tabUpdate)
}
export function createNewTab(document: HoppRESTDocument, switchToIt = true) {
const id = generateNewTabID()
const tab: HoppRESTTab = { id, document }
tabMap.set(id, tab)
tabOrdering.value.push(id)
if (switchToIt) {
currentTabID.value = id
}
platform.analytics?.logEvent({
type: "HOPP_REST_NEW_TAB_OPENED",
})
return tab
}
export function updateTabOrdering(fromIndex: number, toIndex: number) {
tabOrdering.value.splice(
toIndex,
0,
tabOrdering.value.splice(fromIndex, 1)[0]
)
}
export function closeTab(tabID: string) {
if (!tabMap.has(tabID)) {
console.warn(`Tried to close a tab which does not exist (tab id: ${tabID})`)
return
}
if (tabOrdering.value.length === 1) {
console.warn(
`Tried to close the only tab open, which is not allowed. (tab id: ${tabID})`
)
return
}
tabOrdering.value.splice(tabOrdering.value.indexOf(tabID), 1)
nextTick(() => {
tabMap.delete(tabID)
})
}
export function closeOtherTabs(tabID: string) {
if (!tabMap.has(tabID)) {
console.warn(
`The tab to close other tabs does not exist (tab id: ${tabID})`
)
return
}
tabOrdering.value = [tabID]
tabMap.forEach((_, id) => {
if (id !== tabID) tabMap.delete(id)
})
currentTabID.value = tabID
}
export function getDirtyTabsCount() {
let count = 0
for (const tab of tabMap.values()) {
if (tab.document.isDirty) count++
}
return count
}
export function getTabRefWithSaveContext(ctx: HoppRESTSaveContext) {
for (const tab of tabMap.values()) {
// For `team-collection` request id can be considered unique
if (ctx && ctx.originLocation === "team-collection") {
if (
tab.document.saveContext?.originLocation === "team-collection" &&
tab.document.saveContext.requestID === ctx.requestID
) {
return getTabRef(tab.id)
}
} else if (isEqual(ctx, tab.document.saveContext)) return getTabRef(tab.id)
}
return null
}
export function getTabsRefTo(func: (tab: HoppRESTTab) => boolean) {
return Array.from(tabMap.values())
.filter(func)
.map((tab) => getTabRef(tab.id))
}

View File

@@ -198,11 +198,11 @@ export const resolvesEnvsInBody = (
}
),
}
} else {
return {
contentType: body.contentType,
body: parseTemplateString(body.body, env.variables),
}
}
return {
contentType: body.contentType,
body: parseTemplateString(body.body ?? "", env.variables),
}
}
@@ -210,9 +210,7 @@ function getFinalBodyFromRequest(
request: HoppRESTRequest,
envVariables: Environment["variables"]
): FormData | string | null {
if (request.body.contentType === null) {
return null
}
if (request.body.contentType === null) return null
if (request.body.contentType === "application/x-www-form-urlencoded") {
const parsedBodyRecord = pipe(
@@ -280,7 +278,10 @@ function getFinalBodyFromRequest(
),
toFormData
)
} else return parseBodyEnvVariables(request.body.body, envVariables)
}
// body can be null if the content-type is not set
return parseBodyEnvVariables(request.body.body ?? "", envVariables)
}
/**