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(hydra-cli): add subclass filtering support for interfaces #442

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -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<E extends BaseModel> extends BaseService<E> {
buildFindQueryWithParams<W extends WhereInput>(
where: WhereExpression = {},
Expand Down Expand Up @@ -160,6 +164,13 @@ export class WarthogBaseService<E extends BaseModel> extends BaseService<E> {
}
}

/**
*
* @param orderBy clause(s) formatted as <attr1>_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<T>(
orderBy: string | string[] | undefined,
qb: SelectQueryBuilder<T>,
Expand All @@ -177,6 +188,11 @@ export function addOrderBy<T>(
return qb;
}

/**
*
* @param orderBy list of orderby's or a single orderby, formatted as <field1>_ASC or <field2>_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')[]] {
Expand All @@ -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 [];
Expand All @@ -209,3 +230,4 @@ export function orderByFields(orderBy: string | string[] | undefined): string[]
}
return orderBy.map((o) => o.toString().split('_')[0]);
}

24 changes: 21 additions & 3 deletions packages/hydra-cli/src/templates/interfaces/resolver.ts.mst
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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}}[]> {
Expand Down
18 changes: 15 additions & 3 deletions packages/hydra-cli/src/templates/interfaces/service.ts.mst
Original file line number Diff line number Diff line change
@@ -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}}
Expand Down Expand Up @@ -53,24 +53,36 @@ 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,
true
)
);

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<unknown>[] = types.map(
(t) =>
(this.typeToService[t]
.buildFindQueryWithParams(
<any>where,
<any>getWhereForSubclass(t),
undefined,
undefined,
commonFields,
Expand Down
23 changes: 23 additions & 0 deletions packages/hydra-e2e-tests/test/e2e/api/graphql-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
12 changes: 11 additions & 1 deletion packages/hydra-e2e-tests/test/e2e/interfaces-e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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')
})
})
44 changes: 42 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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=

[email protected]:
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"
Expand Down Expand Up @@ -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"
Expand All @@ -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==
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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==
Expand Down