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

Query complexity #139

Merged
merged 14 commits into from
Sep 8, 2018
Merged

Conversation

abhikmitra
Copy link
Contributor

Fixes #36
Adds an option parameter called complexity to the field decorator.

@codecov
Copy link

codecov bot commented Sep 1, 2018

Codecov Report

Merging #139 into master will increase coverage by 0.01%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #139      +/-   ##
==========================================
+ Coverage   95.08%   95.09%   +0.01%     
==========================================
  Files          60       60              
  Lines         956      958       +2     
  Branches      170      170              
==========================================
+ Hits          909      911       +2     
  Misses         46       46              
  Partials        1        1
Impacted Files Coverage Δ
src/decorators/FieldResolver.ts 93.33% <ø> (ø) ⬆️
src/schema/schema-generator.ts 96.95% <ø> (ø) ⬆️
src/decorators/Field.ts 100% <ø> (ø) ⬆️
src/helpers/handlers.ts 80% <ø> (ø) ⬆️
src/metadata/metadata-storage.ts 100% <100%> (ø) ⬆️
src/interfaces/index.ts 100% <100%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update e1b0e0b...8597c7d. Read the comment docs.

Copy link
Owner

@MichalLytek MichalLytek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. What about queries, mutations and subscriptions? Please add proper changes also to them.
  2. query-complexity is not a simple-usage - please create a separate example folder with comments in code how and why we setup validationRules
  3. no implicit any - please describe the types of graphql-query-complexity module in the way that we use, along with interfaces merging to extend GraphQLFieldConfig interface
  4. add proper tests for this feature - registering in metadata storage, as well as simple check if the validation works (one query that pass, one that fails)
  5. It might be useful to add proper section in docs about this integration, how to use it, etc.

I think I need to make a list of all things that PRs have to provide to be merged 😉

@@ -17,6 +17,7 @@ export type SubscriptionFilterFunc = (

export interface DecoratorTypeOptions {
nullable?: boolean;
complexity?: number;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

graphql-query-complexity allows to calculate it dynamically: complexity: (args, childComplexity) => childComplexity * args.count,

Copy link
Owner

@MichalLytek MichalLytek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add tests also for queries and field resolver as they dynamically add fields to object types too.
Also please create proper documentation of this feature in advanced section, with tutorial how to use it and setup, just like authorization have.

@@ -0,0 +1,35 @@

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this might become obsolete soon 😕
slicknode/graphql-query-complexity@f7713b2
We should just wait with this PR for publishing that package with types.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I compiled his code in TSC and put the types. So it would be similar when he publishes.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partially - newest version is flattened to a single file, so there will be no graphql-query-complexity/dist/QueryComplexity and also it's weird to have typings on repo rather than publish it on DefinitelyTyped. So let's wait a few days and decide what @ivome will do 😉

examples/query-complexity/index.ts Show resolved Hide resolved
* More doucmentation can be found (here)[https://github.com/ivome/graphql-query-complexity]
*/
queryComplexity({
maximumComplexity: 8,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here we can declare the maximum cost of the queries, it can be dynamically and depend on user privileges

*/
queryComplexity({
maximumComplexity: 8,
variables: req.query.variables,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we also have to pass the query variables to the queryComplexity validator

maximumComplexity: 8,
variables: req.query.variables,
onComplete: (complexity: number) => {
console.log("Query Complexity:", complexity);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can also react on complexity calculation and do something (@abhikmitra I don't know why we show example with console.log btw 😆)

Copy link
Contributor Author

@abhikmitra abhikmitra Sep 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure what do you have in your mind other than console log ? I just followed the same format as what is followed in graphql-query-complexity repo

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think here you can subtract the amount from user's account points fund.

examples/query-complexity/recipe-resolver.ts Outdated Show resolved Hide resolved
@@ -18,6 +18,15 @@ async function bootstrap() {
port: 4000,
endpoint: "/graphql",
playground: "/playground",
validationRules: req => [
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this from simple-usage

@@ -227,6 +227,7 @@ export abstract class SchemaGenerator {
);
fieldsMap[field.schemaName] = {
type: this.getGraphQLOutputType(field.name, field.getType(), field.typeOptions),
complexity: field.complexity || 1,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we explicitly put 1 here? Isn't it default behavior of graphql-query-complexity?

@abhikmitra
Copy link
Contributor Author

abhikmitra commented Sep 2, 2018

@19majkel94 So all fieldResolvers will always have a corresponding field type right ? Complexity is respected on field type and would not be respected on @fieldResolvers . Is that ok ?

Should we stop allowing complexity on field resolvers ?

@MichalLytek
Copy link
Owner

@abhikmitra
It's a bit complex 😄
For now field resolvers overwrite authorizations and args settings for the field:
https://github.com/19majkel94/type-graphql/blob/d45829c60f7a9e4a9c49d9df7f95012b31229069/src/metadata/metadata-storage.ts#L206-L215
So it should also overwrite complexity property if provided in decorator option.

And now with the fresh eyesight, my design of metadata building is awful 🤢

@abhikmitra
Copy link
Contributor Author

Fixed your comments . Let me know what else you need.

});
```

And it's done! 😉 . You can pass complexity as option to any of `@field`, `@fieldResolver`, `@mutation` & `@subscription`. For the same property, if both the @fieldresolver as well as @field have complexity defined , then the complexity passed to the field resolver decorator takes precedence.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PascalCase for decorators names and always wrap in backticks

@Field({ complexity: 6 })
title: string;

@Field(type => String, { nullable: true, deprecationReason: "Use `description` field instead" })
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove also all this not needed fields - keep focus only on complexity feature, the Recipe is just the background.

@@ -1,7 +1,7 @@
import "reflect-metadata";
import { GraphQLServer, Options } from "graphql-yoga";
import { buildSchema } from "../../src";

import queryComplexity from "graphql-query-complexity";
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is not needed.

@@ -88,6 +91,13 @@ describe("Fields - schema", () => {
expect(schemaIntrospection).toBeDefined();
});

it("should add complexity info to the metadata storage", async () => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should register complexity info for field

fieldResolverMethod
}
}`;
const ast = parse(query);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you create a helper function in this describe suite to avoid duplication in this three it test cases?


And it's done! 😉 . You can pass complexity as option to any of `@field`, `@fieldResolver`, `@mutation` & `@subscription`. For the same property, if both the @fieldresolver as well as @field have complexity defined , then the complexity passed to the field resolver decorator takes precedence.

Have a look at the [graphql-query-complexity](https://github.com/ivome/graphql-query-complexity) docs to understand more about how query complexity is computed.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For more info about how query complexity is computed, please visit graphql-query-complexity docs.

});
```

And it's done! 😉 . You can pass complexity as option to any of `@field`, `@fieldResolver`, `@mutation` & `@subscription`. For the same property, if both the @fieldresolver as well as @field have complexity defined , then the complexity passed to the field resolver decorator takes precedence.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move You can pass complexity as option to any... above this, place it below You can omit the complexity.

@Field({ complexity: 2})
publicField: string;

@Field({ complexity: 3 })
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe show a field with args and show how to use complexityResolver?

---
title: Query Complexity
---
A single GraphQL query can potentially generate thousands of database operations. To keep track and limit of what each graphQl operation can do , `TypeGraphQL` provides you the option of integrating with Query Complexity tools like [graphql-query-complexity](https://github.com/ivome/graphql-query-complexity).
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • fix graphQl
  • ...can potentially generate huge workload for a server, like thousands of database operations.
  • maybe we should also mention DDoS and recursive queries, like user -> posts (1000) -> user -> posts (1000)

@MichalLytek MichalLytek added Enhancement 🆕 New feature or request Community 👨‍👧 Something initiated by a community labels Sep 4, 2018
@MichalLytek
Copy link
Owner

@abhikmitra
Looks like there's no response from @ivome. Could you extract the typings and publish it to https://github.com/DefinitelyTyped/DefinitelyTyped for now? I don't want to block this PR, we can later upgrade the dependency when 0.2.x release of graphql-query-complexity will be published.

docs/complexity.md Outdated Show resolved Hide resolved
@ivome
Copy link

ivome commented Sep 6, 2018

Sorry for the late reply. I just release a new version of graphql-query-complexity with some major changes, deprecations and new features, type definitions in the package etc.
Check the docs of the repository for more information.

@MichalLytek
Copy link
Owner

@ivome Thanks! 🎉

@abhikmitra Please check new features of graphql-query-complexity v0.2.0 and align this PR to that changes, removing our local @types folder 😉

@MichalLytek

This comment has been minimized.

@abhikmitra

This comment has been minimized.

@abhikmitra
Copy link
Contributor Author

@19majkel94 I upgraded to 0.2.0

@@ -1,12 +1,12 @@
---
title: Query Complexity
---
A single graphQl query can potentially generate huge workload for a server, like thousands of database operations which can be used to cause DDoS attacks. To keep track and limit of what each graphQl operation can do , `TypeGraphQL` provides you the option of integrating with Query Complexity tools like [graphql-query-complexity](https://github.com/ivome/graphql-query-complexity).
A single GraphQl query can potentially generate huge workload for a server, like thousands of database operations which can be used to cause DDoS attacks. To keep track and limit of what each GraphQl operation can do , `TypeGraphQL` provides you the option of integrating with Query Complexity tools like [graphql-query-complexity](https://github.com/ivome/graphql-query-complexity).
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really to hard to write GraphQL? Copy and paste? 😆

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@@ -19,15 +19,15 @@ class MyObject {
@Field({ complexity: 2})
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

space - complexity: 2 })

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@@ -51,6 +51,15 @@ In next step, you need to integrate `graphql-query-complexity` with your graphql
onComplete: (complexity: number) => {
console.log("Query Complexity:", complexity);
},
estimators: [
fieldConfigEstimator(),
// Add more estimators here...
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a good suggestion - which one, directives?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

@@ -51,6 +51,15 @@ In next step, you need to integrate `graphql-query-complexity` with your graphql
onComplete: (complexity: number) => {
console.log("Query Complexity:", complexity);
},
estimators: [
fieldConfigEstimator(),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here should be comment that this one is mandatory to make it works

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// Add more estimators here...
// This will assign each field a complexity of 1 if no other estimator
// returned a value.
simpleEstimator({
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And that here we can define the default value for field not explicitly annotated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -32,7 +32,7 @@ export interface ValidateOptions {
validate?: boolean | ValidatorOptions;
}
export interface ComplexityOptions {
complexity?: Complexity;
complexity?: ((arg: ComplexityEstimatorArgs) => number) | number;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a ComplexityEstimator type that we should use - complexity function can return void to fallback to default complexity

@@ -360,7 +360,7 @@ export abstract class SchemaGenerator {
description: handler.description,
deprecationReason: handler.deprecationReason,
complexity: handler.complexity,
};
} as GraphQLFieldConfig<any, any>;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting this error if I remove this
image

@MichalLytek
Copy link
Owner

@abhikmitra
Please create our own type type Complexity = ComplexityEstimator | number and replace all of imports of this from "graphql-query-complexity" in metadata storage & others.

@abhikmitra
Copy link
Contributor Author

@19majkel94 anything else ?

@MichalLytek MichalLytek merged commit 66e7f97 into MichalLytek:master Sep 8, 2018
@MichalLytek MichalLytek added this to the 1.0.0 release milestone Sep 8, 2018
@MichalLytek
Copy link
Owner

@abhikmitra Thank you very much for your contribution! ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Community 👨‍👧 Something initiated by a community Enhancement 🆕 New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants