Skip to content

Commit cdef9ef

Browse files
Switch to accepting schema as introspection (#340)
1 parent fbc5abb commit cdef9ef

34 files changed

+1212
-939
lines changed

.eslintrc.yml

+6
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ overrides:
4343
'@typescript-eslint/array-type': [error, { default: generic }]
4444
'@typescript-eslint/consistent-indexed-object-style':
4545
[error, index-signature]
46+
'@typescript-eslint/no-unused-vars':
47+
- 'error'
48+
- vars: all
49+
args: all
50+
argsIgnorePattern: '^_'
51+
varsIgnorePattern: '^_T'
4652

4753
# FIXME: remove below rules
4854
'react/jsx-key': off

package-lock.json

+4-16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

-2
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,12 @@
5050
"@mui/lab": "5.0.0-alpha.114",
5151
"@mui/material": "5.11.2",
5252
"commonmark": "0.30.0",
53-
"lodash": "4.17.21",
5453
"svg-pan-zoom": "3.6.1"
5554
},
5655
"devDependencies": {
5756
"@playwright/test": "1.32.0",
5857
"@svgr/webpack": "6.5.1",
5958
"@types/commonmark": "0.27.5",
60-
"@types/lodash": "4.14.182",
6159
"@types/node": "18.15.5",
6260
"@types/react": "18.0.26",
6361
"@types/react-dom": "18.0.10",

playwright.config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const config: PlaywrightTestConfig = {
3333
actionTimeout: 0,
3434

3535
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
36-
trace: 'on-first-retry',
36+
trace: 'retain-on-failure',
3737
},
3838
projects: [
3939
{

src/components/GraphViewport.tsx

+23-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { Component } from 'react';
22

3-
import { SVGRender, Viewport } from './../graph/';
3+
import { SVGRender, TypeGraph, Viewport } from './../graph/';
44
import LoadingAnimation from './utils/LoadingAnimation';
5+
import { VoyagerDisplayOptions } from './Voyager';
56

67
const svgRenderer = new SVGRender();
78

89
interface GraphViewportProps {
9-
typeGraph: any;
10-
displayOptions: any;
10+
typeGraph: TypeGraph | null;
11+
displayOptions: VoyagerDisplayOptions;
1112

1213
selectedTypeID: string;
1314
selectedEdgeID: string;
@@ -16,8 +17,21 @@ interface GraphViewportProps {
1617
onSelectEdge: (id: string) => void;
1718
}
1819

19-
export default class GraphViewport extends Component<GraphViewportProps> {
20-
state = { typeGraph: null, displayOptions: null, svgViewport: null };
20+
interface GraphViewportState {
21+
typeGraph: TypeGraph | null;
22+
displayOptions: VoyagerDisplayOptions | null;
23+
svgViewport: Viewport | null;
24+
}
25+
26+
export default class GraphViewport extends Component<
27+
GraphViewportProps,
28+
GraphViewportState
29+
> {
30+
state: GraphViewportState = {
31+
typeGraph: null,
32+
displayOptions: null,
33+
svgViewport: null,
34+
};
2135

2236
// Handle async graph rendering based on this example
2337
// https://gist.github.com/bvaughn/982ab689a41097237f6e9860db7ca8d6
@@ -69,7 +83,10 @@ export default class GraphViewport extends Component<GraphViewportProps> {
6983
this._cleanupSvgViewport();
7084
}
7185

72-
_renderSvgAsync(typeGraph, displayOptions) {
86+
_renderSvgAsync(
87+
typeGraph: TypeGraph | null,
88+
displayOptions: VoyagerDisplayOptions | null,
89+
) {
7390
if (typeGraph == null || displayOptions == null) {
7491
return; // Nothing to render
7592
}

src/components/IntrospectionModal.tsx

+5-4
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ import TextField from '@mui/material/TextField';
1212
import Tooltip from '@mui/material/Tooltip';
1313
import Typography from '@mui/material/Typography';
1414
import Grid from '@mui/material/Unstable_Grid2';
15+
import { buildClientSchema } from 'graphql/utilities';
1516
import { useState } from 'react';
1617

1718
import { voyagerIntrospectionQuery } from '../utils/introspection-query';
18-
import { sdlToIntrospection } from '../utils/sdl-to-introspection';
19+
import { sdlToSchema } from '../utils/sdl-to-introspection';
1920

2021
enum InputType {
2122
Presets = 'Presets',
@@ -102,14 +103,14 @@ export function IntrospectionModal(props: IntrospectionModalProps) {
102103
function handleSubmit() {
103104
switch (inputType) {
104105
case InputType.Presets:
105-
onChange(presets[activePreset].data);
106+
onChange(buildClientSchema(presets[activePreset].data));
106107
break;
107108
case InputType.Introspection:
108109
// check for errors and check if valid
109-
onChange(JSON.parse(jsonText).data);
110+
onChange(buildClientSchema(JSON.parse(jsonText).data));
110111
break;
111112
case InputType.SDL:
112-
onChange(sdlToIntrospection(sdlText));
113+
onChange(sdlToSchema(sdlText));
113114
break;
114115
}
115116
setSubmitted({ inputType, sdlText, jsonText, activePreset });

src/components/Voyager.tsx

+51-41
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import './viewport.css';
44
import Button from '@mui/material/Button';
55
import Stack from '@mui/material/Stack';
66
import { ThemeProvider } from '@mui/material/styles';
7+
import { ExecutionResult } from 'graphql/execution';
8+
import { GraphQLSchema } from 'graphql/type';
9+
import { buildClientSchema, IntrospectionQuery } from 'graphql/utilities';
710
import {
811
Children,
912
type ReactElement,
@@ -15,7 +18,7 @@ import {
1518
} from 'react';
1619

1720
import { getTypeGraph } from '../graph/';
18-
import { extractTypeId, getSchema } from '../introspection';
21+
import { extractTypeName, getSchema, typeNameToId } from '../introspection';
1922
import DocExplorer from './doc-explorer/DocExplorer';
2023
import GraphViewport from './GraphViewport';
2124
import { IntrospectionModal } from './IntrospectionModal';
@@ -33,17 +36,8 @@ export interface VoyagerDisplayOptions {
3336
hideRoot?: boolean;
3437
}
3538

36-
const defaultDisplayOptions = {
37-
rootType: undefined,
38-
skipRelay: true,
39-
skipDeprecated: true,
40-
sortByAlphabet: false,
41-
showLeafFields: true,
42-
hideRoot: false,
43-
};
44-
4539
export interface VoyagerProps {
46-
introspection: unknown;
40+
introspection: Promise<ExecutionResult<IntrospectionQuery> | GraphQLSchema>;
4741
displayOptions?: VoyagerDisplayOptions;
4842
introspectionPresets?: { [name: string]: any };
4943
allowToChangeSchema?: boolean;
@@ -55,43 +49,59 @@ export interface VoyagerProps {
5549
}
5650

5751
export default function Voyager(props: VoyagerProps) {
52+
const initialDisplayOptions: VoyagerDisplayOptions = useMemo(
53+
() => ({
54+
rootType: undefined,
55+
skipRelay: true,
56+
skipDeprecated: true,
57+
sortByAlphabet: false,
58+
showLeafFields: true,
59+
hideRoot: false,
60+
...props.displayOptions,
61+
}),
62+
[props.displayOptions],
63+
);
64+
5865
const [introspectionModalOpen, setIntrospectionModalOpen] = useState(false);
59-
const [introspectionData, setIntrospectionData] = useState(null);
60-
const [displayOptions, setDisplayOptions] = useState({
61-
...defaultDisplayOptions,
62-
...props.displayOptions,
63-
});
66+
const [introspectionResult, setIntrospectionResult] = useState(null);
67+
const [displayOptions, setDisplayOptions] = useState(initialDisplayOptions);
6468

6569
useEffect(() => {
6670
// FIXME: handle rejection and also handle errors inside introspection
6771
// eslint-disable-next-line @typescript-eslint/no-floating-promises
68-
Promise.resolve(props.introspection).then(({ data }) => {
69-
setIntrospectionData(data);
70-
setSelected({ typeID: null, edgeID: null });
71-
});
72+
Promise.resolve(props.introspection).then(setIntrospectionResult);
7273
}, [props.introspection]);
7374

74-
const schema = useMemo(
75-
() =>
76-
getSchema(
77-
introspectionData,
78-
displayOptions.sortByAlphabet,
79-
displayOptions.skipRelay,
80-
displayOptions.skipDeprecated,
81-
),
82-
[
83-
introspectionData,
75+
useEffect(() => {
76+
setDisplayOptions(initialDisplayOptions);
77+
}, [introspectionResult, initialDisplayOptions]);
78+
79+
const typeGraph = useMemo(() => {
80+
if (introspectionResult == null) {
81+
return null;
82+
}
83+
84+
const introspectionSchema =
85+
introspectionResult instanceof GraphQLSchema
86+
? introspectionResult
87+
: buildClientSchema(introspectionResult.data);
88+
89+
const schema = getSchema(
90+
introspectionSchema,
8491
displayOptions.sortByAlphabet,
8592
displayOptions.skipRelay,
8693
displayOptions.skipDeprecated,
87-
],
88-
);
94+
);
95+
return getTypeGraph(
96+
schema,
97+
displayOptions.rootType,
98+
displayOptions.hideRoot,
99+
);
100+
}, [introspectionResult, displayOptions]);
89101

90-
const typeGraph = useMemo(
91-
() =>
92-
getTypeGraph(schema, displayOptions.rootType, displayOptions.hideRoot),
93-
[schema, displayOptions.rootType, displayOptions.hideRoot],
94-
);
102+
useEffect(() => {
103+
setSelected({ typeID: null, edgeID: null });
104+
}, [typeGraph]);
95105

96106
const [selected, setSelected] = useState({ typeID: null, edgeID: null });
97107

@@ -123,7 +133,7 @@ export default function Voyager(props: VoyagerProps) {
123133
open={introspectionModalOpen}
124134
presets={props.introspectionPresets}
125135
onClose={() => setIntrospectionModalOpen(false)}
126-
onChange={setIntrospectionData}
136+
onChange={setIntrospectionResult}
127137
/>
128138
);
129139
}
@@ -171,12 +181,12 @@ export default function Voyager(props: VoyagerProps) {
171181
}
172182

173183
function renderSettings() {
174-
if (schema == null) return null;
184+
if (typeGraph == null) return null;
175185

176186
return (
177187
<Settings
178-
schema={schema}
179188
options={displayOptions}
189+
typeGraph={typeGraph}
180190
onChange={(options) =>
181191
setDisplayOptions((oldOptions) => ({ ...oldOptions, ...options }))
182192
}
@@ -213,7 +223,7 @@ export default function Voyager(props: VoyagerProps) {
213223
// deselect if click again
214224
return { ...oldSelected, edgeID: null };
215225
} else {
216-
return { typeID: extractTypeId(edgeID), edgeID };
226+
return { typeID: typeNameToId(extractTypeName(edgeID)), edgeID };
217227
}
218228
});
219229
}

src/components/doc-explorer/Argument.tsx

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
11
import './Argument.css';
22

3+
import { GraphQLArgument, GraphQLNamedType } from 'graphql/type';
4+
5+
import { highlightTerm } from '../../utils/highlight';
36
import Markdown from '../utils/Markdown';
47
import WrappedTypeName from './WrappedTypeName';
58

69
interface ArgumentProps {
7-
arg: any;
10+
arg: GraphQLArgument;
11+
filter: string;
812
expanded: boolean;
9-
onTypeLink: (any) => void;
13+
onTypeLink: (type: GraphQLNamedType) => void;
1014
}
1115

1216
export default function Argument(props: ArgumentProps) {
13-
const { arg, expanded, onTypeLink } = props;
17+
const { arg, filter, expanded, onTypeLink } = props;
1418

1519
return (
1620
<span className={`arg-wrap ${expanded ? '-expanded' : ''}`}>
1721
<Markdown text={arg.description} className="arg-description" />
1822
<span className="arg">
19-
<span className="arg-name">{arg.name}</span>
23+
<span className="arg-name">{highlightTerm(arg.name, filter)}</span>
2024
<WrappedTypeName container={arg} onTypeLink={onTypeLink} />
2125
{arg.defaultValue != null && (
2226
<span>

0 commit comments

Comments
 (0)