Skip to content

Commit

Permalink
manually re-trigger introspection
Browse files Browse the repository at this point in the history
  • Loading branch information
thomasheyenbrock committed Aug 1, 2022
1 parent 50b8875 commit 2ff3f21
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 34 deletions.
5 changes: 5 additions & 0 deletions .changeset/shy-parents-bow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'graphiql': minor
---

Add a toolbar button for manually triggering introspection
5 changes: 5 additions & 0 deletions .changeset/wild-rings-leave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphiql/react': minor
---

Add a method `introspect` to the schema context and provide a short key (`Shift-Ctrl-R`) for triggering introspection
82 changes: 56 additions & 26 deletions packages/graphiql-react/src/schema.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
Dispatch,
ReactNode,
SetStateAction,
useCallback,
useEffect,
useMemo,
useRef,
Expand All @@ -32,14 +33,15 @@ import { createContextHook, createNullableContext } from './utility/context';
* There's a semantic difference between `null` and `undefined`:
* - When `null` is passed explicitly as prop, GraphiQL will run schema-less
* (i.e. it will never attempt to fetch the schema, even when calling the
* `useFetchSchema` hook).
* `introspect` function).
* - When `schema` is `undefined` GraphiQL will attempt to fetch the schema
* when calling `useFetchSchema`.
* when calling `introspect`.
*/
type MaybeGraphQLSchema = GraphQLSchema | null | undefined;

export type SchemaContextType = {
fetchError: string | null;
introspect(): void;
isFetching: boolean;
schema: MaybeGraphQLSchema;
setFetchError: Dispatch<SetStateAction<string | null>>;
Expand Down Expand Up @@ -68,6 +70,12 @@ export function SchemaContextProvider(props: SchemaContextProviderProps) {
const [isFetching, setIsFetching] = useState(false);
const [fetchError, setFetchError] = useState<string | null>(null);

/**
* A counter that is incremented each time introspection is triggered or the
* schema state is updated.
*/
const counterRef = useRef(0);

/**
* Synchronize prop changes with state
*/
Expand All @@ -79,6 +87,12 @@ export function SchemaContextProvider(props: SchemaContextProviderProps) {
? props.schema
: undefined,
);

/**
* Increment the counter so that in-flight introspection requests don't
* override this change.
*/
counterRef.current++;
}, [props.schema]);

/**
Expand Down Expand Up @@ -108,7 +122,7 @@ export function SchemaContextProvider(props: SchemaContextProviderProps) {
* Fetch the schema
*/
const { fetcher, onSchemaChange } = props;
useEffect(() => {
const introspect = useCallback(() => {
/**
* Only introspect if there is no schema provided via props. If the
* prop is passed an introspection result, we do continue but skip the
Expand All @@ -118,11 +132,12 @@ export function SchemaContextProvider(props: SchemaContextProviderProps) {
return;
}

let isActive = true;
const counter = ++counterRef.current;

setSchema(undefined);

const maybeIntrospectionData = props.schema;
async function introspect() {
async function fetchIntrospectionData() {
if (maybeIntrospectionData) {
// No need to introspect if we already have the data
return maybeIntrospectionData;
Expand Down Expand Up @@ -193,43 +208,37 @@ export function SchemaContextProvider(props: SchemaContextProviderProps) {
setFetchError(responseString);
}

introspect()
fetchIntrospectionData()
.then(introspectionData => {
// Don't continue if the effect has already been cleaned up
if (!isActive || !introspectionData) {
console.log(counter, counterRef.current);
/**
* Don't continue if another introspection request has been started in
* the meantime or if there is no introspection data.
*/
if (counter !== counterRef.current || !introspectionData) {
return;
}

try {
const newSchema = buildClientSchema(introspectionData);
// Only override the schema in state if it's still `undefined` (the
// prop and thus the state could have changed while introspecting,
// so this avoids a race condition by prioritizing the state that
// was set after the introspection request was initialized)
setSchema(current => {
if (current === undefined) {
onSchemaChange?.(newSchema);
return newSchema;
}
return current;
});
setSchema(newSchema);
onSchemaChange?.(newSchema);
} catch (error) {
setFetchError(formatError(error as Error));
}
})
.catch(error => {
// Don't continue if the effect has already been cleaned up
if (!isActive) {
/**
* Don't continue if another introspection request has been started in
* the meantime.
*/
if (counter !== counterRef.current) {
return;
}

setFetchError(formatError(error));
setIsFetching(false);
});

return () => {
isActive = false;
};
}, [
fetcher,
introspectionQueryName,
Expand All @@ -239,6 +248,26 @@ export function SchemaContextProvider(props: SchemaContextProviderProps) {
props.schema,
]);

/**
* Trigger introspection automatically
*/
useEffect(() => {
introspect();
}, [introspect]);

/**
* Trigger introspection manually via short key
*/
useEffect(() => {
function triggerIntrospection(event: KeyboardEvent) {
if (event.keyCode === 82 && event.shiftKey && event.ctrlKey) {
introspect();
}
}
window.addEventListener('keydown', triggerIntrospection);
return () => window.removeEventListener('keydown', triggerIntrospection);
});

/**
* Derive validation errors from the schema
*/
Expand All @@ -256,13 +285,14 @@ export function SchemaContextProvider(props: SchemaContextProviderProps) {
const value = useMemo(
() => ({
fetchError,
introspect,
isFetching,
schema,
setFetchError,
setSchema,
validationErrors,
}),
[fetchError, isFetching, schema, validationErrors],
[fetchError, introspect, isFetching, schema, validationErrors],
);

return (
Expand Down
5 changes: 5 additions & 0 deletions packages/graphiql/src/components/GraphiQL.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,11 @@ class GraphiQLWithContext extends React.Component<
}
label="History"
/>
<ToolbarButton
onClick={() => this.props.schemaContext.introspect()}
title="Fetch GraphQL schema using introspection (Shift-Ctrl-R)"
label="Introspect"
/>
{this.props.toolbar?.additionalContent
? this.props.toolbar.additionalContent
: null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { ExampleSchema } from './ExampleSchema';

const defaultSchemaContext: SchemaContextType = {
fetchError: null,
introspect() {},
isFetching: false,
schema: ExampleSchema,
setFetchError() {},
Expand Down
15 changes: 7 additions & 8 deletions packages/vscode-graphql-syntax/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
# vscode-graphql-syntax

## 1.0.4
### Patch Changes


### Patch Changes

- [#2573](https://github.com/graphql/graphiql/pull/2573) [`a358ac1d`](https://github.com/graphql/graphiql/commit/a358ac1d00082643e124085bca09992adeef212a) Thanks [@acao](https://github.com/acao)! - ## Enhancement

Here we move vscode grammars and basic language support to a new [`GraphQL.vscode-graphql-syntax`](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql-syntax) extension. `GraphQL.vscode-graphql` now depends on this new syntax extension. This constitutes no breaking change for `vscode-graphql` users, as this extension will be installed automatically as an `extensionDependency` for `vscode-graphql`. Both extensions will now have independent release lifecycles, but vscode will keep them both up to date for you :)

Firstly, this allows users to only install the syntax highlighting extension if they don't need LSP server features.

Secondly, this subtle but important change allows alternative LSP servers and non-LSP graphql extensions to use (and contribute!) to our shared, graphql community syntax highlighting. In some ways, it acts as a shared tooling & annotation spec, though it is intended just for vscode, it perhaps can be used as a point of reference for others implementing (embedded) graphql syntax highlighting elsewhere!

If your language and/or library and/or framework would like vscode highlighting, come [join the party](https://github.com/graphql/graphiql/tree/main/packages/vscode-graphql-syntax#contributing)!

If you use relay, we would highly reccomend using the `relay-compiler lsp` extension for vscode [Relay Graphql](https://marketplace.visualstudio.com/items?itemName=meta.relay) (`meta.relay`). They will be [using the new standalone syntax extension](https://github.com/facebook/relay/pull/4032) very soon!

Even non-relay users may want to try this extension as an alternative to our reference implementation, as relay's configuration has relative similarity with `graphql-config`'s format, and doesn't necessitate the use of relay client afaik. We are working hard to optimize and improve `graphql-language-service-server` as a typescript reference implementation, and have some exciting features coming soon, however it's hard to offer more than a brand new & highly performant graphql LSP server written in Rust based on the latest graphql spec with a (mostly) paid team and dedicated open source ecosystem community of co-maintainers! And their implementation appears to allow you to opt out of any relay-specific conventions if you need more flexibility.

0 comments on commit 2ff3f21

Please sign in to comment.