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

feat: implied or external fragments, for #612 #1750

Merged
merged 8 commits into from
Jan 7, 2021
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
79 changes: 79 additions & 0 deletions packages/codemirror-graphql/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ CodeMirror helpers install themselves to the global CodeMirror when they
are imported.

```js
import type { ValidationContext, SDLValidationContext } from 'graphql';

import CodeMirror from 'codemirror';
import 'codemirror/addon/hint/show-hint';
import 'codemirror/addon/lint/lint';
Expand All @@ -30,6 +32,83 @@ CodeMirror.fromTextArea(myTextarea, {
mode: 'graphql',
lint: {
schema: myGraphQLSchema,
validationRules: [ExampleRule],
},
hintOptions: {
schema: myGraphQLSchema,
},
});
```

## External Fragments Example

If you want to have autcompletion for external fragment definitions, there's a new configuration setting available

```ts
import CodeMirror from 'codemirror';
import 'codemirror/addon/hint/show-hint';
import 'codemirror/addon/lint/lint';
import 'codemirror-graphql/hint';
import 'codemirror-graphql/lint';
import 'codemirror-graphql/mode';

const externalFragments = `
fragment MyFragment on Example {
id: ID!
name: String!
}
fragment AnotherFragment on Example {
id: ID!
title: String!
}
`;

CodeMirror.fromTextArea(myTextarea, {
mode: 'graphql',
lint: {
schema: myGraphQLSchema,
},
hintOptions: {
schema: myGraphQLSchema,
// here we use a string, but
// you can also provide an array of FragmentDefinitionNodes
externalFragments,
},
});
```

### Custom Validation Rules

If you want to show custom validation, you can do that too! It uses the `ValidationRule` interface.

```js
import type { ValidationRule } from 'graphql';

import CodeMirror from 'codemirror';
import 'codemirror/addon/hint/show-hint';
import 'codemirror/addon/lint/lint';
import 'codemirror-graphql/hint';
import 'codemirror-graphql/lint';
import 'codemirror-graphql/mode';

const ExampleRule: ValidationRule = context => {
// your custom rules here
const schema = context.getSchema();
const document = context.getDocument();
return {
NamedType(node) {
if (node.name.value !== node.name.value.toLowercase()) {
context.reportError('only lowercase type names allowed!');
}
},
};
};

CodeMirror.fromTextArea(myTextarea, {
mode: 'graphql',
lint: {
schema: myGraphQLSchema,
validationRules: [ExampleRule],
},
hintOptions: {
schema: myGraphQLSchema,
Expand Down
11 changes: 11 additions & 0 deletions packages/codemirror-graphql/src/__tests__/hint-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ function createEditorWithHint() {
schema: TestSchema,
closeOnUnfocus: false,
completeSingle: false,
externalFragments: 'fragment Example on Test { id }',
},
});
}
Expand Down Expand Up @@ -766,6 +767,11 @@ describe('graphql-hint', () => {
type: TestType,
description: 'fragment Foo on Test',
},
{
text: 'Example',
type: TestType,
description: 'fragment Example on Test',
},
];
const expectedSuggestions = getExpectedSuggestions(list);
expect(suggestions.list).to.deep.equal(expectedSuggestions);
Expand All @@ -782,6 +788,11 @@ describe('graphql-hint', () => {
type: TestType,
description: 'fragment Foo on Test',
},
{
text: 'Example',
type: TestType,
description: 'fragment Example on Test',
},
];
const expectedSuggestions = getExpectedSuggestions(list);
expect(suggestions.list).to.deep.equal(expectedSuggestions);
Expand Down
5 changes: 4 additions & 1 deletion packages/codemirror-graphql/src/hint.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import CodeMirror from 'codemirror';
import { getAutocompleteSuggestions } from 'graphql-language-service-interface';
import { Position } from 'graphql-language-service-utils';

import { getFragmentDefinitions } from './utils/getFragmentDefinitions';
/**
* Registers a "hint" helper for CodeMirror.
*
Expand Down Expand Up @@ -52,6 +52,9 @@ CodeMirror.registerHelper('hint', 'graphql', (editor, options) => {
editor.getValue(),
position,
token,
Array.isArray(options.externalFragments)
? options.externalFragments
: getFragmentDefinitions(options.externalFragments),
);

const results = {
Expand Down
3 changes: 1 addition & 2 deletions packages/codemirror-graphql/src/lint.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ const TYPE = {
*/
CodeMirror.registerHelper('lint', 'graphql', (text, options) => {
const schema = options.schema;
const validationRules = options.validationRules;
const rawResults = getDiagnostics(text, schema, validationRules);
const rawResults = getDiagnostics(text, schema, options.validationRules);

const results = rawResults.map(error => ({
message: error.message,
Expand Down
12 changes: 12 additions & 0 deletions packages/codemirror-graphql/src/utils/getFragmentDefinitions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { visit, parse } from 'graphql';
import type { FragmentDefinitionNode } from 'graphql';

export function getFragmentDefinitions(graphqlString: string) {
const definitions: FragmentDefinitionNode[] = [];
visit(parse(graphqlString), {
FragmentDefinition(node) {
definitions.push(node);
},
});
return definitions;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@

import { useMemo } from 'react';

import getQueryFacts from '../../utility/getQueryFacts';
import getOperationFacts from '../../utility/getQueryFacts';
import useSchema from './useSchema';
import useOperation from './useOperation';

export default function useQueryFacts() {
const schema = useSchema();
const { text } = useOperation();
return useMemo(() => (schema ? getQueryFacts(schema, text) : null), [
return useMemo(() => (schema ? getOperationFacts(schema, text) : null), [
schema,
text,
]);
Expand Down
7 changes: 4 additions & 3 deletions packages/graphiql-2-rfc-context/src/utility/getQueryFacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
OperationDefinitionNode,
NamedTypeNode,
GraphQLNamedType,
Kind,
} from 'graphql';

export type VariableToType = {
Expand All @@ -30,7 +31,7 @@ export type QueryFacts = {
*
* If the query cannot be parsed, returns undefined.
*/
export default function getQueryFacts(
export default function getOperationFacts(
schema?: GraphQLSchema,
documentStr?: string | null,
): QueryFacts | undefined {
Expand All @@ -52,7 +53,7 @@ export default function getQueryFacts(
// Collect operations by their names.
const operations: OperationDefinitionNode[] = [];
documentAST.definitions.forEach(def => {
if (def.kind === 'OperationDefinition') {
if (def.kind === Kind.OPERATION_DEFINITION) {
operations.push(def);
}
});
Expand All @@ -71,7 +72,7 @@ export function collectVariables(
[variable: string]: GraphQLNamedType;
} = Object.create(null);
documentAST.definitions.forEach(definition => {
if (definition.kind === 'OperationDefinition') {
if (definition.kind === Kind.OPERATION_DEFINITION) {
const variableDefinitions = definition.variableDefinitions;
if (variableDefinitions) {
variableDefinitions.forEach(({ variable, type }) => {
Expand Down
3 changes: 2 additions & 1 deletion packages/graphiql/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,8 @@ For more details on props, see the [API Docs](https://graphiql-test.netlify.app/
| `query` | `string` (GraphQL) | initial displayed query, if `undefined` is provided, the stored query or `defaultQuery` will be used. You can also set this value at runtime to override the current operation editor state. |
| `validationRules` | `ValidationRule[]` | A array of validation rules that will be used for validating the GraphQL operations. If `undefined` is provided, the default rules (exported as `specifiedRules` from `graphql`) will be used. |
| `variables` | `string` (JSON) | initial displayed query variables, if `undefined` is provided, the stored variables will be used. |
| `headers` | `string` (JSON) | initial displayed request headers. if not defined, it will default to the stored headers if `shouldPersistHeaders` is enabled. |
| `headers` | `string` | initial displayed request headers. if not defined, it will default to the stored headers if `shouldPersistHeaders` is enabled. |
| `externalFragments` | `string | FragmentDefinitionNode[]` | provide fragments external to the operation for completion, validation, and for selective use when executing operations. |
| `operationName` | `string` | an optional name of which GraphQL operation should be executed. |
| `response` | `string` (JSON) | an optional JSON string to use as the initial displayed response. If not provided, no response will be initially shown. You might provide this if illustrating the result of the initial query. |
| `storage` | [`Storage`](https://graphiql-test.netlify.app/typedoc/interfaces/graphiql.storage.html) | **Default:** `window.localStorage`. an interface that matches `window.localStorage` signature that GraphiQL will use to persist state. |
Expand Down
1 change: 1 addition & 0 deletions packages/graphiql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"codemirror": "^5.54.0",
"codemirror-graphql": "^0.14.0",
"copy-to-clipboard": "^3.2.0",
"graphql-language-service": "^3.0.2",
"entities": "^2.0.0",
"markdown-it": "^10.0.0"
},
Expand Down
Loading