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(api-gateway): add meta about query in gql #8410

Open
wants to merge 4 commits 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
2 changes: 1 addition & 1 deletion docs/pages/product/apis-integrations/queries.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ the query. This is useful for creating user interfaces with
[pagination][ref-pagination-recipe].

You can make a total query by using the `total` option with the [REST
API][ref-rest-api-query-format-options]. For the SQL API, you can write an
API][ref-rest-api-query-format-options] or [GraphQL API][ref-ref-graphql-api-args]. For the SQL API, you can write an
equivalent query using the `UNION ALL` statement.

### Ungrouped query
Expand Down
2 changes: 2 additions & 0 deletions docs/pages/reference/graphql-api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ query {
- **`where` ([`RootWhereInput`](#root-where-input)):** Represents a SQL `WHERE` clause.
- **`limit` (`Int`):** A [row limit][ref-row-limit] for your query.
- **`offset` (`Int`):** The number of initial rows to be skipped for your query. The default value is `0`.
- **`total` (`Boolean`):** If set to true, Cube will run a total query and return the total number of rows as if no row limit or offset are set in the query. The default value is false.
- **`timezone` (`String`):** The [time zone][ref-time-zone] for your query. You can set the
desired time zone in the [TZ Database Name](https://en.wikipedia.org/wiki/Tz_database)
format, e.g., `America/Los_Angeles`.
- **`renewQuery` (`Boolean`):** If `renewQuery` is set to `true`, Cube will renew all `refreshKey` for queries and query results in the foreground. The default value is `false`.
- **`ungrouped` (`Boolean`):** If set to `true`, Cube will run an
[ungrouped query][ref-ungrouped-query].
- **`meta` (`Boolean`):** If set to `true`, Cube will return metadata about the query.

[ref-recipe-pagination]: /guides/recipes/queries/pagination

Expand Down
15 changes: 15 additions & 0 deletions packages/cubejs-api-gateway/src/gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
PreAggJobStatusItem,
PreAggJobStatusResponse,
SqlApiRequest, MetaResponseResultFn,
GraphQLRequestContext,
} from './types/request';
import {
CheckAuthInternalOptions,
Expand Down Expand Up @@ -259,6 +260,19 @@ class ApiGateway {
}
return graphqlHTTP({
schema,
extensions: (requestInfo) => {
const context = requestInfo.context as GraphQLRequestContext;
const { meta } = context;

if (!meta) {
return undefined;
}

return {
meta,
};
},

context: {
req,
apiGateway: this
Expand Down Expand Up @@ -1525,6 +1539,7 @@ class ApiGateway {
return res;
})
);
// TODO: Add total for all queries
response.total = normalizedQuery.total
? Number(total.data[0][QueryAlias.TOTAL_COUNT])
: undefined;
Expand Down
36 changes: 25 additions & 11 deletions packages/cubejs-api-gateway/src/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ import {
} from 'graphql-scalars';

import gql from 'graphql-tag';
import type { Cube, LoadResponseResult } from '@cubejs-client/core';
import { QueryType, MemberType } from './types/enums';
import { GraphQLRequestContext } from './types/request';

const DateTimeScalar = asNexusMethod(DateTimeResolver, 'date');

Expand Down Expand Up @@ -270,11 +272,11 @@ function whereArgToQueryFilters(
metaConfig: any[] = []
) {
const queryFilters: any[] = [];

Object.keys(whereArg).forEach((key) => {
const cubeExists = metaConfig.find((cube) => cube.config.name === key);
const normalizedKey = cubeExists ? key : capitalize(key);

if (['OR', 'AND'].includes(key)) {
queryFilters.push({
[key.toLowerCase()]: whereArg[key].reduce(
Expand Down Expand Up @@ -363,7 +365,7 @@ function parseDates(result: any) {
}

export function getJsonQuery(metaConfig: any, args: Record<string, any>, infos: GraphQLResolveInfo) {
const { where, limit, offset, timezone, orderBy, renewQuery, ungrouped } = args;
const { where, limit, offset, timezone, orderBy, renewQuery, ungrouped, total } = args;

const measures: string[] = [];
const dimensions: string[] = [];
Expand All @@ -385,7 +387,7 @@ export function getJsonQuery(metaConfig: any, args: Record<string, any>, infos:

getFieldNodeChildren(infos.fieldNodes[0], infos).forEach(cubeNode => {
const cubeExists = metaConfig.find((cube) => cube.config.name === cubeNode.name.value);

const cubeName = cubeExists ? (cubeNode.name.value) : capitalize(cubeNode.name.value);
const orderByArg = getArgumentValue(cubeNode, 'orderBy', infos.variableValues);
// todo: throw if both RootOrderByInput and [Cube]OrderByInput provided
Expand Down Expand Up @@ -458,6 +460,7 @@ export function getJsonQuery(metaConfig: any, args: Record<string, any>, infos:
...(Object.keys(order).length && { order }),
...(limit && { limit }),
...(offset && { offset }),
...(total && { total }),
...(timezone && { timezone }),
...(filters.length && { filters }),
...(renewQuery && { renewQuery }),
Expand All @@ -471,7 +474,7 @@ export function getJsonQueryFromGraphQLQuery(query: string, metaConfig: any, var
const operation: any = ast.definitions.find(
({ kind }) => kind === 'OperationDefinition'
);

const fieldNodes = operation?.selectionSet.selections;

let args = {};
Expand All @@ -487,11 +490,11 @@ export function getJsonQueryFromGraphQLQuery(query: string, metaConfig: any, var
variableValues,
fragments: {},
};

return getJsonQuery(metaConfig, args, resolveInfo);
}

export function makeSchema(metaConfig: any): GraphQLSchema {
export function makeSchema(metaConfig: { config: Cube }[]): GraphQLSchema {
const types: any[] = [
DateTimeScalar,
FloatFilter,
Expand All @@ -505,7 +508,7 @@ export function makeSchema(metaConfig: any): GraphQLSchema {
if (cube.public === false) {
return false;
}

return ([...cube.config.measures, ...cube.config.dimensions].filter((member) => member.isVisible)).length > 0;
}

Expand Down Expand Up @@ -639,30 +642,41 @@ export function makeSchema(metaConfig: any): GraphQLSchema {
offset: intArg(),
timezone: stringArg(),
renewQuery: booleanArg(),
total: booleanArg(),
meta: booleanArg(),
ungrouped: booleanArg(),
orderBy: arg({
type: 'RootOrderByInput'
}),
},
resolve: async (_, args, { req, apiGateway }, info) => {
resolve: async (_, args, ctx: GraphQLRequestContext, info) => {
const { req, apiGateway } = ctx;
const query = getJsonQuery(metaConfig, args, info);

const results = await new Promise<any>((resolve, reject) => {
const results = await new Promise<LoadResponseResult<any>>((resolve, reject) => {
apiGateway.load({
query,
queryType: QueryType.REGULAR_QUERY,
context: req.context,
res: (message) => {
if (message.error) {
if ('error' in message && message.error) {
reject(new Error(message.error));
}
// @ts-ignore
resolve(message);
},
apiType: 'graphql',
}).catch(reject);
});

parseDates(results);

if (args.meta) {
ctx.meta = { ...(ctx.meta || {}),
[info.path.key]: R.pick([
...(args.total ? ['total'] : []),
'dbType', 'extDbType', 'external', 'lastRefreshTime', 'slowQuery'], results) };
} else if (args.total) ctx.meta = { ...(ctx.meta || {}), [info.path.key]: R.pick(['total'], results) };

return results.data.map(entry => R.toPairs(entry)
.reduce((res, pair) => {
Expand Down
13 changes: 12 additions & 1 deletion packages/cubejs-api-gateway/src/types/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
*/

import type { Request as ExpressRequest } from 'express';
import type { ApiGateway } from 'src/gateway';
import { RequestType, ApiType, ResultType } from './strings';
import { Query } from './query';
import type { QueryType } from './enums';

/**
* Network request context interface.
Expand Down Expand Up @@ -60,6 +62,14 @@ interface Request extends ExpressRequest {
authInfo?: any,
}

interface GraphQLRequestContext {
req: Request & {
context: ExtendedRequestContext;
},
meta?: object,
apiGateway: ApiGateway
}

/**
* Function that should provides basic query conversion mechanic.
* Used as a part of a main configuration object of the server-core
Expand Down Expand Up @@ -123,7 +133,7 @@ type BaseRequest = {
*/
type QueryRequest = BaseRequest & {
query: Record<string, any> | Record<string, any>[];
queryType?: RequestType;
queryType?: RequestType | QueryType;
apiType?: ApiType;
resType?: ResultType
memberToAlias?: Record<string, string>;
Expand Down Expand Up @@ -207,6 +217,7 @@ export {
RequestContext,
RequestExtension,
ExtendedRequestContext,
GraphQLRequestContext,
Request,
SqlApiRequest,
QueryRewriteFn,
Expand Down
4 changes: 4 additions & 0 deletions packages/cubejs-api-gateway/test/graphql.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ describe('GraphQL Schema', () => {
const app = express();

app.use('/graphql', jsonParser, (req, res) => {
// @ts-ignore
const schema = makeSchema(metaConfig);

return graphqlHTTP({
Expand All @@ -174,6 +175,7 @@ describe('GraphQL Schema', () => {
const GRAPHQL_QUERIES_PATH = `${process.cwd()}/test/graphql-queries/base.gql`;

app.use('/graphql', jsonParser, (req, res) => {
// @ts-ignore
const schema = makeSchema(metaConfig);

return graphqlHTTP({
Expand All @@ -196,6 +198,7 @@ describe('GraphQL Schema', () => {
});

test('should make valid schema', () => {
// @ts-ignore
const schema = makeSchema(metaConfig);
expectValidSchema(schema);
});
Expand Down Expand Up @@ -226,6 +229,7 @@ describe('GraphQL Schema', () => {
const app = express();

app.use('/graphql', jsonParser, (req, res) => {
// @ts-ignore
const schema = makeSchema(metaConfigSnakeCase);

return graphqlHTTP({
Expand Down