mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-19 15:31:19 +02:00
Retry button on introspection errors
This commit is contained in:
@@ -8,7 +8,7 @@
|
|||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "Yaak",
|
"productName": "Yaak",
|
||||||
"version": "2023.0.17"
|
"version": "2023.0.18"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"windows": [],
|
"windows": [],
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type { HttpRequest } from '../lib/models';
|
|||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import type { EditorProps } from './core/Editor';
|
import type { EditorProps } from './core/Editor';
|
||||||
import { Editor, formatGraphQL } from './core/Editor';
|
import { Editor, formatGraphQL } from './core/Editor';
|
||||||
|
import { FormattedError } from './core/FormattedError';
|
||||||
import { Separator } from './core/Separator';
|
import { Separator } from './core/Separator';
|
||||||
import { useDialog } from './DialogContext';
|
import { useDialog } from './DialogContext';
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ interface GraphQLBody {
|
|||||||
|
|
||||||
export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEditorProps }: Props) {
|
export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEditorProps }: Props) {
|
||||||
const editorViewRef = useRef<EditorView>(null);
|
const editorViewRef = useRef<EditorView>(null);
|
||||||
const { schema, isLoading, error } = useIntrospectGraphQL(baseRequest);
|
const { schema, isLoading, error, refetch } = useIntrospectGraphQL(baseRequest);
|
||||||
const { query, variables } = useMemo<GraphQLBody>(() => {
|
const { query, variables } = useMemo<GraphQLBody>(() => {
|
||||||
if (defaultValue === undefined) {
|
if (defaultValue === undefined) {
|
||||||
return { query: '', variables: {} };
|
return { query: '', variables: {} };
|
||||||
@@ -89,7 +90,25 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
|
|||||||
dialog.show({
|
dialog.show({
|
||||||
title: 'Introspection Failed',
|
title: 'Introspection Failed',
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
render: () => <div className="whitespace-pre-wrap">{error}</div>,
|
id: 'introspection-failed',
|
||||||
|
render: () => (
|
||||||
|
<div className="whitespace-pre-wrap">
|
||||||
|
<FormattedError>{error ?? 'unknown'}</FormattedError>
|
||||||
|
<div className="w-full mt-3">
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
dialog.hide('introspection-failed');
|
||||||
|
refetch();
|
||||||
|
}}
|
||||||
|
className="ml-auto"
|
||||||
|
color="secondary"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
Try Again
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useRouteError } from 'react-router-dom';
|
import { useRouteError } from 'react-router-dom';
|
||||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
|
import { FormattedError } from './core/FormattedError';
|
||||||
import { Heading } from './core/Heading';
|
import { Heading } from './core/Heading';
|
||||||
import { VStack } from './core/Stacks';
|
import { VStack } from './core/Stacks';
|
||||||
|
|
||||||
@@ -14,9 +15,7 @@ export default function RouteError() {
|
|||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full">
|
||||||
<VStack space={5} className="max-w-[30rem] !h-auto">
|
<VStack space={5} className="max-w-[30rem] !h-auto">
|
||||||
<Heading>Route Error 🔥</Heading>
|
<Heading>Route Error 🔥</Heading>
|
||||||
<pre className="text-sm select-auto cursor-text bg-gray-100 p-3 rounded whitespace-normal">
|
<FormattedError>{message}</FormattedError>
|
||||||
{message}
|
|
||||||
</pre>
|
|
||||||
<VStack space={2}>
|
<VStack space={2}>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|||||||
11
src-web/components/core/FormattedError.tsx
Normal file
11
src-web/components/core/FormattedError.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
interface Props {
|
||||||
|
children: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FormattedError({ children }: Props) {
|
||||||
|
return (
|
||||||
|
<pre className="text-sm select-auto cursor-text bg-gray-100 p-3 rounded whitespace-normal border border-red-500 border-dashed">
|
||||||
|
{children}
|
||||||
|
</pre>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { IntrospectionQuery } from 'graphql';
|
import type { IntrospectionQuery } from 'graphql';
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useLocalStorage } from 'react-use';
|
import { useLocalStorage } from 'react-use';
|
||||||
import { buildClientSchema, getIntrospectionQuery } from '../components/core/Editor';
|
import { buildClientSchema, getIntrospectionQuery } from '../components/core/Editor';
|
||||||
import { minPromiseMillis } from '../lib/minPromiseMillis';
|
import { minPromiseMillis } from '../lib/minPromiseMillis';
|
||||||
@@ -17,6 +17,7 @@ export function useIntrospectGraphQL(baseRequest: HttpRequest) {
|
|||||||
// Debounce the request because it can change rapidly and we don't
|
// Debounce the request because it can change rapidly and we don't
|
||||||
// want to send so too many requests.
|
// want to send so too many requests.
|
||||||
const request = useDebouncedValue(baseRequest);
|
const request = useDebouncedValue(baseRequest);
|
||||||
|
const [refetchKey, setRefetchKey] = useState<number>(0);
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
const [error, setError] = useState<string>();
|
const [error, setError] = useState<string>();
|
||||||
const [introspection, setIntrospection] = useLocalStorage<IntrospectionQuery>(
|
const [introspection, setIntrospection] = useLocalStorage<IntrospectionQuery>(
|
||||||
@@ -63,12 +64,16 @@ export function useIntrospectGraphQL(baseRequest: HttpRequest) {
|
|||||||
runIntrospection(); // Run immediately
|
runIntrospection(); // Run immediately
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [request.id, request.method]);
|
}, [request.id, request.url, request.method, refetchKey]);
|
||||||
|
|
||||||
|
const refetch = useCallback(() => {
|
||||||
|
setRefetchKey((k) => k + 1);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const schema = useMemo(
|
const schema = useMemo(
|
||||||
() => (introspection ? buildClientSchema(introspection) : undefined),
|
() => (introspection ? buildClientSchema(introspection) : undefined),
|
||||||
[introspection],
|
[introspection],
|
||||||
);
|
);
|
||||||
|
|
||||||
return { schema, isLoading, error };
|
return { schema, isLoading, error, refetch };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { HttpResponse } from './models';
|
|||||||
|
|
||||||
export async function getResponseBodyText(response: HttpResponse): Promise<string | null> {
|
export async function getResponseBodyText(response: HttpResponse): Promise<string | null> {
|
||||||
if (response.body) {
|
if (response.body) {
|
||||||
const uint8Array = Uint8Array.of(...response.body);
|
const uint8Array = Uint8Array.from(response.body);
|
||||||
return new TextDecoder().decode(uint8Array);
|
return new TextDecoder().decode(uint8Array);
|
||||||
}
|
}
|
||||||
if (response.bodyPath) {
|
if (response.bodyPath) {
|
||||||
@@ -14,7 +14,7 @@ export async function getResponseBodyText(response: HttpResponse): Promise<strin
|
|||||||
|
|
||||||
export async function getResponseBodyBlob(response: HttpResponse): Promise<Uint8Array | null> {
|
export async function getResponseBodyBlob(response: HttpResponse): Promise<Uint8Array | null> {
|
||||||
if (response.body) {
|
if (response.body) {
|
||||||
return Uint8Array.of(...response.body);
|
return Uint8Array.from(response.body);
|
||||||
}
|
}
|
||||||
if (response.bodyPath) {
|
if (response.bodyPath) {
|
||||||
return readBinaryFile(response.bodyPath);
|
return readBinaryFile(response.bodyPath);
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ const darkTheme: AppTheme = {
|
|||||||
appearance: 'dark',
|
appearance: 'dark',
|
||||||
layers: {
|
layers: {
|
||||||
root: {
|
root: {
|
||||||
blackPoint: 0.09,
|
blackPoint: 0.2,
|
||||||
colors: {
|
colors: {
|
||||||
gray: '#9b8ebe',
|
gray: '#6b5b98',
|
||||||
red: '#ff417b',
|
red: '#ff417b',
|
||||||
orange: '#fd9014',
|
orange: '#fd9014',
|
||||||
yellow: '#e8d13f',
|
yellow: '#e8d13f',
|
||||||
|
|||||||
@@ -29,7 +29,21 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
"mono": ["JetBrains Mono", "Menlo", "monospace"],
|
"mono": ["JetBrains Mono", "Menlo", "monospace"],
|
||||||
"sans": ["Inter", "sans-serif"]
|
"sans": [
|
||||||
|
"Inter",
|
||||||
|
"-apple-system",
|
||||||
|
"BlinkMacSystemFont",
|
||||||
|
"Segoe UI",
|
||||||
|
"Roboto",
|
||||||
|
"Oxygen-Sans",
|
||||||
|
"Ubuntu",
|
||||||
|
"Cantarell",
|
||||||
|
"Helvetica Neue",
|
||||||
|
"sans-serif",
|
||||||
|
"Apple Color Emoji",
|
||||||
|
"Segoe UI Emoji",
|
||||||
|
"Segoe UI Symbol",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
fontSize: {
|
fontSize: {
|
||||||
'3xs': "0.6rem",
|
'3xs': "0.6rem",
|
||||||
|
|||||||
Reference in New Issue
Block a user