Skip to content

Commit

Permalink
modify apollo angular visitor to allow external operation definitions…
Browse files Browse the repository at this point in the history
… related to #4719 (#4765)

* modify apollo angular visitor to allow external opration definitions

* handle document

* fix document

* resolve unit test failures

* add unit test

* reset version

* add changeset

* add a warning for importOperationTypesFrom, update apollo-angular documentation

* more warnings and tests to help get configuration correct

* more eslint ignores

* update documentation for external documentMode

* more readme updates

* more readme updates
  • Loading branch information
jbarrus authored Nov 4, 2020
1 parent 93e49f8 commit 7610ce6
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .changeset/four-ants-lick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-codegen/typescript-apollo-angular': minor
---

add support for importing operations from external file in angular-apollo plugin
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,11 @@ export class ClientSideBaseVisitor<
`import * as Operations from './${this.clearExtension(basename(this._documents[0].location))}';`
);
} else {
if (!this.config.importDocumentNodeExternallyFrom) {
// eslint-disable-next-line no-console
console.warn('importDocumentNodeExternallyFrom must be provided if documentMode=external');
}

this._imports.add(
`import * as Operations from '${this.clearExtension(this.config.importDocumentNodeExternallyFrom)}';`
);
Expand Down
43 changes: 34 additions & 9 deletions packages/plugins/typescript/apollo-angular/src/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export class ApolloAngularVisitor extends ClientSideBaseVisitor<
ApolloAngularRawPluginConfig,
ApolloAngularPluginConfig
> {
private _externalImportPrefix = '';
private _operationsToInclude: {
node: OperationDefinitionNode;
documentVariableName: string;
Expand Down Expand Up @@ -77,6 +78,22 @@ export class ApolloAngularVisitor extends ClientSideBaseVisitor<
documents
);

if (this.config.importOperationTypesFrom) {
this._externalImportPrefix = `${this.config.importOperationTypesFrom}.`;

if (this.config.documentMode !== DocumentMode.external || !this.config.importDocumentNodeExternallyFrom) {
// eslint-disable-next-line no-console
console.warn(
'"importOperationTypesFrom" should be used with "documentMode=external" and "importDocumentNodeExternallyFrom"'
);
}

if (this.config.importOperationTypesFrom !== 'Operations') {
// eslint-disable-next-line no-console
console.warn('importOperationTypesFrom only works correctly when left empty or set to "Operations"');
}
}

autoBind(this);
}

Expand Down Expand Up @@ -222,7 +239,9 @@ export class ApolloAngularVisitor extends ClientSideBaseVisitor<
}

private _getDocumentNodeVariable(node: OperationDefinitionNode, documentVariableName: string): string {
return this.config.documentMode === DocumentMode.external ? `Operations.${node.name.value}` : documentVariableName;
return this.config.importOperationTypesFrom
? `${this.config.importOperationTypesFrom}.${documentVariableName}`
: documentVariableName;
}

private _operationSuffix(operationType: string): string {
Expand Down Expand Up @@ -256,6 +275,9 @@ export class ApolloAngularVisitor extends ClientSideBaseVisitor<
serviceName,
});

operationResultType = this._externalImportPrefix + operationResultType;
operationVariablesTypes = this._externalImportPrefix + operationVariablesTypes;

const content = `
@Injectable({
providedIn: ${this._providedIn(node)}
Expand Down Expand Up @@ -285,20 +307,23 @@ export class ApolloAngularVisitor extends ClientSideBaseVisitor<

const allPossibleActions = this._operationsToInclude
.map(o => {
const operationResultType = this._externalImportPrefix + o.operationResultType;
const operationVariablesTypes = this._externalImportPrefix + o.operationVariablesTypes;

const optionalVariables =
!o.node.variableDefinitions ||
o.node.variableDefinitions.length === 0 ||
o.node.variableDefinitions.every(v => v.type.kind !== Kind.NON_NULL_TYPE || !!v.defaultValue);

const options =
o.operationType === 'Mutation'
? `${o.operationType}OptionsAlone<${o.operationResultType}, ${o.operationVariablesTypes}>`
: `${o.operationType}OptionsAlone<${o.operationVariablesTypes}>`;
? `${o.operationType}OptionsAlone<${operationResultType}, ${operationVariablesTypes}>`
: `${o.operationType}OptionsAlone<${operationVariablesTypes}>`;

const method = `
${camelCase(o.node.name.value)}(variables${optionalVariables ? '?' : ''}: ${
o.operationVariablesTypes
}, options?: ${options}) {
${camelCase(o.node.name.value)}(variables${
optionalVariables ? '?' : ''
}: ${operationVariablesTypes}, options?: ${options}) {
return this.${camelCase(o.serviceName)}.${actionType(o.operationType)}(variables, options)
}`;

Expand All @@ -307,9 +332,9 @@ ${camelCase(o.node.name.value)}(variables${optionalVariables ? '?' : ''}: ${
if (o.operationType === 'Query') {
watchMethod = `
${camelCase(o.node.name.value)}Watch(variables${optionalVariables ? '?' : ''}: ${
o.operationVariablesTypes
}, options?: WatchQueryOptionsAlone<${o.operationVariablesTypes}>) {
${camelCase(o.node.name.value)}Watch(variables${
optionalVariables ? '?' : ''
}: ${operationVariablesTypes}, options?: WatchQueryOptionsAlone<${operationVariablesTypes}>) {
return this.${camelCase(o.serviceName)}.watch(variables, options)
}`;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { parse, GraphQLSchema, buildClientSchema, buildSchema, extendSchema } fr
import { Types, mergeOutputs } from '@graphql-codegen/plugin-helpers';
import { plugin as tsPlugin } from '../../typescript/src/index';
import { plugin as tsDocumentsPlugin } from '../../../typescript/operations/src/index';
import { DocumentMode } from '@graphql-codegen/visitor-plugin-common';

describe('Apollo Angular', () => {
const schema = buildClientSchema(require('../../../../../dev-test/githunt/schema.json'));
Expand Down Expand Up @@ -190,6 +191,110 @@ describe('Apollo Angular', () => {
expect(content.content).not.toContain('@namedClient');
await validateTypeScript(content, modifiedSchema, docs, {});
});

it('should output warning if documentMode = external and importDocumentNodeExternallyFrom is not set', async () => {
spyOn(console, 'warn');
const docs = [{ location: '', document: basicDoc }];
await plugin(
schema,
docs,
{
documentMode: DocumentMode.external,
},
{
outputFile: 'graphql.ts',
}
);

// eslint-disable-next-line no-console
expect(console.warn).toHaveBeenCalledWith(
'importDocumentNodeExternallyFrom must be provided if documentMode=external'
);
});

it('output warning if importOperationTypesFrom is set to something other than "Operations"', async () => {
spyOn(console, 'warn');
const docs = [{ location: '', document: basicDoc }];
await plugin(
schema,
docs,
{
documentMode: DocumentMode.external,
importOperationTypesFrom: 'Whatever',
},
{
outputFile: 'graphql.ts',
}
);

// eslint-disable-next-line no-console
expect(console.warn).toHaveBeenCalledWith(
'importOperationTypesFrom only works correctly when left empty or set to "Operations"'
);
});

it('output warning if importOperationTypesFrom is set and documentMode is not "external"', async () => {
spyOn(console, 'warn');
const docs = [{ location: '', document: basicDoc }];
await plugin(
schema,
docs,
{
importOperationTypesFrom: 'Operations',
},
{
outputFile: 'graphql.ts',
}
);

// eslint-disable-next-line no-console
expect(console.warn).toHaveBeenCalledWith(
'"importOperationTypesFrom" should be used with "documentMode=external" and "importDocumentNodeExternallyFrom"'
);
});

it('output warning if importOperationTypesFrom is set and importDocumentNodeExternallyFrom is not', async () => {
spyOn(console, 'warn');
const docs = [{ location: '', document: basicDoc }];
await plugin(
schema,
docs,
{
documentMode: DocumentMode.external,
importOperationTypesFrom: 'Operations',
},
{
outputFile: 'graphql.ts',
}
);

// eslint-disable-next-line no-console
expect(console.warn).toHaveBeenCalledWith(
'"importOperationTypesFrom" should be used with "documentMode=external" and "importDocumentNodeExternallyFrom"'
);
});

it('should allow importing operations and documents from another file', async () => {
const docs = [{ location: '', document: basicDoc }];
const content = (await plugin(
schema,
docs,
{
documentMode: DocumentMode.external,
importOperationTypesFrom: 'Operations',
importDocumentNodeExternallyFrom: '@myproject/generated',
},
{
outputFile: 'graphql.ts',
}
)) as Types.ComplexPluginOutput;

expect(content.prepend).toContain(`import * as Operations from '@myproject/generated';`);
expect(content.content).toContain('Operations.TestQuery');
expect(content.content).toContain('Operations.TestQueryVariables');
expect(content.content).toContain('Operations.TestDocument');
await validateTypeScript(content, schema, docs, {});
});
});

describe('Component', () => {
Expand Down
5 changes: 3 additions & 2 deletions website/docs/generated-config/typescript-apollo-angular.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,15 +287,16 @@ type: `string`
default: ``

This config should be used if `documentMode` is `external`. This has 2 usage:
- any string: This would be the path to import document nodes from. This can be used if we want to manually create the document nodes e.g. Use `graphql-tag` in a separate file and export the generated document
- any string: This would be the path to import document nodes from. This can be used if we want to manually create the document nodes e.g. Use `graphql-tag` in a separate file and export the generated document. `importOperationTypesFrom` and `importOperationTypesFrom` must also be set as well.
- 'near-operation-file': This is a special mode that is intended to be used with `near-operation-file` preset to import document nodes from those files. If these files are `.graphql` files, we make use of webpack loader.

#### Usage Examples

```yml
config:
documentMode: external
importDocumentNodeExternallyFrom: path/to/document-node-file
importOperationTypesFrom: 'Operations',
importDocumentNodeExternallyFrom: '@myproject/generated'
```

```yml
Expand Down

0 comments on commit 7610ce6

Please sign in to comment.