From e2ed015e3d99ae6f350dc83b57131f67d41052a3 Mon Sep 17 00:00:00 2001 From: Dmit Date: Wed, 30 Jun 2021 19:03:49 +0300 Subject: [PATCH] feat(hydra-cli): add subclass filtering support for interfaces affects: @joystream/hydra-cli, @joystream/hydra-e2e-tests this lays out a ground work for #438 Solution 1. This commit adds support for primitive fields on subclasses and adds an e2e-test --- .../src/WarthogBaseService.ts.mst | 22 ++++++++++ .../src/templates/interfaces/resolver.ts.mst | 24 ++++++++-- .../src/templates/interfaces/service.ts.mst | 18 ++++++-- .../test/e2e/api/graphql-queries.ts | 23 ++++++++++ .../test/e2e/interfaces-e2e.test.ts | 12 ++++- yarn.lock | 44 ++++++++++++++++++- 6 files changed, 134 insertions(+), 9 deletions(-) diff --git a/packages/hydra-cli/src/templates/graphql-server/src/WarthogBaseService.ts.mst b/packages/hydra-cli/src/templates/graphql-server/src/WarthogBaseService.ts.mst index 88d06ca2a..02ec24e74 100644 --- a/packages/hydra-cli/src/templates/graphql-server/src/WarthogBaseService.ts.mst +++ b/packages/hydra-cli/src/templates/graphql-server/src/WarthogBaseService.ts.mst @@ -10,6 +10,10 @@ interface WhereFilterAttributes { [key: string]: string | number | null; } +/** + * This code is by and large taken from the Warthog sources + * and should be incorporated into warthog fork + */ export class WarthogBaseService extends BaseService { buildFindQueryWithParams( where: WhereExpression = {}, @@ -160,6 +164,13 @@ export class WarthogBaseService extends BaseService { } } +/** + * + * @param orderBy clause(s) formatted as _DESC + * @param qb query builder + * @param attrToDBColumn a function that maps the attribute to the db column name + * @returns query builder with the order by clause + */ export function addOrderBy( orderBy: string | string[] | undefined, qb: SelectQueryBuilder, @@ -177,6 +188,11 @@ export function addOrderBy( return qb; } +/** + * + * @param orderBy list of orderby's or a single orderby, formatted as _ASC or _DESC + * @returns an array of order by clauses suitable for typeorm: [[field1, field2], ['asc', 'desc']] + */ export function parseOrderBy( orderBy: string | string[] | undefined ): [string[], ('asc' | 'desc')[]] { @@ -200,6 +216,11 @@ export function parseOrderBy( return [attrs, directions]; } +/** + * + * @param orderBy list of orderBy clauses + * @returns list of fields used for the ordering + */ export function orderByFields(orderBy: string | string[] | undefined): string[] { if (orderBy === undefined) { return []; @@ -209,3 +230,4 @@ export function orderByFields(orderBy: string | string[] | undefined): string[] } return orderBy.map((o) => o.toString().split('_')[0]); } + diff --git a/packages/hydra-cli/src/templates/interfaces/resolver.ts.mst b/packages/hydra-cli/src/templates/interfaces/resolver.ts.mst index 9134a61b9..831c03bee 100644 --- a/packages/hydra-cli/src/templates/interfaces/resolver.ts.mst +++ b/packages/hydra-cli/src/templates/interfaces/resolver.ts.mst @@ -1,4 +1,4 @@ -import { Arg, Args, Mutation, Query, Resolver, Info } from 'type-graphql'; +import { Arg, Args, ArgsType, InputType, Mutation, Query, Resolver, Info, Field } from 'type-graphql'; import { Inject } from 'typedi'; import { Fields, StandardDeleteResponse, UserId } from '@joystream/warthog'; import { GraphQLResolveInfo } from 'graphql'; @@ -9,19 +9,37 @@ import { {{className}}UpdateArgs, {{className}}WhereArgs, {{className}}WhereInput, - {{className}}WhereUniqueInput + {{className}}WhereUniqueInput, + {{#subclasses}} + {{className}}WhereInput, + {{/subclasses}} } from '{{{generatedFolderRelPath}}}'; import { {{className}} } from './{{kebabName}}.model'; import { {{className}}Service } from './{{kebabName}}.service'; +@InputType() +export class {{className}}WithSubclassesWhereInput extends {{className}}WhereInput { + {{#subclasses}} + @Field(() => {{className}}WhereInput, { nullable: true }) + {{camelName}}?: {{className}}WhereInput; + {{/subclasses}} +} + +@ArgsType() +export class {{className}}WithSubclassesWhereArgs extends {{className}}WhereArgs { + @Field(() => {{className}}WithSubclassesWhereInput, { nullable: true }) + where?: {{className}}WithSubclassesWhereInput; +} + + @Resolver() export class {{className}}Resolver { constructor(@Inject('{{className}}Service') public readonly service: {{className}}Service) {} @Query(() => [{{className}}]) async {{camelNamePlural}}( - @Args() { where, orderBy, limit, offset }: {{className}}WhereArgs, + @Args() { where, orderBy, limit, offset }: {{className}}WithSubclassesWhereArgs, @Fields() fields: string[], @Info() info?: GraphQLResolveInfo | string ): Promise<{{className}}[]> { diff --git a/packages/hydra-cli/src/templates/interfaces/service.ts.mst b/packages/hydra-cli/src/templates/interfaces/service.ts.mst index 5fbaf9ec5..a62ba795d 100644 --- a/packages/hydra-cli/src/templates/interfaces/service.ts.mst +++ b/packages/hydra-cli/src/templates/interfaces/service.ts.mst @@ -1,7 +1,7 @@ import { Service, Inject } from 'typedi'; import { getManager, SelectQueryBuilder, ObjectLiteral } from 'typeorm'; import { BaseModel, WhereInput } from '@joystream/warthog'; -import { snakeCase, camelCase, uniq, orderBy } from 'lodash'; +import { snakeCase, camelCase, uniq, orderBy, pickBy } from 'lodash'; import { GraphQLResolveInfo } from 'graphql'; {{#subclasses}} @@ -53,12 +53,13 @@ export class {{className}}Service { const { type_in, type_eq } = where; const types: string[] = (type_eq ? [type_eq] - : type_in || [ {{#subclasses}} {{className}}, {{/subclasses}} ] + : type_in || [ {{#subclasses}} '{{className}}', {{/subclasses}} ] ).map((t: EventTypeOptions) => t.toString()); delete where.type_in; delete where.type_eq; // take fields that are present in all implemetations + console.log(`Types: ${JSON.stringify(types)}`) const commonFields = fields.filter((f) => types.reduce( (hasField: boolean, t) => this.typeToService[t].columnMap[f] !== undefined && hasField, @@ -66,11 +67,22 @@ export class {{className}}Service { ) ); + const getWhereForSubclass = (subclass: string) => { + const colMap = this.typeToService[subclass].columnMap + // TODO: is this filtering good enough? + const commonWhere = pickBy(where, (v, k) => k.includes('_') && colMap[k.split('_')[0]] !== undefined) + const subclassWhere = where[camelCase(subclass)] || {} + return { + ...commonWhere, + ...subclassWhere + } + } + const queries: SelectQueryBuilder[] = types.map( (t) => (this.typeToService[t] .buildFindQueryWithParams( - where, + getWhereForSubclass(t), undefined, undefined, commonFields, diff --git a/packages/hydra-e2e-tests/test/e2e/api/graphql-queries.ts b/packages/hydra-e2e-tests/test/e2e/api/graphql-queries.ts index deaf1bf64..72a18eee5 100644 --- a/packages/hydra-e2e-tests/test/e2e/api/graphql-queries.ts +++ b/packages/hydra-e2e-tests/test/e2e/api/graphql-queries.ts @@ -151,6 +151,29 @@ export const VARIANT_FILTER_MISREABLE_ACCOUNTS = gql` } ` +export const EVENT_SUBCLASSES_QUERY = gql` + query { + events( + where: { + eventA: { field1_eq: "field1" } + eventB: { field2_eq: "field2" } + eventC: { field3_eq: "field3" } + } + orderBy: [indexInBlock_DESC, network_DESC] + ) { + ... on EventA { + field1 + } + ... on EventB { + field2 + } + ... on EventC { + field3 + } + } + } +` + export const EVENT_INTERFACE_QUERY = gql` query { events( diff --git a/packages/hydra-e2e-tests/test/e2e/interfaces-e2e.test.ts b/packages/hydra-e2e-tests/test/e2e/interfaces-e2e.test.ts index 046b4d4ef..5a7cff6d0 100644 --- a/packages/hydra-e2e-tests/test/e2e/interfaces-e2e.test.ts +++ b/packages/hydra-e2e-tests/test/e2e/interfaces-e2e.test.ts @@ -5,7 +5,10 @@ import { getProcessorStatus, queryInterfacesByEnum, } from './api/processor-api' -import { EVENT_INTERFACE_QUERY } from './api/graphql-queries' +import { + EVENT_INTERFACE_QUERY, + EVENT_SUBCLASSES_QUERY, +} from './api/graphql-queries' describe('end-to-end interfaces tests', () => { before(async () => { @@ -52,4 +55,11 @@ describe('end-to-end interfaces tests', () => { const { events } = await queryInterfacesByEnum() expect(events.length).to.be.equal(1, 'shoud find an interface by type') }) + + it('executes a flat interface query with fragments', async () => { + const result = await getGQLClient().request<{ + events: any[] + }>(EVENT_SUBCLASSES_QUERY) + expect(result.events.length).to.be.equal(3, 'should find three events') + }) }) diff --git a/yarn.lock b/yarn.lock index fa803c930..2a0863232 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6020,6 +6020,11 @@ dotenv@*, dotenv@^8.1.0, dotenv@^8.2.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== +dotenv@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + dotenvi@^0.9.0: version "0.9.1" resolved "https://registry.yarnpkg.com/dotenvi/-/dotenvi-0.9.1.tgz#e280012ee9d201a0c57cb1f6e43559603b6f0fb4" @@ -12031,11 +12036,25 @@ pg-connection-string@^2.4.0, pg-connection-string@^2.5.0: resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== +pg-format@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/pg-format/-/pg-format-1.0.4.tgz#27734236c2ad3f4e5064915a59334e20040a828e" + integrity sha1-J3NCNsKtP05QZJFaWTNOIAQKgo4= + pg-int8@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== +pg-listen@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/pg-listen/-/pg-listen-1.7.0.tgz#5a5c68a1cabf88d2b78ed9cf133667f597d3b860" + integrity sha512-MKDwKLm4ryhy7iq1yw1K1MvUzBdTkaT16HZToddX9QaT8XSdt3Kins5mYH6DLECGFzFWG09VdXvWOIYogjXrsg== + dependencies: + debug "^4.1.1" + pg-format "^1.0.4" + typed-emitter "^0.1.0" + pg-packet-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pg-packet-stream/-/pg-packet-stream-1.1.0.tgz#e45c3ae678b901a2873af1e17b92d787962ef914" @@ -14375,6 +14394,22 @@ ts-node-dev@^1.0.0-pre.40, ts-node-dev@^1.0.0-pre.63: ts-node "^9.0.0" tsconfig "^7.0.0" +ts-node-dev@^1.0.0-pre.60: + version "1.1.7" + resolved "https://registry.yarnpkg.com/ts-node-dev/-/ts-node-dev-1.1.7.tgz#f157c25235e86a9e8ce3470b1ec04b89bacd90ff" + integrity sha512-/YvByJdIw/p88RXmaRB3Kkk+PiUP7g/EAbBvQjDIG+kkm0CMvhdHSB21yEiws22Uls4uFAfCiuEZM4929yjWjg== + dependencies: + chokidar "^3.5.1" + dynamic-dedupe "^0.3.0" + minimist "^1.2.5" + mkdirp "^1.0.4" + resolve "^1.0.0" + rimraf "^2.6.1" + source-map-support "^0.5.12" + tree-kill "^1.2.2" + ts-node "^9.0.0" + tsconfig "^7.0.0" + ts-node@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-7.0.1.tgz#9562dc2d1e6d248d24bc55f773e3f614337d9baf" @@ -14389,7 +14424,7 @@ ts-node@^7.0.1: source-map-support "^0.5.6" yn "^2.0.0" -ts-node@^8: +ts-node@^8, ts-node@^8.10: version "8.10.2" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d" integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA== @@ -14568,6 +14603,11 @@ type@^2.0.0: resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== +typed-emitter@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/typed-emitter/-/typed-emitter-0.1.0.tgz#ca532f100ccbf850e3a73b8ebf43d43e4f1f3849" + integrity sha512-Tfay0l6gJMP5rkil8CzGbLthukn+9BN/VXWcABVFPjOoelJ+koW8BuPZYk+h/L+lEeIp1fSzVRiWRPIjKVjPdg== + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -14618,7 +14658,7 @@ typeorm@^0.2.25, typeorm@^0.2.26: yargs "^16.2.0" zen-observable-ts "^1.0.0" -typeorm@^0.2.32: +typeorm@^0.2.31, typeorm@^0.2.32: version "0.2.34" resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.34.tgz#637b3cec2de54ee7f423012b813a2022c0aacc8b" integrity sha512-FZAeEGGdSGq7uTH3FWRQq67JjKu0mgANsSZ04j3kvDYNgy9KwBl/6RFgMVgiSgjf7Rqd7NrhC2KxVT7I80qf7w==