refactor: interceptor error display in graphql response (#3553)
This commit is contained in:
@@ -5,7 +5,7 @@
|
|||||||
// Read more: https://github.com/vuejs/core/pull/3399
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
export {}
|
export {}
|
||||||
|
|
||||||
declare module "vue" {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
AppActionHandler: typeof import('./components/app/ActionHandler.vue')['default']
|
AppActionHandler: typeof import('./components/app/ActionHandler.vue')['default']
|
||||||
AppAnnouncement: typeof import('./components/app/Announcement.vue')['default']
|
AppAnnouncement: typeof import('./components/app/Announcement.vue')['default']
|
||||||
@@ -93,11 +93,13 @@ declare module "vue" {
|
|||||||
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
|
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
|
||||||
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
|
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
|
||||||
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
|
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
|
||||||
|
HoppSmartAutoComplete: typeof import('@hoppscotch/ui')['HoppSmartAutoComplete']
|
||||||
HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox']
|
HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox']
|
||||||
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
|
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
|
||||||
HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand']
|
HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand']
|
||||||
HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip']
|
HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip']
|
||||||
HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput']
|
HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput']
|
||||||
|
HoppSmartIntersection: typeof import('@hoppscotch/ui')['HoppSmartIntersection']
|
||||||
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
|
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
|
||||||
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
|
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
|
||||||
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
||||||
@@ -145,6 +147,7 @@ declare module "vue" {
|
|||||||
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
|
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
|
||||||
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
||||||
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
|
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
|
||||||
|
IconLucideBrush: typeof import('~icons/lucide/brush')['default']
|
||||||
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
|
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
|
||||||
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
|
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
|
||||||
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
|
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
|
||||||
@@ -154,6 +157,7 @@ declare module "vue" {
|
|||||||
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
|
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
|
||||||
IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
|
IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
|
||||||
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
|
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
|
||||||
|
IconLucideRss: typeof import('~icons/lucide/rss')['default']
|
||||||
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
||||||
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
||||||
InterceptorsErrorPlaceholder: typeof import('./components/interceptors/ErrorPlaceholder.vue')['default']
|
InterceptorsErrorPlaceholder: typeof import('./components/interceptors/ErrorPlaceholder.vue')['default']
|
||||||
@@ -202,6 +206,7 @@ declare module "vue" {
|
|||||||
SmartSlideOver: typeof import('./../../hoppscotch-ui/src/components/smart/SlideOver.vue')['default']
|
SmartSlideOver: typeof import('./../../hoppscotch-ui/src/components/smart/SlideOver.vue')['default']
|
||||||
SmartSpinner: typeof import('./../../hoppscotch-ui/src/components/smart/Spinner.vue')['default']
|
SmartSpinner: typeof import('./../../hoppscotch-ui/src/components/smart/Spinner.vue')['default']
|
||||||
SmartTab: typeof import('./../../hoppscotch-ui/src/components/smart/Tab.vue')['default']
|
SmartTab: typeof import('./../../hoppscotch-ui/src/components/smart/Tab.vue')['default']
|
||||||
|
SmartTable: typeof import('./../../hoppscotch-ui/src/components/smart/Table.vue')['default']
|
||||||
SmartTabs: typeof import('./../../hoppscotch-ui/src/components/smart/Tabs.vue')['default']
|
SmartTabs: typeof import('./../../hoppscotch-ui/src/components/smart/Tabs.vue')['default']
|
||||||
SmartToggle: typeof import('./../../hoppscotch-ui/src/components/smart/Toggle.vue')['default']
|
SmartToggle: typeof import('./../../hoppscotch-ui/src/components/smart/Toggle.vue')['default']
|
||||||
SmartTree: typeof import('./../../hoppscotch-ui/src/components/smart/Tree.vue')['default']
|
SmartTree: typeof import('./../../hoppscotch-ui/src/components/smart/Tree.vue')['default']
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ import {
|
|||||||
GQLResponseEvent,
|
GQLResponseEvent,
|
||||||
runGQLOperation,
|
runGQLOperation,
|
||||||
gqlMessageEvent,
|
gqlMessageEvent,
|
||||||
|
connection,
|
||||||
} from "~/helpers/graphql/connection"
|
} from "~/helpers/graphql/connection"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { InterceptorService } from "~/services/interceptor.service"
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
@@ -152,13 +153,7 @@ const runQuery = async (
|
|||||||
toast.success(t("authorization.graphql_headers"))
|
toast.success(t("authorization.graphql_headers"))
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.log(e)
|
|
||||||
// response.value = [`${e}`]
|
|
||||||
completePageProgress()
|
completePageProgress()
|
||||||
toast.error(
|
|
||||||
`${t("error.something_went_wrong")}. ${t("error.check_console_details")}`,
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
platform.analytics?.logEvent({
|
platform.analytics?.logEvent({
|
||||||
@@ -177,7 +172,10 @@ watch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (event?.operationType !== "subscription") {
|
if (
|
||||||
|
event?.type === "response" &&
|
||||||
|
event?.operationType !== "subscription"
|
||||||
|
) {
|
||||||
// response.value = [event]
|
// response.value = [event]
|
||||||
emit("update:response", [event])
|
emit("update:response", [event])
|
||||||
} else {
|
} else {
|
||||||
@@ -192,6 +190,26 @@ watch(
|
|||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => connection,
|
||||||
|
(newVal) => {
|
||||||
|
if (newVal.error && newVal.state === "DISCONNECTED") {
|
||||||
|
const response = [
|
||||||
|
{
|
||||||
|
type: "error",
|
||||||
|
error: {
|
||||||
|
message: newVal.error.message(t),
|
||||||
|
type: newVal.error.type,
|
||||||
|
component: newVal.error.component,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
emit("update:response", response)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
const hideRequestModal = () => {
|
const hideRequestModal = () => {
|
||||||
showSaveRequestModal.value = false
|
showSaveRequestModal.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-1 flex-col overflow-auto whitespace-nowrap">
|
<div class="flex flex-col flex-1 overflow-auto whitespace-nowrap">
|
||||||
<div v-if="response?.length === 1" class="flex flex-1 flex-col">
|
<div
|
||||||
|
v-if="
|
||||||
|
response && response.length === 1 && response[0].type === 'response'
|
||||||
|
"
|
||||||
|
class="flex flex-col flex-1"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="sticky top-0 z-10 flex flex-shrink-0 items-center justify-between overflow-x-auto border-b border-dividerLight bg-primary pl-4"
|
class="sticky top-0 z-10 flex flex-shrink-0 items-center justify-between overflow-x-auto border-b border-dividerLight bg-primary pl-4"
|
||||||
>
|
>
|
||||||
@@ -35,6 +40,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div ref="schemaEditor" class="flex flex-1 flex-col"></div>
|
<div ref="schemaEditor" class="flex flex-1 flex-col"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<component
|
||||||
|
:is="response[0].error.component"
|
||||||
|
v-else-if="
|
||||||
|
response && response[0].type === 'error' && response[0].error.component
|
||||||
|
"
|
||||||
|
class="flex-1"
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
v-else-if="response && response?.length > 1"
|
v-else-if="response && response?.length > 1"
|
||||||
class="flex flex-1 flex-col"
|
class="flex flex-1 flex-col"
|
||||||
@@ -74,8 +86,16 @@ const props = withDefaults(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const responseString = computed(() => {
|
const responseString = computed(() => {
|
||||||
if (props.response?.length === 1) {
|
const response = props.response
|
||||||
return JSON.stringify(JSON.parse(props.response[0].data), null, 2)
|
if (response && response[0].type === "error") {
|
||||||
|
return ""
|
||||||
|
} else if (
|
||||||
|
response &&
|
||||||
|
response.length === 1 &&
|
||||||
|
response[0].type === "response" &&
|
||||||
|
response[0].data
|
||||||
|
) {
|
||||||
|
return JSON.stringify(JSON.parse(response[0].data), null, 2)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -49,7 +49,11 @@
|
|||||||
v-for="(entry, index) in log"
|
v-for="(entry, index) in log"
|
||||||
:key="`entry-${index}`"
|
:key="`entry-${index}`"
|
||||||
:is-open="log.length - 1 === index"
|
:is-open="log.length - 1 === index"
|
||||||
:entry="{ ts: entry.time, source: 'info', payload: entry.data }"
|
:entry="{
|
||||||
|
ts: entry.type === 'response' ? entry.time : undefined,
|
||||||
|
source: 'info',
|
||||||
|
payload: entry.type === 'response' ? entry.data : '',
|
||||||
|
}"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ import {
|
|||||||
getIntrospectionQuery,
|
getIntrospectionQuery,
|
||||||
printSchema,
|
printSchema,
|
||||||
} from "graphql"
|
} from "graphql"
|
||||||
import { computed, reactive, ref } from "vue"
|
import { Component, computed, reactive, ref } from "vue"
|
||||||
import { getService } from "~/modules/dioc"
|
import { getService } from "~/modules/dioc"
|
||||||
|
import { getI18n } from "~/modules/i18n"
|
||||||
|
|
||||||
import { addGraphqlHistoryEntry, makeGQLHistoryEntry } from "~/newstore/history"
|
import { addGraphqlHistoryEntry, makeGQLHistoryEntry } from "~/newstore/history"
|
||||||
|
|
||||||
@@ -32,13 +33,23 @@ type RunQueryOptions = {
|
|||||||
operationType: OperationType
|
operationType: OperationType
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GQLResponseEvent = {
|
export type GQLResponseEvent =
|
||||||
time: number
|
| {
|
||||||
operationName: string | undefined
|
type: "response"
|
||||||
operationType: OperationType
|
time: number
|
||||||
data: string
|
operationName: string | undefined
|
||||||
rawQuery?: RunQueryOptions
|
operationType: OperationType
|
||||||
}
|
data: string
|
||||||
|
rawQuery?: RunQueryOptions
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "error"
|
||||||
|
error: {
|
||||||
|
type: string
|
||||||
|
message: string
|
||||||
|
component?: Component
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type ConnectionState = "CONNECTING" | "CONNECTED" | "DISCONNECTED"
|
export type ConnectionState = "CONNECTING" | "CONNECTED" | "DISCONNECTED"
|
||||||
export type SubscriptionState = "SUBSCRIBING" | "SUBSCRIBED" | "UNSUBSCRIBED"
|
export type SubscriptionState = "SUBSCRIBING" | "SUBSCRIBED" | "UNSUBSCRIBED"
|
||||||
@@ -61,6 +72,11 @@ type Connection = {
|
|||||||
subscriptionState: Map<string, SubscriptionState>
|
subscriptionState: Map<string, SubscriptionState>
|
||||||
socket: WebSocket | undefined
|
socket: WebSocket | undefined
|
||||||
schema: GraphQLSchema | null
|
schema: GraphQLSchema | null
|
||||||
|
error?: {
|
||||||
|
type: string
|
||||||
|
message: (t: ReturnType<typeof getI18n>) => string
|
||||||
|
component?: Component
|
||||||
|
} | null
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabs = getService(GQLTabService)
|
const tabs = getService(GQLTabService)
|
||||||
@@ -71,6 +87,7 @@ export const connection = reactive<Connection>({
|
|||||||
subscriptionState: new Map<string, SubscriptionState>(),
|
subscriptionState: new Map<string, SubscriptionState>(),
|
||||||
socket: undefined,
|
socket: undefined,
|
||||||
schema: null,
|
schema: null,
|
||||||
|
error: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const schema = computed(() => connection.schema)
|
export const schema = computed(() => connection.schema)
|
||||||
@@ -202,7 +219,19 @@ const getSchema = async (url: string, headers: GQLHeader[]) => {
|
|||||||
const res = await interceptorService.runRequest(reqOptions).response
|
const res = await interceptorService.runRequest(reqOptions).response
|
||||||
|
|
||||||
if (E.isLeft(res)) {
|
if (E.isLeft(res)) {
|
||||||
console.error(res.left)
|
if (
|
||||||
|
res.left !== "cancellation" &&
|
||||||
|
res.left.error === "NO_PW_EXT_HOOK" &&
|
||||||
|
res.left.humanMessage
|
||||||
|
) {
|
||||||
|
connection.error = {
|
||||||
|
type: res.left.error,
|
||||||
|
message: (t: ReturnType<typeof getI18n>) =>
|
||||||
|
res.left.humanMessage.description(t),
|
||||||
|
component: res.left.component,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error(res.left.toString())
|
throw new Error(res.left.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,6 +247,7 @@ const getSchema = async (url: string, headers: GQLHeader[]) => {
|
|||||||
const schema = buildClientSchema(introspectResponse.data)
|
const schema = buildClientSchema(introspectResponse.data)
|
||||||
|
|
||||||
connection.schema = schema
|
connection.schema = schema
|
||||||
|
connection.error = null
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
disconnect()
|
disconnect()
|
||||||
@@ -280,7 +310,18 @@ export const runGQLOperation = async (options: RunQueryOptions) => {
|
|||||||
const result = await interceptorService.runRequest(reqOptions).response
|
const result = await interceptorService.runRequest(reqOptions).response
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
if (E.isLeft(result)) {
|
||||||
console.error(result.left)
|
if (
|
||||||
|
result.left !== "cancellation" &&
|
||||||
|
result.left.error === "NO_PW_EXT_HOOK" &&
|
||||||
|
result.left.humanMessage
|
||||||
|
) {
|
||||||
|
connection.error = {
|
||||||
|
type: result.left.error,
|
||||||
|
message: (t: ReturnType<typeof getI18n>) =>
|
||||||
|
result.left.humanMessage.description(t),
|
||||||
|
component: result.left.component,
|
||||||
|
}
|
||||||
|
}
|
||||||
throw new Error(result.left.toString())
|
throw new Error(result.left.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,6 +333,7 @@ export const runGQLOperation = async (options: RunQueryOptions) => {
|
|||||||
.replace(/\0+$/, "")
|
.replace(/\0+$/, "")
|
||||||
|
|
||||||
gqlMessageEvent.value = {
|
gqlMessageEvent.value = {
|
||||||
|
type: "response",
|
||||||
time: Date.now(),
|
time: Date.now(),
|
||||||
operationName: operationName ?? "query",
|
operationName: operationName ?? "query",
|
||||||
data: responseText,
|
data: responseText,
|
||||||
@@ -299,6 +341,10 @@ export const runGQLOperation = async (options: RunQueryOptions) => {
|
|||||||
operationType,
|
operationType,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (connection.state !== "CONNECTED") {
|
||||||
|
connection.state = "CONNECTED"
|
||||||
|
}
|
||||||
|
|
||||||
addQueryToHistory(options, responseText)
|
addQueryToHistory(options, responseText)
|
||||||
|
|
||||||
return responseText
|
return responseText
|
||||||
@@ -352,6 +398,7 @@ export const runSubscription = (
|
|||||||
}
|
}
|
||||||
case GQL.DATA: {
|
case GQL.DATA: {
|
||||||
gqlMessageEvent.value = {
|
gqlMessageEvent.value = {
|
||||||
|
type: "response",
|
||||||
time: Date.now(),
|
time: Date.now(),
|
||||||
operationName,
|
operationName,
|
||||||
data: JSON.stringify(data.payload),
|
data: JSON.stringify(data.payload),
|
||||||
|
|||||||
@@ -209,7 +209,6 @@ export class ExtensionInterceptorService
|
|||||||
req: AxiosRequestConfig
|
req: AxiosRequestConfig
|
||||||
): RequestRunResult["response"] {
|
): RequestRunResult["response"] {
|
||||||
const extensionHook = window.__POSTWOMAN_EXTENSION_HOOK__
|
const extensionHook = window.__POSTWOMAN_EXTENSION_HOOK__
|
||||||
|
|
||||||
if (!extensionHook) {
|
if (!extensionHook) {
|
||||||
return E.left(<InterceptorError>{
|
return E.left(<InterceptorError>{
|
||||||
// TODO: i18n this
|
// TODO: i18n this
|
||||||
@@ -230,6 +229,7 @@ export class ExtensionInterceptorService
|
|||||||
|
|
||||||
return E.right(result)
|
return E.right(result)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
// TODO: improve type checking
|
// TODO: improve type checking
|
||||||
if ((e as any).response) {
|
if ((e as any).response) {
|
||||||
return E.right((e as any).response)
|
return E.right((e as any).response)
|
||||||
|
|||||||
Reference in New Issue
Block a user