Skip to content

Commit

Permalink
hotfix: graphql connection error handler (#4523)
Browse files Browse the repository at this point in the history
Co-authored-by: jamesgeorge007 <[email protected]>
  • Loading branch information
anwarulislam and jamesgeorge007 authored Nov 12, 2024
1 parent 3de3209 commit e1fbe68
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 15 deletions.
1 change: 1 addition & 0 deletions packages/hoppscotch-common/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@
},
"graphql": {
"connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?",
"connection_error_http": "Failed to fetch GraphQL Schema due to network error.",
"connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is",
"connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is",
"mutations": "Mutations",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,10 @@ watch(
watch(
() => connection,
(newVal) => {
if (newVal.error && newVal.state === "DISCONNECTED") {
if (
newVal.error &&
(newVal.state === "DISCONNECTED" || newVal.state === "ERROR")
) {
const response = [
{
type: "error",
Expand Down
60 changes: 47 additions & 13 deletions packages/hoppscotch-common/src/helpers/graphql/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
printSchema,
} from "graphql"
import { Component, computed, reactive, ref } from "vue"
import { useToast } from "~/composables/toast"
import { getService } from "~/modules/dioc"
import { getI18n } from "~/modules/i18n"

Expand Down Expand Up @@ -52,7 +53,11 @@ export type GQLResponseEvent =
}
}

export type ConnectionState = "CONNECTING" | "CONNECTED" | "DISCONNECTED"
export type ConnectionState =
| "CONNECTING"
| "CONNECTED"
| "DISCONNECTED"
| "ERROR"
export type SubscriptionState = "SUBSCRIBING" | "SUBSCRIBED" | "UNSUBSCRIBED"

const GQL = {
Expand Down Expand Up @@ -100,10 +105,7 @@ export const gqlMessageEvent = ref<GQLResponseEvent | "reset">()

export const schemaString = computed(() => {
if (!connection.schema) return ""

return printSchema(connection.schema, {
commentDescriptions: true,
})
return printSchema(connection.schema)
})

export const queryFields = computed(() => {
Expand Down Expand Up @@ -159,21 +161,40 @@ export const graphqlTypes = computed(() => {

let timeoutSubscription: any

export const connect = async (url: string, headers: GQLHeader[]) => {
export const connect = async (
url: string,
headers: GQLHeader[],
isRunGQLOperation = false
) => {
if (connection.state === "CONNECTED") {
throw new Error(
"A connection is already running. Close it before starting another."
)
}

// Polling
connection.state = "CONNECTED"
const toast = useToast()
const t = getI18n()

connection.state = "CONNECTING"

const poll = async () => {
await getSchema(url, headers)
timeoutSubscription = setTimeout(() => {
poll()
}, GQL_SCHEMA_POLL_INTERVAL)
try {
await getSchema(url, headers)
// polling for schema
if (connection.state !== "CONNECTED") connection.state = "CONNECTED"
timeoutSubscription = setTimeout(() => {
poll()
}, GQL_SCHEMA_POLL_INTERVAL)
} catch (error) {
connection.state = "ERROR"

// Show an error toast if the introspection attempt failed and not while sending a request
if (!isRunGQLOperation) {
toast.error(t("graphql.connection_error_http"))
}

console.error(error)
}
}

await poll()
Expand Down Expand Up @@ -221,6 +242,8 @@ const getSchema = async (url: string, headers: GQLHeader[]) => {
const res = await interceptorService.runRequest(reqOptions).response

if (E.isLeft(res)) {
connection.state = "ERROR"

if (
res.left !== "cancellation" &&
res.left.error === "NO_PW_EXT_HOOK" &&
Expand All @@ -237,6 +260,17 @@ const getSchema = async (url: string, headers: GQLHeader[]) => {
throw new Error(res.left.toString())
}

if (res.right.status !== 200) {
connection.state = "ERROR"
connection.error = {
type: "HTTP_ERROR",
message: (t: ReturnType<typeof getI18n>) =>
t("graphql.connection_error_http"),
component: undefined,
}
throw new Error("Failed to fetch schema. Status: " + res.right.status)
}

const data = res.right

// HACK : Temporary trailing null character issue from the extension fix
Expand All @@ -258,7 +292,7 @@ const getSchema = async (url: string, headers: GQLHeader[]) => {

export const runGQLOperation = async (options: RunQueryOptions) => {
if (connection.state !== "CONNECTED") {
await connect(options.url, options.headers)
await connect(options.url, options.headers, true)
}

const { url, headers, query, variables, auth, operationName, operationType } =
Expand Down
4 changes: 3 additions & 1 deletion packages/hoppscotch-common/src/pages/graphql.vue
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@
import { usePageHead } from "@composables/head"
import { useI18n } from "@composables/i18n"
import { useService } from "dioc/vue"
import { computed, onBeforeUnmount, ref } from "vue"
import { computed, onBeforeUnmount, ref, watch } from "vue"
import { useToast } from "~/composables/toast"
import { defineActionHandler } from "~/helpers/actions"
import { connection, disconnect } from "~/helpers/graphql/connection"
import { getDefaultGQLRequest } from "~/helpers/graphql/default"
Expand All @@ -97,6 +98,7 @@ import { HoppTab } from "~/services/tab"
import { GQLTabService } from "~/services/tab/graphql"
const t = useI18n()
const toast = useToast()
const tabs = useService(GQLTabService)
const currentTabID = computed(() => tabs.currentTabID.value)
Expand Down

0 comments on commit e1fbe68

Please sign in to comment.