Skip to content

Commit 143a2f0

Browse files
committed
Merge branch 'main' of github.com:graphql-hive/console into feat/lab-query-plan
2 parents bc6a84f + 90d4b4b commit 143a2f0

13 files changed

Lines changed: 389 additions & 423 deletions

File tree

docker/configs/otel-collector/extension-hiveauth/go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,11 @@ require (
4646
go.opentelemetry.io/otel/trace v1.40.0 // indirect
4747
go.uber.org/multierr v1.11.0 // indirect
4848
go.yaml.in/yaml/v3 v3.0.4 // indirect
49+
golang.org/x/net v0.51.0 // indirect
4950
golang.org/x/sys v0.41.0 // indirect
50-
google.golang.org/grpc v1.79.1 // indirect
51+
golang.org/x/text v0.34.0 // indirect
52+
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
53+
google.golang.org/grpc v1.79.3 // indirect
5154
google.golang.org/protobuf v1.36.11 // indirect
5255
gopkg.in/yaml.v3 v3.0.1 // indirect
5356
)

docker/configs/otel-collector/extension-hiveauth/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:
113113
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
114114
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
115115
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
116+
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
117+
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
116118
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
117119
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
118120
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@
154154
"lodash-es@4.x.x": "^4.17.23",
155155
"lodash@4.x.x": "^4.17.23",
156156
"seroval@<1.4.1": "^1.4.1",
157-
"fast-xml-parser@<5.5.6": "^5.5.6",
157+
"fast-xml-parser@<5.5.7": "^5.5.7",
158158
"minimatch@10.x.x": "^10.2.2",
159159
"amqplib": "^0.8.0",
160160
"minimatch@9.x.x": "^9.0.6",

packages/libraries/laboratory/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
},
4040
"dependencies": {
4141
"@base-ui/react": "^1.1.0",
42-
"dagrejs": "^0.2.1",
4342
"radix-ui": "^1.4.3",
4443
"uuid": "^13.0.0"
4544
},

packages/libraries/laboratory/src/components/flow.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export const Flow = (props: { nodes: FlowNode[]; graph?: Record<string, any> })
9090
ranksep: 32,
9191
marginx: 32,
9292
marginy: 32,
93-
// graph: 'tight-tree',
93+
graph: 'tight-tree',
9494
})
9595
.setDefaultEdgeLabel(() => ({}));
9696

@@ -194,6 +194,7 @@ export const Flow = (props: { nodes: FlowNode[]; graph?: Record<string, any> })
194194
return (
195195
<div className={cn('bg-background size-full')}>
196196
<div className={cn('relative size-full overflow-auto')}>
197+
<div className="bg-size-[16px_16px] absolute inset-0 h-full w-full bg-[radial-gradient(hsl(var(--border))_1px,transparent_1px)] opacity-50" />
197198
<svg
198199
className="absolute left-0 top-0"
199200
style={{ width: graphSize.width, height: graphSize.height }}
@@ -254,11 +255,11 @@ export const Flow = (props: { nodes: FlowNode[]; graph?: Record<string, any> })
254255
}
255256
}}
256257
className={cn(
257-
'bg-card absolute flex w-64 flex-col justify-start gap-2 rounded-lg border p-2 text-sm shadow-sm transition-all',
258+
'bg-card absolute z-20 flex w-64 flex-col justify-start gap-2 rounded-lg border p-2 text-sm shadow-sm transition-all',
258259
{
259260
'border-primary shadow-primary/5 shadow-xl':
260261
(isHovered || isFollowingHoveredNode) && !node.isCluster,
261-
'pointer-events-none -mt-[10px] w-auto rounded-2xl border-dashed bg-transparent':
262+
'bg-card/50 pointer-events-none z-10 -mt-[10px] w-auto rounded-2xl border-dashed':
262263
node.isCluster,
263264
},
264265
)}

packages/libraries/laboratory/src/components/laboratory/laboratory.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ const LaboratoryContent = () => {
344344
</Tooltip>
345345
<div
346346
className={cn(
347-
'relative z-10 mt-auto flex aspect-square h-12 w-full items-center justify-center border-l-2 border-transparent',
347+
'z-100 relative mt-auto flex aspect-square h-12 w-full items-center justify-center border-l-2 border-transparent',
348348
{
349349
'border-primary': activePanel === 'settings',
350350
},

packages/libraries/laboratory/src/components/laboratory/operation.tsx

Lines changed: 27 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,16 @@ import {
1212
PanelLeftCloseIcon,
1313
PanelLeftOpenIcon,
1414
PlayIcon,
15-
PlusCircleIcon,
1615
PowerIcon,
1716
PowerOffIcon,
1817
SquarePenIcon,
19-
TrashIcon,
2018
} from 'lucide-react';
2119
import { compressToEncodedURIComponent } from 'lz-string';
2220
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
2321
import { toast } from 'sonner';
2422
import { z } from 'zod';
25-
import { Input } from '@/components/ui/input';
2623
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
24+
import { QueryPlanSchema } from '@/lib/query-plan/schema';
2725
import { DropdownMenuTrigger } from '@radix-ui/react-dropdown-menu';
2826
import { useForm } from '@tanstack/react-form';
2927
import type {
@@ -32,18 +30,10 @@ import type {
3230
LaboratoryHistorySubscription,
3331
} from '../../lib/history';
3432
import type { LaboratoryOperation } from '../../lib/operations';
35-
import { QueryPlanTree, renderQueryPlan } from '../../lib/query-plan';
33+
import { QueryPlanTree, renderQueryPlan } from '../../lib/query-plan/utils';
3634
import { cn } from '../../lib/utils';
3735
import { Badge } from '../ui/badge';
3836
import { Button } from '../ui/button';
39-
import {
40-
Combobox,
41-
ComboboxContent,
42-
ComboboxEmpty,
43-
ComboboxInput,
44-
ComboboxItem,
45-
ComboboxList,
46-
} from '../ui/combobox';
4737
import {
4838
Dialog,
4939
DialogClose,
@@ -93,134 +83,13 @@ const Variables = (props: { operation?: LaboratoryOperation | null; isReadOnly?:
9383
);
9484
};
9585

96-
const HEADERS_SUGGESTIONS = [
97-
'Authorization',
98-
'Content-Type',
99-
'Accept',
100-
'User-Agent',
101-
'X-Requested-With',
102-
'X-CSRF-Token',
103-
];
104-
10586
const Headers = (props: { operation?: LaboratoryOperation | null; isReadOnly?: boolean }) => {
10687
const { activeOperation, updateActiveOperation } = useLaboratory();
10788

10889
const operation = useMemo(() => {
10990
return props.operation ?? activeOperation ?? null;
11091
}, [props.operation, activeOperation]);
11192

112-
// const [headers, setHeaders] = useState<[string, string][]>(() => {
113-
// return Object.entries(JSON.parse(operation?.headers ?? '{}'));
114-
// });
115-
116-
// const updateHeaderKey = useCallback((index: number, newKey: string, value: string) => {
117-
// setHeaders(prev => {
118-
// const newHeaders = [...prev];
119-
// newHeaders[index] = [newKey, value];
120-
// return newHeaders;
121-
// });
122-
// }, []);
123-
124-
// const updateHeaderValue = useCallback((index: number, value: string) => {
125-
// setHeaders(prev => {
126-
// const newHeaders = [...prev];
127-
// newHeaders[index] = [newHeaders[index][0], value];
128-
// return newHeaders;
129-
// });
130-
// }, []);
131-
132-
// const deleteHeader = useCallback((index: number) => {
133-
// setHeaders(prev => {
134-
// const newHeaders = [...prev];
135-
// newHeaders.splice(index, 1);
136-
// return newHeaders;
137-
// });
138-
// }, []);
139-
140-
// const addHeader = useCallback(() => {
141-
// setHeaders(prev => [...prev, ['', '']]);
142-
// }, []);
143-
144-
// useEffect(() => {
145-
// const result = JSON.stringify(Object.fromEntries(headers.filter(([key]) => !!key)));
146-
147-
// if (result === operation?.headers) {
148-
// return;
149-
// }
150-
151-
// updateActiveOperation({
152-
// headers: JSON.stringify(Object.fromEntries(headers)),
153-
// });
154-
// }, [headers, updateActiveOperation, operation?.headers]);
155-
156-
// console.log(headers.some(header2 => header2[0].toLowerCase() === 'Authorization'.toLowerCase()));
157-
158-
// return (
159-
// <div className="grid size-full grid-rows-[1fr_auto]">
160-
// <div className="grid gap-2 overflow-y-auto p-3">
161-
// {headers.map((header, index, arr) => (
162-
// <div key={header[0]} className="grid grid-cols-[1fr_1fr_auto] gap-2">
163-
// <Combobox
164-
// items={HEADERS_SUGGESTIONS}
165-
// value={header[0]}
166-
// onValueChange={newKey => {
167-
// updateHeaderKey(index, newKey ?? '', header[1]);
168-
// }}
169-
// >
170-
// <ComboboxInput
171-
// placeholder="Header key"
172-
// aria-invalid={arr.some(
173-
// header2 =>
174-
// header2 !== header && header2[0].toLowerCase() === header[0].toLowerCase(),
175-
// )}
176-
// />
177-
// <ComboboxContent className="w-full">
178-
// <ComboboxList>
179-
// {HEADERS_SUGGESTIONS.map(item => (
180-
// <ComboboxItem key={item} value={item}>
181-
// {item}
182-
// </ComboboxItem>
183-
// ))}
184-
// </ComboboxList>
185-
// </ComboboxContent>
186-
// </Combobox>
187-
// <Input
188-
// defaultValue={`${header[1]}`}
189-
// placeholder="Value"
190-
// onChange={e => {
191-
// updateHeaderValue(index, e.target.value);
192-
// }}
193-
// />
194-
// <Button
195-
// variant="destructive"
196-
// size="icon"
197-
// className="size-9 rounded-sm"
198-
// disabled={index === 0}
199-
// onClick={() => {
200-
// deleteHeader(index);
201-
// }}
202-
// >
203-
// <TrashIcon className="size-4" />
204-
// </Button>
205-
// </div>
206-
// ))}
207-
// </div>
208-
// <div className="border-t p-3">
209-
// <Button
210-
// variant="link"
211-
// size="sm"
212-
// className="w-full"
213-
// onClick={() => {
214-
// addHeader();
215-
// }}
216-
// >
217-
// <PlusCircleIcon className="size-4" />
218-
// Add header
219-
// </Button>
220-
// </div>
221-
// </div>
222-
// );
223-
22493
return (
22594
<Editor
22695
uri={monaco.Uri.file('headers.json')}
@@ -320,6 +189,18 @@ export const ResponsePreflight = ({ historyItem }: { historyItem?: LaboratoryHis
320189
export const ResponseQueryPlan = ({ historyItem }: { historyItem?: LaboratoryHistory | null }) => {
321190
const [mode, setMode] = useState<'text' | 'visual'>('text');
322191

192+
const queryPlan = useMemo(() => {
193+
const queryPlan =
194+
JSON.parse((historyItem as LaboratoryHistoryRequest)?.response ?? '{}').extensions
195+
?.queryPlan ?? {};
196+
197+
if (!queryPlan) {
198+
return null;
199+
}
200+
201+
return QueryPlanSchema.safeParse(queryPlan).success ? queryPlan : null;
202+
}, [historyItem]);
203+
323204
return (
324205
<div className="relative size-full">
325206
<ToggleGroup
@@ -339,18 +220,10 @@ export const ResponseQueryPlan = ({ historyItem }: { historyItem?: LaboratoryHis
339220
</ToggleGroupItem>
340221
</ToggleGroup>
341222
{mode === 'visual' ? (
342-
<QueryPlanTree
343-
plan={
344-
JSON.parse((historyItem as LaboratoryHistoryRequest)?.response ?? '{}').extensions
345-
?.queryPlan ?? {}
346-
}
347-
/>
223+
<QueryPlanTree plan={queryPlan} />
348224
) : (
349225
<Editor
350-
value={renderQueryPlan(
351-
JSON.parse((historyItem as LaboratoryHistoryRequest)?.response ?? '{}').extensions
352-
?.queryPlan ?? {},
353-
)}
226+
value={renderQueryPlan(queryPlan)}
354227
defaultLanguage="graphql"
355228
theme="hive-laboratory"
356229
options={{ readOnly: true }}
@@ -444,12 +317,20 @@ export const Response = ({ historyItem }: { historyItem?: LaboratoryHistoryReque
444317
);
445318
}, [historyItem]);
446319

447-
const hasQueryPlan = useMemo(() => {
320+
const hasValidQueryPlan = useMemo(() => {
448321
if (!historyItem) {
449322
return false;
450323
}
451324

452-
return !!JSON.parse(historyItem.response).extensions?.queryPlan;
325+
const queryPlan = JSON.parse(historyItem.response).extensions?.queryPlan;
326+
327+
if (!queryPlan) {
328+
return false;
329+
}
330+
331+
console.log(QueryPlanSchema.safeParse(queryPlan).error?.message);
332+
333+
return QueryPlanSchema.safeParse(queryPlan).success;
453334
}, [historyItem?.response]);
454335

455336
return (
@@ -492,7 +373,7 @@ export const Response = ({ historyItem }: { historyItem?: LaboratoryHistoryReque
492373
<TabsTrigger value="response" className="grow-0 rounded-sm">
493374
Response
494375
</TabsTrigger>
495-
{hasQueryPlan && (
376+
{hasValidQueryPlan && (
496377
<TabsTrigger value="query-plan" className="grow-0 rounded-sm">
497378
Query Plan
498379
</TabsTrigger>

packages/libraries/laboratory/src/components/ui/combobox.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ function ComboboxChip({
241241
);
242242
}
243243

244-
function ComboboxChipsInput({ className, children, ...props }: ComboboxPrimitive.Input.Props) {
244+
function ComboboxChipsInput({ className, ...props }: ComboboxPrimitive.Input.Props) {
245245
return (
246246
<ComboboxPrimitive.Input
247247
data-slot="combobox-chip-input"

packages/libraries/laboratory/src/components/ui/dropdown-menu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ function DropdownMenuItem({
6262
data-inset={inset}
6363
data-variant={variant}
6464
className={cn(
65-
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive! [&_svg:not([class*='text-'])]:text-muted-foreground outline-hidden data-disabled:pointer-events-none data-inset:pl-8 data-disabled:opacity-50 relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
65+
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive! [&_svg:not([class*='text-'])]:text-muted-foreground outline-hidden data-disabled:pointer-events-none data-inset:pl-8 data-disabled:opacity-50 relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
6666
className,
6767
)}
6868
{...props}

0 commit comments

Comments
 (0)