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