Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to accepting schema as introspection #340

Merged
merged 1 commit into from
Aug 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ overrides:
'@typescript-eslint/array-type': [error, { default: generic }]
'@typescript-eslint/consistent-indexed-object-style':
[error, index-signature]
'@typescript-eslint/no-unused-vars':
- 'error'
- vars: all
args: all
argsIgnorePattern: '^_'
varsIgnorePattern: '^_T'

# FIXME: remove below rules
'react/jsx-key': off
Expand Down
20 changes: 4 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,12 @@
"@mui/lab": "5.0.0-alpha.114",
"@mui/material": "5.11.2",
"commonmark": "0.30.0",
"lodash": "4.17.21",
"svg-pan-zoom": "3.6.1"
},
"devDependencies": {
"@playwright/test": "1.32.0",
"@svgr/webpack": "6.5.1",
"@types/commonmark": "0.27.5",
"@types/lodash": "4.14.182",
"@types/node": "18.15.5",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.10",
Expand Down
2 changes: 1 addition & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const config: PlaywrightTestConfig = {
actionTimeout: 0,

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
trace: 'retain-on-failure',
},
projects: [
{
Expand Down
29 changes: 23 additions & 6 deletions src/components/GraphViewport.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Component } from 'react';

import { SVGRender, Viewport } from './../graph/';
import { SVGRender, TypeGraph, Viewport } from './../graph/';
import LoadingAnimation from './utils/LoadingAnimation';
import { VoyagerDisplayOptions } from './Voyager';

const svgRenderer = new SVGRender();

interface GraphViewportProps {
typeGraph: any;
displayOptions: any;
typeGraph: TypeGraph | null;
displayOptions: VoyagerDisplayOptions;

selectedTypeID: string;
selectedEdgeID: string;
Expand All @@ -16,8 +17,21 @@ interface GraphViewportProps {
onSelectEdge: (id: string) => void;
}

export default class GraphViewport extends Component<GraphViewportProps> {
state = { typeGraph: null, displayOptions: null, svgViewport: null };
interface GraphViewportState {
typeGraph: TypeGraph | null;
displayOptions: VoyagerDisplayOptions | null;
svgViewport: Viewport | null;
}

export default class GraphViewport extends Component<
GraphViewportProps,
GraphViewportState
> {
state: GraphViewportState = {
typeGraph: null,
displayOptions: null,
svgViewport: null,
};

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

_renderSvgAsync(typeGraph, displayOptions) {
_renderSvgAsync(
typeGraph: TypeGraph | null,
displayOptions: VoyagerDisplayOptions | null,
) {
if (typeGraph == null || displayOptions == null) {
return; // Nothing to render
}
Expand Down
9 changes: 5 additions & 4 deletions src/components/IntrospectionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import TextField from '@mui/material/TextField';
import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import { buildClientSchema } from 'graphql/utilities';
import { useState } from 'react';

import { voyagerIntrospectionQuery } from '../utils/introspection-query';
import { sdlToIntrospection } from '../utils/sdl-to-introspection';
import { sdlToSchema } from '../utils/sdl-to-introspection';

enum InputType {
Presets = 'Presets',
Expand Down Expand Up @@ -102,14 +103,14 @@ export function IntrospectionModal(props: IntrospectionModalProps) {
function handleSubmit() {
switch (inputType) {
case InputType.Presets:
onChange(presets[activePreset].data);
onChange(buildClientSchema(presets[activePreset].data));
break;
case InputType.Introspection:
// check for errors and check if valid
onChange(JSON.parse(jsonText).data);
onChange(buildClientSchema(JSON.parse(jsonText).data));
break;
case InputType.SDL:
onChange(sdlToIntrospection(sdlText));
onChange(sdlToSchema(sdlText));
break;
}
setSubmitted({ inputType, sdlText, jsonText, activePreset });
Expand Down
92 changes: 51 additions & 41 deletions src/components/Voyager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import './viewport.css';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import { ThemeProvider } from '@mui/material/styles';
import { ExecutionResult } from 'graphql/execution';
import { GraphQLSchema } from 'graphql/type';
import { buildClientSchema, IntrospectionQuery } from 'graphql/utilities';
import {
Children,
type ReactElement,
Expand All @@ -15,7 +18,7 @@ import {
} from 'react';

import { getTypeGraph } from '../graph/';
import { extractTypeId, getSchema } from '../introspection';
import { extractTypeName, getSchema, typeNameToId } from '../introspection';
import DocExplorer from './doc-explorer/DocExplorer';
import GraphViewport from './GraphViewport';
import { IntrospectionModal } from './IntrospectionModal';
Expand All @@ -33,17 +36,8 @@ export interface VoyagerDisplayOptions {
hideRoot?: boolean;
}

const defaultDisplayOptions = {
rootType: undefined,
skipRelay: true,
skipDeprecated: true,
sortByAlphabet: false,
showLeafFields: true,
hideRoot: false,
};

export interface VoyagerProps {
introspection: unknown;
introspection: Promise<ExecutionResult<IntrospectionQuery> | GraphQLSchema>;
displayOptions?: VoyagerDisplayOptions;
introspectionPresets?: { [name: string]: any };
allowToChangeSchema?: boolean;
Expand All @@ -55,43 +49,59 @@ export interface VoyagerProps {
}

export default function Voyager(props: VoyagerProps) {
const initialDisplayOptions: VoyagerDisplayOptions = useMemo(
() => ({
rootType: undefined,
skipRelay: true,
skipDeprecated: true,
sortByAlphabet: false,
showLeafFields: true,
hideRoot: false,
...props.displayOptions,
}),
[props.displayOptions],
);

const [introspectionModalOpen, setIntrospectionModalOpen] = useState(false);
const [introspectionData, setIntrospectionData] = useState(null);
const [displayOptions, setDisplayOptions] = useState({
...defaultDisplayOptions,
...props.displayOptions,
});
const [introspectionResult, setIntrospectionResult] = useState(null);
const [displayOptions, setDisplayOptions] = useState(initialDisplayOptions);

useEffect(() => {
// FIXME: handle rejection and also handle errors inside introspection
// eslint-disable-next-line @typescript-eslint/no-floating-promises
Promise.resolve(props.introspection).then(({ data }) => {
setIntrospectionData(data);
setSelected({ typeID: null, edgeID: null });
});
Promise.resolve(props.introspection).then(setIntrospectionResult);
}, [props.introspection]);

const schema = useMemo(
() =>
getSchema(
introspectionData,
displayOptions.sortByAlphabet,
displayOptions.skipRelay,
displayOptions.skipDeprecated,
),
[
introspectionData,
useEffect(() => {
setDisplayOptions(initialDisplayOptions);
}, [introspectionResult, initialDisplayOptions]);

const typeGraph = useMemo(() => {
if (introspectionResult == null) {
return null;
}

const introspectionSchema =
introspectionResult instanceof GraphQLSchema
? introspectionResult
: buildClientSchema(introspectionResult.data);

const schema = getSchema(
introspectionSchema,
displayOptions.sortByAlphabet,
displayOptions.skipRelay,
displayOptions.skipDeprecated,
],
);
);
return getTypeGraph(
schema,
displayOptions.rootType,
displayOptions.hideRoot,
);
}, [introspectionResult, displayOptions]);

const typeGraph = useMemo(
() =>
getTypeGraph(schema, displayOptions.rootType, displayOptions.hideRoot),
[schema, displayOptions.rootType, displayOptions.hideRoot],
);
useEffect(() => {
setSelected({ typeID: null, edgeID: null });
}, [typeGraph]);

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

Expand Down Expand Up @@ -123,7 +133,7 @@ export default function Voyager(props: VoyagerProps) {
open={introspectionModalOpen}
presets={props.introspectionPresets}
onClose={() => setIntrospectionModalOpen(false)}
onChange={setIntrospectionData}
onChange={setIntrospectionResult}
/>
);
}
Expand Down Expand Up @@ -171,12 +181,12 @@ export default function Voyager(props: VoyagerProps) {
}

function renderSettings() {
if (schema == null) return null;
if (typeGraph == null) return null;

return (
<Settings
schema={schema}
options={displayOptions}
typeGraph={typeGraph}
onChange={(options) =>
setDisplayOptions((oldOptions) => ({ ...oldOptions, ...options }))
}
Expand Down Expand Up @@ -213,7 +223,7 @@ export default function Voyager(props: VoyagerProps) {
// deselect if click again
return { ...oldSelected, edgeID: null };
} else {
return { typeID: extractTypeId(edgeID), edgeID };
return { typeID: typeNameToId(extractTypeName(edgeID)), edgeID };
}
});
}
Expand Down
12 changes: 8 additions & 4 deletions src/components/doc-explorer/Argument.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import './Argument.css';

import { GraphQLArgument, GraphQLNamedType } from 'graphql/type';

import { highlightTerm } from '../../utils/highlight';
import Markdown from '../utils/Markdown';
import WrappedTypeName from './WrappedTypeName';

interface ArgumentProps {
arg: any;
arg: GraphQLArgument;
filter: string;
expanded: boolean;
onTypeLink: (any) => void;
onTypeLink: (type: GraphQLNamedType) => void;
}

export default function Argument(props: ArgumentProps) {
const { arg, expanded, onTypeLink } = props;
const { arg, filter, expanded, onTypeLink } = props;

return (
<span className={`arg-wrap ${expanded ? '-expanded' : ''}`}>
<Markdown text={arg.description} className="arg-description" />
<span className="arg">
<span className="arg-name">{arg.name}</span>
<span className="arg-name">{highlightTerm(arg.name, filter)}</span>
<WrappedTypeName container={arg} onTypeLink={onTypeLink} />
{arg.defaultValue != null && (
<span>
Expand Down
Loading
Loading