diff --git a/src/adapter/index.ts b/src/adapter/index.ts deleted file mode 100644 index 952f776..0000000 --- a/src/adapter/index.ts +++ /dev/null @@ -1,437 +0,0 @@ -import * as gql from 'graphql'; -import _ from 'lodash'; -import {DateTime} from 'luxon'; -import {singular} from 'pluralize'; -import {Writable} from 'ts-essentials'; -import VError from 'verror'; - -import {FarosClient} from '../client'; -import { - isEmbeddedObjectListType, - isEmbeddedObjectType, - isModelQuery, - isObjectListType, - METADATA, - NODES, - paginatedQueryV2, - REFRESHED_AT -} from '../graphql/graphql'; -import { - asLeafValueType, - FieldPaths, - isLeafType, - isLeafValue, - isNestedValue, - isPrimitiveListType, - isTypedArray, - LeafValueType, -} from './types'; - -export {FieldPaths} from './types'; - -/** - * Converts a V1 query namespace, specified as a model namespace and model name, - * into a V2 query namespace. - * - * For example, queryNamespace('vcs', 'commits') returns 'vcs_Commit'. - */ -function queryNamespace(modelNamespace: string, model: string): string { - let modelName; - if (modelNamespace === 'faros' && model.endsWith('Options')) { - // Don't singularize options models - modelName = model; - } else { - modelName = singular(model); - } - return `${modelNamespace}_${_.upperFirst(modelName)}`; -} - -/** - * Returns the V2 field for an embedded V1 field. - * - * For example, embeddedName('env', 'category') returns 'envCategory'. - */ -function embeddedName(parentName: string, fieldName: string): string { - return `${parentName}${_.upperFirst(fieldName)}`; -} - -/** Should this V1 field be omitted from a V2 query? */ -function omitField(name: string, type: gql.GraphQLOutputType): boolean { - return name === METADATA || name === NODES || isEmbeddedObjectType(type); -} - -/** Extracts nested fields with optional field rename */ -function nestedFields( - field: gql.FieldNode, - rename?: (name: string) => string -): gql.FieldNode[] { - const nestedFields: gql.FieldNode[] = []; - const selections = field.selectionSet?.selections ?? []; - for (const selection of selections) { - if (selection.kind === 'Field') { - const name = selection.name.value; - nestedFields.push({ - ...selection, - name: { - kind: gql.Kind.NAME, - value: rename ? rename(name) : name - } - }); - } - } - return nestedFields; -} - -/** - * Returns a mapping from V2 to V1 field paths. If a field is an object list, - * then its path will point to a nested structure that contains paths that - * are relative to it. - * - * Note: In V2, fields inside embedded object lists are stored identically to - * those in V1. The "mirrorPaths" boolean argument is true whenever traversal is - * happening inside an emdedded object list. This causes the V1 and V2 paths to - * be set to the same values, i.e., they'll be "mirrored". - */ -export function getFieldPaths( - ast: gql.ASTNode, - typeInfo: gql.TypeInfo, - mirrorPaths = false -): FieldPaths { - const fieldStack: string[] = []; - const newFieldStack: string[] = []; - function pushField(name: string, type: gql.GraphQLOutputType): void { - fieldStack.push(name); - if (mirrorPaths || !omitField(name, type)) { - newFieldStack.push(name); - } - } - function popField(name: string): void { - fieldStack.pop(); - if (mirrorPaths || _.last(newFieldStack) === name) { - newFieldStack.pop(); - } - } - - const fieldPaths: Writable = {}; - gql.visit(ast, gql.visitWithTypeInfo(typeInfo, { - Field: { - enter(node) { - let type = typeInfo.getType(); - const parentType = typeInfo.getParentType(); - if (gql.isNonNullType(type)) { - type = type.ofType; - } - if (!type) { - throw new VError( - 'unable to determine type of field: %s', - fieldStack.join('.') - ); - } - - const fieldName = node.name.value; - pushField(fieldName, type); - if (isModelQuery(parentType, type)) { - // Convert the V1 query namespace to a V2 query namespace - // For example: vcs { commits { ...} } => vcs_Commit { ... } - newFieldStack.shift(); - newFieldStack.shift(); - const [namespace, name] = fieldStack; - newFieldStack.unshift(queryNamespace(namespace, name)); - } else if (isObjectListType(type)) { - // Recursive call to next object list - const nextNode = node.selectionSet; - if (nextNode) { - const newFieldPath = newFieldStack.join('.'); - const fieldPath = fieldStack.join('.'); - typeInfo.enter(nextNode); - fieldPaths[newFieldPath] = { - path: fieldPath, - nestedPaths: getFieldPaths( - nextNode, - typeInfo, - mirrorPaths || isEmbeddedObjectType(type.ofType) - ) - }; - typeInfo.leave(nextNode); - popField(fieldName); - return false; - } - } else if (isLeafType(type)) { - const fieldPath = fieldStack.join('.'); - let newFieldPath = newFieldStack.join('.'); - // Timestamps in v1 are always stringified epoch millis, except when - // they're inside embedded object lists. In that case, they're stored - // as epoch millis. - const stringifyTimestamps = !mirrorPaths; - let fieldType = asLeafValueType(type, stringifyTimestamps); - if (mirrorPaths) { - fieldPaths[fieldPath] = {path: fieldPath, type: fieldType}; - return undefined; - } else if (isEmbeddedObjectType(parentType)) { - const parentName = fieldStack[fieldStack.length - 2]; - if (parentName === METADATA) { - if (fieldName === REFRESHED_AT) { - // Hack: While V1 serializes "refreshedAt" the same as other - // timestamps (epoch millis string), it types it as a string. - // In V2, it's stored and typed like every other timestamp. - // We force conversion from ISO 8601 string => epoch millis - // string by overriding the type from string to timestamp. - fieldType = 'epoch_millis_string'; - } - } else { - // Prefix the last field name with the embedded field name - newFieldPath = [ - ...newFieldStack.slice(0, -1), - embeddedName(parentName, fieldName) - ].join('.'); - } - } - fieldPaths[newFieldPath] = {path: fieldPath, type: fieldType}; - } - return undefined; - }, - leave(node) { - popField(node.name.value); - } - } - })); - return fieldPaths; -} - -/** - * Converts a V1 query into a V2 query using the following rules: - * - * 1. Replaces the first two field selections, i.e., the model namespace and - * name, are replaced by a single, combined selection. Example: - * - * vcs { commits { sha createdAt } } => vcs_Commit { sha createdAt } - * - * 2. Flattens the selection set of each "nodes" and "metadata" field into the - * selection set of its parent. Examples: - * - * commits { nodes { sha createdAt } } => commits { sha createdAt } - * commit { metadata { refreshedAt } } => commit { refreshedAt } - * - * 3. Flattens the selection set of each embedded field into the selection set - * of its parent, prefixing names of nested fields with the parent name. - * Example: - * - * deployment { env { category } } => deployment { envCategory } - * - * 4. Removes nested fields from embedded object lists since these are stored - * as JSONB arrays in V2 and cannot be extracted. Example: - * - * task { additionalFields { name value } } => task { additionalFields } - * - * 5. Renames the "first" field argument to "limit". Example: - * - * deployments(first: 1) { uid } => deployments(limit: 1) { uid } - */ -export function asV2AST(ast: gql.ASTNode, typeInfo: gql.TypeInfo): gql.ASTNode { - return gql.visit(ast, gql.visitWithTypeInfo(typeInfo, { - Field: { - // Handles rule (1) - leave(node, key, parent, path, ancestors) { - const grandparent = ancestors[ancestors.length - 2]; - if (isTypedArray(grandparent)) { - return undefined; - } else if (grandparent.kind !== 'OperationDefinition') { - return undefined; - } else if (grandparent.operation !== 'query') { - throw new Error('only queries can be converted'); - } - - const modelField = node.selectionSet?.selections?.[0]; - if (!modelField || modelField.kind !== 'Field') { - throw new Error('query does not select a model'); - } - const modelNamespace = node.name.value; - const model = modelField.name.value; - return { - ...modelField, - name: { - kind: gql.Kind.NAME, - value: queryNamespace(modelNamespace, model) - }, - }; - } - }, - // Handles rules (2), (3) and (4) - SelectionSet: { - leave(node) { - const newSelections: gql.SelectionNode[] = []; - for (const selection of node.selections) { - typeInfo.enter(selection); - const selectionType = typeInfo.getType(); - if (selection.kind === 'Field') { - if ( - selection.name.value === NODES || - selection.name.value === METADATA - ) { - // Rule (2): flatten nodes and metadata fields - newSelections.push(...nestedFields(selection)); - } else if (isEmbeddedObjectType(selectionType)) { - // Rule (3): flatten embedded fields and rename them - const prefix = selection.name.value; - newSelections.push(...nestedFields( - selection, - (name) => embeddedName(prefix, name) - )); - } else if (isEmbeddedObjectListType(selectionType)) { - // Rule (4): omit fields from embedded object lists - newSelections.push(_.omit(selection, 'selectionSet')); - } else { - // Otherwise, leave the nested fields alone - newSelections.push(selection); - } - } - typeInfo.leave(selection); - } - return {kind: gql.Kind.SELECTION_SET, selections: newSelections}; - } - }, - // Handles rule (5) - Argument(node) { - if (node.name.value === 'first') { - return {...node, name: {kind: gql.Kind.NAME, value: 'limit'}}; - } - return undefined; - } - })); -} - -/** Shim that retrieves data from a V2 graph using a V1 query */ -export class QueryAdapter { - constructor( - private readonly faros: FarosClient, - private readonly v1Schema: gql.GraphQLSchema - ) {} - - /** Converts a V2 field value into a V1 field value */ - private v1Value(v2Value: any, type: LeafValueType): any { - if (isPrimitiveListType(type)) { - if (Array.isArray(v2Value)) { - // Recursively convert entries - const v1List: any[] = []; - for (const v2EntryValue of v2Value) { - v1List.push(this.v1Value(v2EntryValue, type.entryType)); - } - return v1List; - } - return v2Value; - } else if (_.isNil(v2Value)) { - return v2Value; - } - - if (type === 'float' || type === 'double') { - if (_.isString(v2Value)) { - const double = parseFloat(v2Value); - if (!isNaN(double)) { - return double; - } - } else if (_.isNumber(v2Value)) { - return v2Value; - } - throw new VError('invalid double: %s', v2Value); - } else if (type === 'long') { - // Long may be a string or number in V2 - if (_.isString(v2Value)) { - if (/^-?\d+$/.test(v2Value)) { - return v2Value; - } - } else if (_.isNumber(v2Value)) { - return `${v2Value}`; - } - throw new VError('invalid long: %s', v2Value); - } else if (type === 'epoch_millis' || type === 'epoch_millis_string') { - const stringify = type === 'epoch_millis_string'; - if (_.isString(v2Value)) { - const millis = DateTime.fromISO(v2Value).toMillis(); - if (!isNaN(millis)) { - return stringify ? `${millis}` : millis; - } - } else if (_.isNumber(v2Value)) { - return stringify ? `${v2Value}` : v2Value; - } - throw new VError('invalid timestamp: %s', v2Value); - } - return v2Value; - } - - /** Converts a V2 node into a V1 node */ - private v1Node(v2Node: any, fieldPaths: FieldPaths): any { - const v1Node: any = {}; - for (const [v2Path, v1Path] of Object.entries(fieldPaths)) { - if (isLeafValue(v1Path)) { - try { - const v1Value = this.v1Value(_.get(v2Node, v2Path), v1Path.type); - _.set(v1Node, v1Path.path, v1Value); - } catch (err: any) { - throw new VError( - err, - 'failed to convert value in v2 field \'%s\' into value in v1 ' + - 'field \'%s\' of type \'%s\'', - v2Path, - v1Path.path, - isPrimitiveListType(v1Path.type) - ? `[${v1Path.type.entryType}]` - : v1Path.type - ); - } - } else { - const nestedV1Nodes: any[] = []; - const nestedV2Nodes = _.get(v2Node, v2Path); - const nestedPaths = v1Path.nestedPaths; - if (Array.isArray(nestedV2Nodes)) { - for (const nestedV2Node of nestedV2Nodes) { - const nestedV1Node = this.v1Node(nestedV2Node, nestedPaths); - if (nestedV1Node) { - nestedV1Nodes.push(nestedV1Node); - } - } - } - _.set(v1Node, v1Path.path, nestedV1Nodes); - } - } - return v1Node; - } - - /** Returns paths relative to the initial nodes path */ - private nodePaths(v1AST: gql.ASTNode, v1TypeInfo: gql.TypeInfo): FieldPaths { - const fieldPaths = getFieldPaths(v1AST, v1TypeInfo); - const [pathValue] = Object.values(fieldPaths); - if (isNestedValue(pathValue)) { - return pathValue.nestedPaths; - } - throw new VError('invalid path value: %s', pathValue); - } - - nodes( - graph: string, - v1Query: string, - pageSize = 100, - args: Map = new Map(), - postProcessV2Query: (v2Query: string) => string = _.identity - ): AsyncIterable { - const v1AST = gql.parse(v1Query); - const v1TypeInfo = new gql.TypeInfo(this.v1Schema); - const nodePaths = this.nodePaths(v1AST, v1TypeInfo); - const v2Query = postProcessV2Query(gql.print(asV2AST(v1AST, v1TypeInfo))); - const v2Nodes = this.faros.nodeIterable( - graph, - v2Query, - pageSize, - paginatedQueryV2, - args - ); - // eslint-disable-next-line @typescript-eslint/no-this-alias - const self = this; - return { - async *[Symbol.asyncIterator](): AsyncIterator { - for await (const v2Node of v2Nodes) { - yield self.v1Node(v2Node, nodePaths); - } - } - }; - } -} diff --git a/src/adapter/types.ts b/src/adapter/types.ts deleted file mode 100644 index 58bb180..0000000 --- a/src/adapter/types.ts +++ /dev/null @@ -1,124 +0,0 @@ -import * as gql from 'graphql'; -import _ from 'lodash'; -import VError from 'verror'; - -/** Typescript needs a hint to correctly narrow readonly arrays */ -export function isTypedArray( - arg: T | ReadonlyArray -): arg is ReadonlyArray { - return Array.isArray(arg); -} - -// Store as a const list so we can do a containment check and -// and also derive a union type of string literals from it -const primitiveTypes = [ - 'boolean', - 'double', - 'float', - 'int', - 'long', - 'string', - 'epoch_millis', - 'epoch_millis_string', -] as const; -type PrimitiveType = typeof primitiveTypes[number]; - -export function isPrimitiveType(val: any): val is PrimitiveType { - return _.isString(val) && _.includes(primitiveTypes, val); -} - -interface PrimitiveListType {readonly entryType: PrimitiveType} - -export function isPrimitiveListType(val: any): val is PrimitiveListType { - return _.isPlainObject(val) && isPrimitiveType(val.entryType); -} - -export type LeafValueType = PrimitiveType | PrimitiveListType; - -interface LeafValue { - readonly path: string; - readonly type: LeafValueType; -} - -interface NestedValue { - readonly path: string; - readonly nestedPaths: FieldPaths; -} - -export function isLeafValue(val: any): val is LeafValue { - return ( - _.isPlainObject(val) && - 'path' in val && _.isString(val.path) && - 'type' in val && ( - isPrimitiveType(val.type) || - isPrimitiveListType(val.type) - ) - ); -} - -export function isNestedValue(val: any): val is NestedValue { - return ( - 'path' in val && _.isString(val.path) && - 'nestedPaths' in val && _.isPlainObject(val.nestedPaths) - ); -} - -type PathValue = LeafValue | NestedValue; - -export interface FieldPaths { - readonly [path: string | symbol]: PathValue; -} - -export function asLeafValueType( - type: gql.GraphQLType, - stringifyTimestamps = true -): LeafValueType { - function asPrimitiveType(type: gql.GraphQLLeafType): PrimitiveType { - if (gql.isEnumType(type) || type.name === 'String' || type.name === 'ID') { - return 'string'; - } else if (type.name === 'Boolean') { - return 'boolean'; - } else if (type.name === 'Double') { - return 'double'; - } else if (type.name === 'Float') { - return 'float'; - } else if (type.name === 'Int') { - return 'int'; - } else if (type.name === 'Long') { - return 'long'; - } else if (type.name === 'Timestamp') { - if (stringifyTimestamps) { - return 'epoch_millis_string'; - } - return 'epoch_millis'; - } - throw new VError('unknown GraphQL leaf type: %s', type); - } - - // Leaf types and lists of (non-null) leaf types are allowed - if (gql.isListType(type)) { - type = type.ofType; - if (gql.isNonNullType(type)) { - type = type.ofType; - } - if (gql.isLeafType(type)) { - return {entryType: asPrimitiveType(type)}; - } - } else if (gql.isLeafType(type)) { - return asPrimitiveType(type); - } - throw new VError('unknown GraphQL leaf type: %s', type); -} - -// TODO: Merge with graphql.ts. This is implemented differently. -export function isLeafType(type: any): boolean { - if (gql.isNonNullType(type)) { - return isLeafType(type.ofType); - } else if (gql.isListType(type)) { - type = type.ofType; - if (gql.isNonNullType(type)) { - type = type.ofType; - } - } - return gql.isLeafType(type); -} diff --git a/src/client.ts b/src/client.ts index 8938402..b48fe05 100644 --- a/src/client.ts +++ b/src/client.ts @@ -8,7 +8,7 @@ import * as zlib from 'zlib'; import {makeAxiosInstanceWithRetry} from './axios'; import {wrapApiError} from './errors'; -import {paginatedQuery} from './graphql/graphql'; +import {paginatedQueryV2} from './graphql/graphql'; import {batchMutation} from './graphql/query-builder'; import {Mutation, Schema} from './graphql/types'; import { @@ -305,7 +305,7 @@ export class FarosClient { graph: string, rawQuery: string, pageSize = 100, - paginator = paginatedQuery, + paginator = paginatedQueryV2, args: Map = new Map() ): AsyncIterable { const {query, edgesPath, edgeIdPath, pageInfoPath} = paginator(rawQuery); diff --git a/src/graphql/graphql.ts b/src/graphql/graphql.ts index 4253623..9a5e8de 100644 --- a/src/graphql/graphql.ts +++ b/src/graphql/graphql.ts @@ -4,7 +4,6 @@ import {isScalarType, Kind} from 'graphql'; import {VariableDefinitionNode} from 'graphql/language/ast'; import {jsonToGraphQLQuery, VariableType} from 'json-to-graphql-query'; import _ from 'lodash'; -import {plural} from 'pluralize'; import {Dictionary} from 'ts-essentials'; import {Memoize} from 'typescript-memoize'; import {VError} from 'verror'; @@ -13,8 +12,8 @@ import {FarosClient} from '../client'; import {PathToModel, Query, Reference} from './types'; export type AnyRecord = Record; -type AsyncOrSyncIterable = AsyncIterable | Iterable; export type RecordIterable = AsyncOrSyncIterable; +type AsyncOrSyncIterable = AsyncIterable | Iterable; export interface PaginatedQuery { readonly query: string; @@ -36,11 +35,9 @@ export interface FlattenContext { export const NODES = 'nodes'; export const EDGES = 'edges'; -export const METADATA = 'metadata'; export const REFRESHED_AT = 'refreshedAt'; const DEFAULT_DIRECTIVE = 'default'; -const MAX_NODE_DEPTH = 10; const ID_FLD = 'id'; @@ -51,237 +48,22 @@ function invalidQuery(message: string): Error { return new Error(`invalid query: ${message}`); } -/** - * An embedded type is an object type that can be inlined in another object - * type with the following exceptions: - * - * 1. It is not a query type for a model namespace, e.g., vcs_Query - * 2. It is not a connection spec type and does not implement a connection spec - * type, e.g., node, connection or edge - */ - export function isEmbeddedObjectType(type: any | undefined | null): boolean { - if (gql.isNonNullType(type)) { - return isEmbeddedObjectType(type.ofType); - } else if (!gql.isObjectType(type)) { - return false; - } - - return !( - type.name.endsWith('Query') || - type.name.endsWith('Connection') || - type.name.endsWith('Edge') || - isV1ModelType(type) - ); -} - export function isObjectListType(type: any): type is gql.GraphQLList { return gql.isListType(type) && gql.isObjectType(type.ofType); } -export function isEmbeddedObjectListType( - type: any -): type is gql.GraphQLList { - return gql.isListType(type) && isEmbeddedObjectType(type.ofType); -} - export function isModelQuery( parentType: any, type: any ): type is gql.GraphQLObjectType { return ( - gql.isObjectType(parentType) && parentType.name.endsWith('Query') && - gql.isObjectType(type) && type.name.endsWith('Connection') + gql.isObjectType(parentType) && + parentType.name.endsWith('Query') && + gql.isObjectType(type) && + type.name.endsWith('Connection') ); } -function isLeafType(type: any): boolean { - if (gql.isNonNullType(type)) { - type = type.ofType; - } - - if (gql.isListType(type)) { - let ofType = type.ofType; - // The element type can also be non-null - if (gql.isNonNullType(ofType)) { - ofType = ofType.ofType; - } - // A list of object types will be serialized as a list of strings - return gql.isLeafType(ofType) || gql.isObjectType(ofType); - } - return gql.isLeafType(type); -} - -/** Returns paths to all node collections in a query */ -export function queryNodesPaths(query: string): ReadonlyArray { - const fieldPath: string[] = []; - const nodesPaths: string[][] = []; - gql.visit(gql.parse(query), { - Document(node) { - const definition = node.definitions[0]; - if ( - node.definitions.length !== 1 || - definition.kind !== 'OperationDefinition' || - definition.operation !== 'query' - ) { - throw invalidQuery( - 'document should contain a single query operation definition' - ); - } - }, - Field: { - enter(node) { - const name = node.alias?.value ?? node.name.value; - fieldPath.push(name); - if (name === NODES) { - nodesPaths.push([...fieldPath]); - } - }, - leave() { - fieldPath.pop(); - }, - }, - }); - return nodesPaths; -} - -export function paginatedQuery(query: string): PaginatedQuery { - const fieldPath: string[] = []; - const edgesPath: string[] = []; - const pageInfoPath: string[] = []; - const ast = gql.visit(gql.parse(query), { - Document(node) { - if (node.definitions.length !== 1) { - throw invalidQuery( - 'document should contain a single query operation definition' - ); - } - }, - OperationDefinition(node) { - if (node.operation !== 'query') { - throw invalidQuery('only query operations are supported'); - } - - // Verify that all fields up until the first nodes field - // contain a single selection - const selections = [...node.selectionSet.selections]; - while (selections.length) { - if (selections.length > 1) { - throw invalidQuery( - 'query operation can only contain a single selection' - ); - } - const selection = selections.pop(); - if (selection?.kind === 'Field') { - if (selection.name.value === NODES) { - break; - } - selections.push(...(selection.selectionSet?.selections || [])); - } - } - - // Add pagination variables to query operation - return createOperationDefinition( - node, - [['pageSize', 'Int'], ['cursor', 'Cursor']] - ); - }, - Field: { - enter(node) { - fieldPath.push(node.name.value); - const isParentOfNodes = node.selectionSet?.selections.some( - (s) => s.kind === 'Field' && s.name.value === NODES - ); - - if (edgesPath.length) { - // Skip rest of nodes once edges path has been set - return false; - } else if (isParentOfNodes) { - pageInfoPath.push(...fieldPath, 'pageInfo'); - // copy existing filter args - const existing = (node.arguments ?? []).filter((n) => - ALLOWED_ARG_TYPES.has(n.name.value) - ); - return { - kind: 'Field', - name: node.name, - // Add pagination arguments - arguments: [ - ...existing, - { - kind: 'Argument', - name: {kind: 'Name', value: 'first'}, - value: { - kind: 'Variable', - name: {kind: 'Name', value: 'pageSize'}, - }, - }, - { - kind: 'Argument', - name: {kind: 'Name', value: 'after'}, - value: { - kind: 'Variable', - name: {kind: 'Name', value: 'cursor'}, - }, - }, - ], - // Add pageInfo alongside the existing selection set - selectionSet: { - kind: 'SelectionSet', - selections: [ - ...(node.selectionSet?.selections || []), - { - kind: 'Field', - name: {kind: 'Name', value: 'pageInfo'}, - selectionSet: { - kind: 'SelectionSet', - selections: [ - { - kind: 'Field', - name: {kind: 'Name', value: 'hasNextPage'}, - }, - ], - }, - }, - ], - }, - }; - } else if (node.name.value === NODES) { - edgesPath.push(...fieldPath.slice(0, -1), EDGES); - // Replace the first nodes field with edges field - return { - kind: 'Field', - name: {kind: 'Name', value: EDGES}, - selectionSet: { - kind: 'SelectionSet', - selections: [ - { - kind: 'Field', - name: {kind: 'Name', value: 'cursor'}, - }, - { - kind: 'Field', - name: {kind: 'Name', value: 'node'}, - selectionSet: node.selectionSet, - }, - ], - }, - }; - } - return undefined; - }, - leave() { - fieldPath.pop(); - }, - }, - }); - - return { - query: gql.print(ast), - edgesPath, - pageInfoPath, - }; -} - export function paginatedQueryV2(query: string): PaginatedQuery { switch (process.env.GRAPHQL_V2_PAGINATOR) { case 'relay': @@ -314,10 +96,10 @@ export function paginatedWithRelayV2(query: string): PaginatedQuery { } // Add pagination variables to query operation - return createOperationDefinition( - node, - [['pageSize', 'Int'], ['cursor', 'String']] - ); + return createOperationDefinition(node, [ + ['pageSize', 'Int'], + ['cursor', 'String'], + ]); }, Field: { enter(node) { @@ -411,8 +193,8 @@ function createOperationDefinition( node: gql.OperationDefinitionNode, varDefs: [string, string][] ): gql.OperationDefinitionNode { - const variableDefinitions: VariableDefinitionNode [] = - varDefs.map(([varName, varType]) => { + const variableDefinitions: VariableDefinitionNode[] = varDefs.map( + ([varName, varType]) => { return { kind: Kind.VARIABLE_DEFINITION, variable: { @@ -424,7 +206,8 @@ function createOperationDefinition( name: {kind: Kind.NAME, value: varType}, }, }; - }); + } + ); variableDefinitions.push(...(node.variableDefinitions || [])); return { kind: Kind.OPERATION_DEFINITION, @@ -479,6 +262,8 @@ export function paginateWithKeysetV2(query: string): PaginatedQuery { } }, OperationDefinition(node) { + // TODO: Unlike the old v1 paginator, this one doesn't restrict the query + // to a single model at the root of the query node. Seems like a mistake? if (node.operation !== 'query') { throw invalidQuery('only query operations are supported'); } @@ -497,9 +282,8 @@ export function paginateWithKeysetV2(query: string): PaginatedQuery { } edgesPath.push(node.name.value); - const existingWhereArgs = node.arguments?.filter( - (n) => n.name.value === 'where' - ) ?? []; + const existingWhereArgs = + node.arguments?.filter((n) => n.name.value === 'where') ?? []; let whereArgs: gql.ArgumentNode[] = [ ...existingWhereArgs, { @@ -602,7 +386,7 @@ export function paginateWithOffsetLimitV2(query: string): PaginatedQuery { Document(node) { if (node.definitions.length !== 1) { throw invalidQuery( - 'document should contain a single query operation definition', + 'document should contain a single query operation definition' ); } }, @@ -612,10 +396,10 @@ export function paginateWithOffsetLimitV2(query: string): PaginatedQuery { } // Add pagination variables to query operation - return createOperationDefinition( - node, - [['offset', 'Int'], ['limit', 'Int']] - ); + return createOperationDefinition(node, [ + ['offset', 'Int'], + ['limit', 'Int'], + ]); }, Field: { enter(node) { @@ -626,7 +410,7 @@ export function paginateWithOffsetLimitV2(query: string): PaginatedQuery { edgesPath.push(node.name.value); // copy existing where args const existing = (node.arguments ?? []).filter((n) => - ALLOWED_ARG_TYPES.has(n.name.value), + ALLOWED_ARG_TYPES.has(n.name.value) ); return { ...node, @@ -776,9 +560,11 @@ export function flattenV2( ); } // use field description to determine if the jsonb field is an array - if (isScalarType(gqlType) - && gqlType.name === 'jsonb' - && typeInfo.getFieldDef()?.description === 'array') { + if ( + isScalarType(gqlType) && + gqlType.name === 'jsonb' && + typeInfo.getFieldDef()?.description === 'array' + ) { jsonArrayPaths.add(leafPath); } leafPaths.push(leafPath); @@ -962,147 +748,35 @@ function setPathToDefault( pathToDefault.set(path, defaultValue); } -/** Flattens nested nodes returned from a query */ -export function flatten( - query: string, - schema: gql.GraphQLSchema -): FlattenContext { - const fieldPath: string[] = []; - const leafPaths: string[] = []; - const pathToDefault = new Map(); - const pathToType = new Map(); - const params = new Map(); - const typeInfo = new gql.TypeInfo(schema); - gql.visit( - gql.parse(query), - gql.visitWithTypeInfo(typeInfo, { - VariableDefinition(node: gql.VariableDefinitionNode): boolean { - addVariableDefinition(node, schema, params); - return false; - }, - Argument(): boolean { - // Skip arg subtrees - return false; - }, - Directive(node) { - setPathToDefault(node, fieldPath, pathToType, pathToDefault); - }, - Field: { - enter(node): boolean | void { - const name = node.alias?.value ?? node.name.value; - fieldPath.push(name); - const type = typeInfo.getType(); - if (name !== NODES && isLeafType(type)) { - const leafPath = fieldPath.join('.'); - const gqlType = unwrapType(type); - if (!gqlType) { - throw new VError( - 'cannot unwrap type \'%s\' of field \'%s\'', - type, - leafPath - ); - } - leafPaths.push(leafPath); - pathToType.set(leafPath, gqlType); - if (node.selectionSet?.selections?.length) { - // Returning false bypasses call to leave() - fieldPath.pop(); - return false; - } - } - return undefined; - }, - leave(): void { - fieldPath.pop(); - }, - }, - FragmentDefinition(): void { - throw new VError('fragments are not supported'); - }, - FragmentSpread(): void { - throw new VError('fragments are not supported'); - }, - }) - ); - - // Verify the query doesn't exceed the max node depth - const nodesPaths = queryNodesPaths(query); - if (!nodesPaths.length) { - throw new VError('query must contain at least one nodes collection'); +/** + * Returns an iterable that cross-joins an array of object iterables + * and merges each output object array into a single object + */ +export async function* crossMerge( + iters: (AsyncIterable | Iterable)[] +): AsyncIterable { + if (!iters.length) { + return; } - for (const path of nodesPaths) { - let nodeDepth = 0; - for (const pathPart of path) { - if (pathPart === NODES) { - nodeDepth++; - } + const [curr, ...rest] = iters; + let count1 = 0; + for await (const item1 of crossMerge(rest)) { + count1++; + let count2 = 0; + for await (const item2 of curr) { + count2++; + yield {...item1, ...item2}; } - if (nodeDepth > MAX_NODE_DEPTH) { - throw new VError('query exceeds max node depth of %d', MAX_NODE_DEPTH); + if (!count2) { + yield item1; + } + } + if (!count1) { + for await (const item2 of curr) { + yield item2; } } - - // Verify names won't collide once flattened - const fieldTypes = new Map(); - const leafToPath = new Map(); - const leafToDefault = new Map(); - for (const path of leafPaths) { - const name = fieldName(path); - if (leafToPath.has(name)) { - const otherPath = leafToPath.get(name); - throw new VError( - 'fields \'%s\' and \'%s\' will both map to the same name: ' + - '\'%s\'. use field aliases to prevent collision.', - path, - otherPath, - name - ); - } - leafToPath.set(name, path); - leafToDefault.set(name, pathToDefault.get(path)); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - fieldTypes.set(name, pathToType.get(path)!); - } - - return { - fieldTypes, - params, - currentPath: nodesPaths[0].join('.'), - leafToDefault, - leafPaths, - depth: 0, - }; -} - -/** - * Returns an iterable that cross-joins an array of object iterables - * and merges each output object array into a single object - */ -export async function* crossMerge( - iters: (AsyncIterable | Iterable)[] -): AsyncIterable { - if (!iters.length) { - return; - } - const [curr, ...rest] = iters; - let count1 = 0; - for await (const item1 of crossMerge(rest)) { - count1++; - let count2 = 0; - for await (const item2 of curr) { - count2++; - yield {...item1, ...item2}; - } - if (!count2) { - yield item1; - } - } - if (!count1) { - for await (const item2 of curr) { - yield item2; - } - } -} +} /** Flattens an iterable of nested node objects */ export function flattenIterable( @@ -1210,239 +884,66 @@ export interface Reader { }; } -export function readerFromQuery( - graph: string, - faros: FarosClient, - query: Query, - pageSize: number, - graphSchema: gql.GraphQLSchema, - incremental = false, - paginator = paginatedQuery, - flattener = flatten -): Reader { - const flattenCtx = flattener(query.gql, graphSchema); +interface ReaderFromQueryConfig { + readonly client: FarosClient; + readonly graph: string; + readonly graphSchema: gql.GraphQLSchema; + readonly query: Query; + readonly pageSize: number; + readonly incremental?: boolean; +} + +export function readerFromQuery(cfg: ReaderFromQueryConfig): Reader { + const flattenCtx = flattenV2(cfg.query.gql, cfg.graphSchema); if (!(flattenCtx.leafPaths.length && flattenCtx.fieldTypes.size)) { throw new VError( 'unable to extract metadata from query %s: %s', - query.name, query.gql); + cfg.query.name, + cfg.query.gql + ); } return { execute(args: Map): AsyncIterable { - const nodes = faros.nodeIterable( - graph, - query.gql, - pageSize, - paginator, + const nodes = cfg.client.nodeIterable( + cfg.graph, + cfg.query.gql, + cfg.pageSize, + paginatedQueryV2, args ); return flattenIterable(flattenCtx, nodes); }, metadata: { - name: query.name, + name: cfg.query.name, fields: flattenCtx.fieldTypes, - modelKeys: incremental ? [ID_FLD] : undefined, + modelKeys: cfg.incremental ? [ID_FLD] : undefined, params: flattenCtx.params, - incremental + incremental: cfg.incremental ?? false, }, }; } -export function createNonIncrementalReaders( - client: FarosClient, - graph: string, - pageSize: number, - graphSchema: gql.GraphQLSchema, - graphqlVersion: string, - queries: ReadonlyArray -): ReadonlyArray { - return queries.map((query) => { - switch (graphqlVersion) { - case 'v1': - return readerFromQuery(graph, client, query, pageSize, graphSchema); - case 'v2': - return readerFromQuery( - graph, - client, - query, - pageSize, - graphSchema, - false, - paginatedQueryV2, - flattenV2 - ); - default: - throw new VError('invalid graphql version %s', graphqlVersion); - } - }); +interface NonIncrementalReadersConfig { + readonly client: FarosClient; + readonly graph: string; + readonly graphSchema: gql.GraphQLSchema; + readonly queries: ReadonlyArray; + readonly pageSize: number; } -/** - * Creates an incremental query from a model type. - * The selections will include: - * 1. All the scalar fields. - * 2. Nested fragments for all referenced models, selecting their IDs. - * 3. A fragment "metadata { refreshedAt }" - * - * By default, it aliases referenced models IDs to prevent collisions - * if flattened. - * E.g., { id pipeline { id } } => { id pipeline { pipelineId: id } } - * The avoidCollisions parameter controls this behavior. - * - * If resolvedPrimaryKeys is provided, it will use the fully resolved - * primary key fragment for referenced models instead of the ID field. - */ -export function buildIncrementalQueryV1( - type: gql.GraphQLObjectType, - avoidCollisions = true, - resolvedPrimaryKeys: Dictionary = {}, - resolvedEmbeddedFields: Dictionary = {} -): Query { - const name = type.name; - // add fields and FKs - const fieldsObj: any = {}; - // add PK - fieldsObj[ID_FLD] = true; - for (const fldName of Object.keys(type.getFields())) { - const field = type.getFields()[fldName]; - let unwrappedType = unwrapType(field.type) || field.type; - if (gql.isListType(unwrappedType)) { - unwrappedType = unwrappedType.ofType; - } - - if (gql.isScalarType(unwrappedType)) { - fieldsObj[field.name] = true; // arbitrary value here - } else if (isV1EmbeddedType(unwrappedType)) { - const resolved = resolvedEmbeddedFields[unwrappedType.name]; - ok( - !_.isNil(resolved), - `expected ${unwrappedType.name} embedded type to have been resolved` - ); - fieldsObj[field.name] = { - [resolved]: true, - }; - } else if (isV1ModelType(unwrappedType)) { - // this is foreign key to a top-level model. - // add nested fragment to select id of referenced model - const fk = resolvedPrimaryKeys[unwrappedType.name] || ID_FLD; - - if (avoidCollisions) { - let nestedName = `${field.name}Id`; - // check for collision between nested name and scalars - if (_.has(type.getFields(), nestedName)) { - nestedName = `${field.name}Fk`; - } - fieldsObj[field.name] = { - [`${nestedName}: ${fk}`]: true, - }; - } else { - fieldsObj[field.name] = { - [fk]: true, - }; - } - } - } - - // Add refreshedAt - fieldsObj.metadata = { - refreshedAt: true, - }; - - // transform name into a dot-separated path for setting fields and filters - // e.g. cicd_ReleaseTagAssociation => cicd.releaseTagAssociations - const segments = name.split('_'); - ok(segments.length > 1, `expected 2 or more elements in ${segments}`); - // last segment is model name and needs to be lowerFirst and plural - const names = segments.slice(0, -1); - names.push(_.lowerFirst(plural(_.last(segments) as string))); - // prepend query - names.unshift('query'); - // add fields under nodes - const fieldPath = names.concat('nodes').join('.'); - const query = _.set({}, fieldPath, fieldsObj); - // add filter for refreshedAt at level of model name - const filterPath = names.concat('__args').join('.'); - const refreshedFilter = { - filter: { - refreshedAtMillis: { - greaterThanOrEqualTo: new VariableType('from'), - lessThan: new VariableType('to'), - }, - }, - }; - _.set(query, filterPath, refreshedFilter); - _.set(query, 'query.__variables', { - from: 'builtin_BigInt!', - to: 'builtin_BigInt!', - }); - return {name, gql: jsonToGraphQLQuery(query)}; -} - -function isV1ModelType(type: any): type is gql.GraphQLObjectType { - return ( - gql.isObjectType(type) && - type.getInterfaces().length > 0 && - type.getInterfaces()[0].name === 'Node' - ); -} - -function isV1EmbeddedType(type: any): type is gql.GraphQLObjectType { - return ( - gql.isObjectType(type) && - !gql.isIntrospectionType(type) && - !_.some(type.getInterfaces(), (i) => i.name === 'Node') - ); -} - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export function createIncrementalReadersV1( - client: FarosClient, - graph: string, - pageSize: number, - graphQLSchema: gql.GraphQLSchema +export function createNonIncrementalReaders( + cfg: NonIncrementalReadersConfig ): ReadonlyArray { - const result: Reader[] = createIncrementalQueriesV1(graphQLSchema).map( - (query) => - readerFromQuery(graph, client, query, pageSize, graphQLSchema, true) + return cfg.queries.map((query) => + readerFromQuery({ + client: cfg.client, + graph: cfg.graph, + graphSchema: cfg.graphSchema, + query, + pageSize: cfg.pageSize, + incremental: false, + }) ); - - if (!result.length) { - throw new VError('failed to load v1 incremental readers'); - } - return result; -} - -export function createIncrementalQueriesV1( - graphQLSchema: gql.GraphQLSchema, - primaryKeys?: Dictionary>, - avoidCollisions = true -): ReadonlyArray { - const result: Query[] = []; - const resolvedPrimaryKeys = primaryKeys - ? new PrimaryKeyResolver( - graphQLSchema, - primaryKeys, - {}, - isV1ModelType - ).resolvePrimaryKeys() - : {}; - const resolvedEmbeddedFields = new EmbeddedFieldResolver( - graphQLSchema - ).resolveEmbeddedFields(); - for (const name of Object.keys(graphQLSchema.getTypeMap())) { - const type = graphQLSchema.getType(name); - if (isV1ModelType(type)) { - result.push( - buildIncrementalQueryV1( - type, - avoidCollisions, - resolvedPrimaryKeys, - resolvedEmbeddedFields - ) - ); - } - } - - return result; } function isV2ModelType(type: any): type is gql.GraphQLObjectType { @@ -1454,8 +955,18 @@ function isV2ModelType(type: any): type is gql.GraphQLObjectType { function isScalar(type: any): boolean { const unwrapped = unwrapType(type); - return gql.isScalarType(unwrapped) || - (gql.isListType(unwrapped) && gql.isScalarType(unwrapped.ofType)); + return ( + gql.isScalarType(unwrapped) || + (gql.isListType(unwrapped) && gql.isScalarType(unwrapped.ofType)) + ); +} + +interface IncrementalQueryConfig { + readonly type: gql.GraphQLObjectType; + readonly resolvedPrimaryKeys?: Dictionary; + readonly references?: Dictionary; + readonly avoidCollisions?: boolean; + readonly scalarsOnly?: boolean; } /** @@ -1472,31 +983,29 @@ function isScalar(type: any): boolean { * If resolvedPrimaryKeys is provided, it will use the fully resolved * primary key fragment for referenced models instead of the ID field. */ -export function buildIncrementalQueryV2( - type: gql.GraphQLObjectType, - avoidCollisions = true, - resolvedPrimaryKeys: Dictionary = {}, - references: Dictionary = {}, - scalarsOnly = false -): Query { - const name = type.name; +export function buildIncrementalQueryV2(cfg: IncrementalQueryConfig): Query { + const avoidCollisions = cfg.avoidCollisions ?? true; + const resolvedPrimaryKeys = cfg.resolvedPrimaryKeys ?? {}; + const references = cfg.references ?? {}; + const scalarsOnly = cfg.scalarsOnly ?? false; + const name = cfg.type.name; // add fields and FKs const fieldsObj: any = {}; // add PK fieldsObj[ID_FLD] = true; - for (const fldName of Object.keys(type.getFields())) { - const field = type.getFields()[fldName]; + for (const fldName of Object.keys(cfg.type.getFields())) { + const field = cfg.type.getFields()[fldName]; if (isScalar(field.type)) { const reference = references[fldName]; if (reference) { // This is a (scalar) foreign key to a top-level model // Check that the non-scalar corresponding foreign key // exists and skip from the query selection - const checkField = type.getFields()[reference.field]; + const checkField = cfg.type.getFields()[reference.field]; ok( !_.isNil(checkField), `expected ${reference.field} to be a reference field of` + - ` ${type.name} (foreign key to ${reference.model})` + ` ${cfg.type.name} (foreign key to ${reference.model})` ); } else { fieldsObj[field.name] = true; // arbitrary value here @@ -1508,7 +1017,7 @@ export function buildIncrementalQueryV2( if (avoidCollisions) { let nestedName = `${field.name}Id`; // check for collision between nested name and scalars - if (_.has(type.getFields(), nestedName)) { + if (_.has(cfg.type.getFields(), nestedName)) { nestedName = `${field.name}Fk`; } { @@ -1545,33 +1054,31 @@ export function buildIncrementalQueryV2( return {name, gql: jsonToGraphQLQuery(query)}; } -// eslint-disable-next-line @typescript-eslint/no-unused-vars +interface IncrementalReadersConfig { + readonly client: FarosClient; + readonly graph: string; + readonly pageSize: number; + readonly graphSchema: gql.GraphQLSchema; + readonly avoidCollisions: boolean; + readonly scalarsOnly: boolean; +} + export function createIncrementalReadersV2( - client: FarosClient, - graph: string, - pageSize: number, - graphQLSchema: gql.GraphQLSchema, - avoidCollisions = true, - scalarsOnly = false + cfg: IncrementalReadersConfig ): ReadonlyArray { - const result: Reader[] = createIncrementalQueriesV2( - graphQLSchema, - undefined, - undefined, - avoidCollisions, - scalarsOnly - ).map( - (query) => - readerFromQuery( - graph, - client, - query, - pageSize, - graphQLSchema, - true, - paginatedQueryV2, - flattenV2 - ) + const result: Reader[] = createIncrementalQueriesV2({ + graphSchema: cfg.graphSchema, + avoidCollisions: cfg.avoidCollisions ?? true, + scalarsOnly: cfg.scalarsOnly ?? false, + }).map((query) => + readerFromQuery({ + client: cfg.client, + graph: cfg.graph, + graphSchema: cfg.graphSchema, + pageSize: cfg.pageSize, + incremental: true, + query, + }) ); if (!result.length) { throw new VError('failed to create v2 incremental readers'); @@ -1579,38 +1086,43 @@ export function createIncrementalReadersV2( return result; } +interface IncrementalQueriesConfig { + readonly graphSchema: gql.GraphQLSchema; + readonly primaryKeys?: Dictionary>; + readonly references?: Dictionary>; + readonly avoidCollisions?: boolean; + readonly scalarsOnly?: boolean; +} + export function createIncrementalQueriesV2( - graphQLSchema: gql.GraphQLSchema, - primaryKeys?: Dictionary>, - references?: Dictionary>, - avoidCollisions = true, - scalarsOnly = false + cfg: IncrementalQueriesConfig ): ReadonlyArray { + const avoidCollisions = cfg.avoidCollisions ?? true; + const scalarsOnly = cfg.avoidCollisions ?? false; const result: Query[] = []; - const resolvedPrimaryKeys = primaryKeys + const resolvedPrimaryKeys = cfg.primaryKeys ? new PrimaryKeyResolver( - graphQLSchema, - primaryKeys, - references || {}, - isV2ModelType + cfg.graphSchema, + cfg.primaryKeys, + cfg.references || {} ).resolvePrimaryKeys() : {}; - for (const name of Object.keys(graphQLSchema.getTypeMap())) { - const type = graphQLSchema.getType(name); + for (const name of Object.keys(cfg.graphSchema.getTypeMap())) { + const type = cfg.graphSchema.getType(name); let typeReferences = {}; - if (references && type) { - typeReferences = references[type.name] || {}; + if (cfg.references && type) { + typeReferences = cfg.references[type.name] || {}; } if (isV2ModelType(type)) { result.push( - buildIncrementalQueryV2( + buildIncrementalQueryV2({ type, - avoidCollisions, resolvedPrimaryKeys, - typeReferences, + references: typeReferences, + avoidCollisions, scalarsOnly - ) + }) ); } } @@ -1618,184 +1130,6 @@ export function createIncrementalQueriesV2( return result; } -/** - * Converts a V1 query into incremental: - * Adds "from" and "to" query variables. - * Adds a filter "from" <= refreshedAt < "to" to the top level model. - * Makes sure metadata { refreshedAt } is selected. - * - * Example: - * vcs { - * pullRequests { - * nodes { - * title - * } - * } - * } - * - * becomes: - * query incrementalQuery($from: builtin_BigInt!, $to: builtin_BigInt!) { - * vcs { - * pullRequests( - * filter: { - * refreshedAtMillis: { - * greaterThanOrEqualTo: $from, - * lessThan: $to - * } - * } - * ) { - * nodes { - * title - * metadata { - * refreshedAt - * } - * } - * } - * } - * } - */ -export function toIncrementalV1(query: string): string { - let hasMetadata = false, - hasRefreshedAt = false, - firstNodesSeen = false; - let fieldDepth = 0; - - const ast = gql.visit(gql.parse(query), { - Document(node) { - if (node.definitions.length !== 1) { - throw invalidQuery( - 'document should contain a single query operation definition' - ); - } - }, - OperationDefinition(node) { - if (node.operation !== 'query') { - throw invalidQuery('only query operations are supported'); - } - - // Add refreshedAtMillis filter variables to query operation - return withVariableDefinitions(node, [ - { - kind: Kind.VARIABLE_DEFINITION, - variable: { - kind: Kind.VARIABLE, - name: {kind: Kind.NAME, value: 'from'}, - }, - type: { - kind: Kind.NAMED_TYPE, - name: {kind: Kind.NAME, value: 'builtin_BigInt!'}, - }, - }, - { - kind: Kind.VARIABLE_DEFINITION, - variable: { - kind: Kind.VARIABLE, - name: {kind: Kind.NAME, value: 'to'}, - }, - type: { - kind: Kind.NAMED_TYPE, - name: {kind: Kind.NAME, value: 'builtin_BigInt!'}, - }, - }, - ]); - }, - Field: { - enter(node) { - const name = node.alias?.value ?? node.name.value; - - if (!firstNodesSeen) { - if (name === NODES) { - firstNodesSeen = true; - } - fieldDepth++; - return undefined; - } - if (!hasMetadata) { - if (name === 'metadata') { - hasMetadata = true; - fieldDepth++; - return undefined; - } - return false; - } - if (!hasRefreshedAt) { - if (name === 'refreshedAt') { - hasRefreshedAt = true; - fieldDepth++; - return undefined; - } - return false; - } - return false; - }, - leave(node) { - const name = node.alias?.value ?? node.name.value; - fieldDepth--; - - // We're at the top level model - // Add the filter here - if (fieldDepth === 1) { - const refreshedFilter = buildRefreshedFilter( - 'filter', - 'refreshedAtMillis', - 'greaterThanOrEqualTo', - 'lessThan' - ); - - return { - ...node, - arguments: [...(node.arguments || []), refreshedFilter], - }; - } - - if (name === NODES && !hasMetadata) { - // Adds metadata { refreshedAt } - const selections = node.selectionSet?.selections || []; - const newSelection = { - kind: 'Field', - name: {kind: 'Name', value: 'metadata'}, - selectionSet: { - kind: 'SelectionSet', - selections: [ - { - kind: 'Field', - name: {kind: 'Name', value: 'refreshedAt'}, - }, - ], - }, - }; - - return { - ...node, - selectionSet: { - kind: Kind.SELECTION_SET, - selections: [...selections, newSelection], - }, - }; - } - if (name === 'metadata' && !hasRefreshedAt) { - // Adds refreshedAt - const selections = node.selectionSet?.selections || []; - const newSelection = { - kind: 'Field', - name: {kind: 'Name', value: 'refreshedAt'}, - }; - - return { - ...node, - selectionSet: { - kind: Kind.SELECTION_SET, - selections: [...selections, newSelection], - }, - }; - } - return undefined; - }, - }, - }); - return gql.print(ast); -} - /** * Converts a V2 query into incremental: * Adds "from" and "to" query variables. @@ -1914,67 +1248,6 @@ export function toIncrementalV2(query: string): string { return gql.print(ast); } -/** - * Returns the path to the queried top-level model in a V1 query - * - * Example, for query: - * vcs { - * pullRequests { - * nodes { - * title - * } - * } - * } - * - * returns: - * { - * modelName: 'vcs_PullRequest', - * path: ['vcs', 'pullRequests', 'nodes'], - * } - */ -export function pathToModelV1( - query: string, - schema: gql.GraphQLSchema -): PathToModel { - const typeInfo = new gql.TypeInfo(schema); - let firstNodesSeen = false; - let modelName: string | undefined; - const fieldPath: string[] = []; - - gql.visit( - gql.parse(query), - gql.visitWithTypeInfo(typeInfo, { - Field: { - enter(node) { - const name = node.alias?.value ?? node.name.value; - - if (firstNodesSeen) { - const type = typeInfo.getParentType(); - ok(isV1ModelType(type)); - modelName = type.name; - return false; - } - - fieldPath.push(name); - - if (name === NODES) { - firstNodesSeen = true; - } - - return undefined; - }, - }, - }) - ); - - ok(modelName !== undefined, 'Could not find queried top-level model'); - - return { - path: fieldPath, - modelName, - }; -} - /** * Returns the path to the queried top-level model in a V2 query * @@ -2105,8 +1378,7 @@ class PrimaryKeyResolver { constructor( readonly graphQLSchema: gql.GraphQLSchema, readonly primaryKeys: Dictionary>, - readonly references: Dictionary>, - readonly isTopLevelModelTypeChecker = isV1ModelType + readonly references: Dictionary> ) {} /** @@ -2123,7 +1395,7 @@ class PrimaryKeyResolver { for (const name of Object.keys(this.graphQLSchema.getTypeMap())) { const type = this.graphQLSchema.getType(name); - if (this.isTopLevelModelTypeChecker(type)) { + if (isV2ModelType(type)) { result[name] = this.resolvePrimaryKey(type); } } @@ -2156,7 +1428,7 @@ class PrimaryKeyResolver { if (gql.isScalarType(unwrapType(field.type))) { resolved.push(field.name); - } else if (this.isTopLevelModelTypeChecker(field.type)) { + } else if (isV2ModelType(field.type)) { resolved.push( `${field.name} { ${this.resolvePrimaryKey(field.type)} }` ); @@ -2166,52 +1438,3 @@ class PrimaryKeyResolver { return resolved.join(' '); } } - -class EmbeddedFieldResolver { - constructor(readonly graphQLSchema: gql.GraphQLSchema) {} - - /** - * Fully resolves embedded objects in the schema. - * - * E.g., given a type 'Person' with fields 'name: String' and - * 'contact: Contact' and type 'Contact' with fields - * 'email: String' and 'phone: String', the type 'Person' - * resolves to { name contact { email phone } } - */ - public resolveEmbeddedFields(): Dictionary { - const result: Dictionary = {}; - - for (const name of Object.keys(this.graphQLSchema.getTypeMap())) { - const type = this.graphQLSchema.getType(name); - if (isV1EmbeddedType(type)) { - result[name] = this.resolveEmbeddedField(type); - } - } - - return result; - } - - @Memoize((type: gql.GraphQLObjectType) => type.name) - private resolveEmbeddedField(type: gql.GraphQLObjectType): string { - const resolved = []; - - for (const fldName of Object.keys(type.getFields())) { - const field = type.getFields()[fldName]; - let unwrappedType = unwrapType(field.type) || field.type; - - if (gql.isListType(unwrappedType)) { - unwrappedType = unwrappedType.ofType; - } - - if (gql.isScalarType(unwrappedType) || gql.isEnumType(unwrappedType)) { - resolved.push(field.name); - } else if (isV1EmbeddedType(unwrappedType)) { - resolved.push( - `${field.name} { ${this.resolveEmbeddedField(unwrappedType)} }` - ); - } - } - - return resolved.join(' '); - } -} diff --git a/src/index.ts b/src/index.ts index 9fac795..f3332ce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -50,26 +50,17 @@ export { PaginatedQuery, Reader, RecordIterable, - buildIncrementalQueryV1, buildIncrementalQueryV2, - createIncrementalQueriesV1, createIncrementalQueriesV2, - createIncrementalReadersV1, createIncrementalReadersV2, createNonIncrementalReaders, crossMerge, - flatten, flattenIterable, flattenV2, - paginatedQuery, paginatedQueryV2, - pathToModelV1, pathToModelV2, - queryNodesPaths, readerFromQuery, - toIncrementalV1, toIncrementalV2, } from './graphql/graphql'; export {FarosGraphSchema} from './schema'; export {Utils} from './utils'; -export {FieldPaths, getFieldPaths, asV2AST, QueryAdapter} from './adapter'; diff --git a/test/__snapshots__/graphql.test.ts.snap b/test/__snapshots__/graphql.test.ts.snap index ccb266f..c79b871 100644 --- a/test/__snapshots__/graphql.test.ts.snap +++ b/test/__snapshots__/graphql.test.ts.snap @@ -1,19 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`graphql build incremental V1 1`] = ` -{ - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { builds (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid pipeline { pipelineId: id } metadata { refreshedAt } } } } }", - "name": "cicd_Build", -} -`; - -exports[`graphql build incremental V1 2`] = ` -{ - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { builds (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid pipeline { id } metadata { refreshedAt } } } } }", - "name": "cicd_Build", -} -`; - exports[`graphql build incremental V2 1`] = ` { "gql": "query ($from: timestamptz!, $to: timestamptz!) { cicd_Build (where: {refreshedAt: {_gte: $from, _lt: $to}}) { id createdAt endedAt name number origin pipeline { pipelineId: id } refreshedAt startedAt status statusCategory statusDetail uid url } }", @@ -28,82 +14,6 @@ exports[`graphql build incremental V2 2`] = ` } `; -exports[`graphql convert to incremental V1 1`] = ` -"query incrementalQuery($from: builtin_BigInt!, $to: builtin_BigInt!) { - vcs { - pullRequests( - filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}} - ) { - nodes { - metadata { - refreshedAt - } - title - number - reviews { - nodes { - reviewer { - name - } - } - } - } - } - } -}" -`; - -exports[`graphql convert to incremental V1 2`] = ` -"query incrementalQuery($from: builtin_BigInt!, $to: builtin_BigInt!) { - vcs { - pullRequests( - filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}} - ) { - nodes { - metadata { - origin - refreshedAt - } - title - number - reviews { - nodes { - reviewer { - name - } - } - } - } - } - } -}" -`; - -exports[`graphql convert to incremental V1 3`] = ` -"query incrementalQuery($from: builtin_BigInt!, $to: builtin_BigInt!) { - vcs { - pullRequests( - filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}} - ) { - nodes { - title - number - reviews { - nodes { - reviewer { - name - } - } - } - metadata { - refreshedAt - } - } - } - } -}" -`; - exports[`graphql convert to incremental V2 1`] = ` "query incrementalQuery($from: timestamptz!, $to: timestamptz!) { vcs_PullRequest(where: {refreshedAt: {_gte: $from, _lt: $to}}) { @@ -124,228 +34,6 @@ exports[`graphql convert to incremental V2 2`] = ` }" `; -exports[`graphql create incremental queries V1 1`] = ` -[ - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { deployments (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid url requestedAt startedAt endedAt application { applicationId: id } build { buildId: id } metadata { refreshedAt } } } } }", - "name": "cicd_Deployment", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { deploymentChangesets (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id deployment { deploymentId: id } commit { commitId: id } metadata { refreshedAt } } } } }", - "name": "cicd_DeploymentChangeset", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { vcs { commits (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id sha metadata { refreshedAt } } } } }", - "name": "vcs_Commit", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { builds (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid pipeline { pipelineId: id } metadata { refreshedAt } } } } }", - "name": "cicd_Build", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { pipelines (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid metadata { refreshedAt } } } } }", - "name": "cicd_Pipeline", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { compute { applications (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id name metadata { refreshedAt } } } } }", - "name": "compute_Application", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { tms { tasks (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid creator { creatorId: id } metadata { refreshedAt } } } } }", - "name": "tms_Task", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { tms { taskProjectRelationships (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id task { taskId: id } project { projectId: id } metadata { refreshedAt } } } } }", - "name": "tms_TaskProjectRelationship", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { tms { projects (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid metadata { refreshedAt } } } } }", - "name": "tms_Project", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { tms { taskBoardProjectRelationships (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id board { boardId: id } project { projectId: id } metadata { refreshedAt } } } } }", - "name": "tms_TaskBoardProjectRelationship", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { tms { taskBoards (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid metadata { refreshedAt } } } } }", - "name": "tms_TaskBoard", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { tms { users (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid metadata { refreshedAt } } } } }", - "name": "tms_User", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { tms { taskAssignments (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id assignedAt task { taskId: id } assignee { assigneeId: id } metadata { refreshedAt } } } } }", - "name": "tms_TaskAssignment", - }, -] -`; - -exports[`graphql create incremental queries V1 2`] = ` -[ - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { deployments (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid url requestedAt startedAt endedAt application { id } build { id } metadata { refreshedAt } } } } }", - "name": "cicd_Deployment", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { deploymentChangesets (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id deployment { id } commit { id } metadata { refreshedAt } } } } }", - "name": "cicd_DeploymentChangeset", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { vcs { commits (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id sha metadata { refreshedAt } } } } }", - "name": "vcs_Commit", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { builds (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid pipeline { id } metadata { refreshedAt } } } } }", - "name": "cicd_Build", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { pipelines (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid metadata { refreshedAt } } } } }", - "name": "cicd_Pipeline", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { compute { applications (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id name metadata { refreshedAt } } } } }", - "name": "compute_Application", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { tms { tasks (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid creator { id } metadata { refreshedAt } } } } }", - "name": "tms_Task", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { tms { taskProjectRelationships (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id task { id } project { id } metadata { refreshedAt } } } } }", - "name": "tms_TaskProjectRelationship", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { tms { projects (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid metadata { refreshedAt } } } } }", - "name": "tms_Project", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { tms { taskBoardProjectRelationships (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id board { id } project { id } metadata { refreshedAt } } } } }", - "name": "tms_TaskBoardProjectRelationship", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { tms { taskBoards (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid metadata { refreshedAt } } } } }", - "name": "tms_TaskBoard", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { tms { users (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid metadata { refreshedAt } } } } }", - "name": "tms_User", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { tms { taskAssignments (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id assignedAt task { id } assignee { id } metadata { refreshedAt } } } } }", - "name": "tms_TaskAssignment", - }, -] -`; - -exports[`graphql create incremental queries V1 with embedded fields 1`] = ` -[ - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { fake { models (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id deep { env { category detail } envs { category detail } name topics } person { name contact { email phone } } metadata { refreshedAt } } } } }", - "name": "fake_Model", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { deployments (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid source env { category detail } envs { category detail } application { applicationId: id } build { buildId: id } metadata { refreshedAt } } } } }", - "name": "cicd_Deployment", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { compute { applications (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id name displayName platform metadata { refreshedAt } } } } }", - "name": "compute_Application", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { builds (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid name number pipeline { pipelineId: id } metadata { refreshedAt } } } } }", - "name": "cicd_Build", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { pipelines (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid name organization { organizationId: id } metadata { refreshedAt } } } } }", - "name": "cicd_Pipeline", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { organizations (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid name source metadata { refreshedAt } } } } }", - "name": "cicd_Organization", - }, -] -`; - -exports[`graphql create incremental queries V1 with embedded fields 2`] = ` -[ - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { fake { models (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id deep { env { category detail } envs { category detail } name topics } person { name contact { email phone } } metadata { refreshedAt } } } } }", - "name": "fake_Model", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { deployments (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid source env { category detail } envs { category detail } application { id } build { id } metadata { refreshedAt } } } } }", - "name": "cicd_Deployment", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { compute { applications (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id name displayName platform metadata { refreshedAt } } } } }", - "name": "compute_Application", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { builds (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid name number pipeline { id } metadata { refreshedAt } } } } }", - "name": "cicd_Build", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { pipelines (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid name organization { id } metadata { refreshedAt } } } } }", - "name": "cicd_Pipeline", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { organizations (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid name source metadata { refreshedAt } } } } }", - "name": "cicd_Organization", - }, -] -`; - -exports[`graphql create incremental queries V1 with primary keys info 1`] = ` -[ - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { deployments (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid source application { applicationId: id } build { buildId: pipeline { organization { source uid } uid } uid } metadata { refreshedAt } } } } }", - "name": "cicd_Deployment", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { compute { applications (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id name displayName platform metadata { refreshedAt } } } } }", - "name": "compute_Application", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { builds (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid name number pipeline { pipelineId: organization { source uid } uid } metadata { refreshedAt } } } } }", - "name": "cicd_Build", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { pipelines (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid name organization { organizationId: source uid } metadata { refreshedAt } } } } }", - "name": "cicd_Pipeline", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { organizations (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid name source metadata { refreshedAt } } } } }", - "name": "cicd_Organization", - }, -] -`; - -exports[`graphql create incremental queries V1 with primary keys info 2`] = ` -[ - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { deployments (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid source application { id } build { pipeline { organization { source uid } uid } uid } metadata { refreshedAt } } } } }", - "name": "cicd_Deployment", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { compute { applications (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id name displayName platform metadata { refreshedAt } } } } }", - "name": "compute_Application", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { builds (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid name number pipeline { organization { source uid } uid } metadata { refreshedAt } } } } }", - "name": "cicd_Build", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { pipelines (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid name organization { source uid } metadata { refreshedAt } } } } }", - "name": "cicd_Pipeline", - }, - { - "gql": "query ($from: builtin_BigInt!, $to: builtin_BigInt!) { cicd { organizations (filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from, lessThan: $to}}) { nodes { id uid name source metadata { refreshedAt } } } } }", - "name": "cicd_Organization", - }, -] -`; - exports[`graphql create incremental queries V2 1`] = ` [ { @@ -383,7 +71,7 @@ exports[`graphql create incremental queries V2 2`] = ` exports[`graphql create incremental queries V2 3`] = ` [ { - "gql": "query ($from: timestamptz!, $to: timestamptz!) { cicd_Build (where: {refreshedAt: {_gte: $from, _lt: $to}}) { id createdAt endedAt name number origin refreshedAt startedAt status statusCategory statusDetail uid url } }", + "gql": "query ($from: timestamptz!, $to: timestamptz!) { cicd_Build (where: {refreshedAt: {_gte: $from, _lt: $to}}) { id createdAt endedAt name number origin pipeline { id } refreshedAt startedAt status statusCategory statusDetail uid url } }", "name": "cicd_Build", }, { diff --git a/test/adapter.test.ts b/test/adapter.test.ts deleted file mode 100644 index 1f39565..0000000 --- a/test/adapter.test.ts +++ /dev/null @@ -1,1098 +0,0 @@ -import 'jest-extended'; - -import * as gql from 'graphql'; -import _ from 'lodash'; - -import {FarosClient} from '../src'; -import * as sut from '../src/adapter'; -import { - graphSchemaForAdapterTest as v1Schema, - graphSchemaV2ForAdapterTest as v2Schema, - toArray, - toIterator, -} from './helpers'; - -describe('AST utilities', () => { - // Sorts fields and removes directives and aliases - function normalizeAST(ast: gql.DocumentNode): gql.DocumentNode { - return gql.visit(ast, { - Directive() { - return null; - }, - Field(node) { - if (node.alias) { - return _.omit(node, 'alias'); - } - return undefined; - }, - SelectionSet: { - leave(node) { - return { - ...node, - selections: _.sortBy(node.selections, (s) => { - if (s.kind === 'Field') { - return s.name.value; - } - return undefined; - }) - }; - } - } - }); - } - - interface ConversionAssertion { - readonly v1Query: string; - readonly v2Query: string; - readonly fieldPaths?: sut.FieldPaths; - readonly failureMessage?: string; - } - - function expectConversion(assertion: ConversionAssertion): void { - const message = assertion.failureMessage; - const v1TypeInfo = new gql.TypeInfo(v1Schema); - const v1AST = normalizeAST(gql.parse(assertion.v1Query)); - const v2AST = normalizeAST(gql.parse(assertion.v2Query)); - const actualV2AST = normalizeAST( - sut.asV2AST(v1AST, v1TypeInfo) as gql.DocumentNode - ); - expect(gql.validate(v1Schema, v1AST), message).toBeEmpty(); - expect(gql.validate(v2Schema, v2AST), message).toBeEmpty(); - expect(gql.print(actualV2AST), message).toEqual(gql.print(v2AST)); - const fieldPaths = assertion.fieldPaths; - if (fieldPaths) { - expect(sut.getFieldPaths(v1AST, v1TypeInfo), message).toEqual(fieldPaths); - } - } - - test('rewrite namespace', () => { - expectConversion({ - v1Query: ` - { - cicd { - deploymentChangesets { - nodes { - deployment { - id - uid - } - commit { - id - sha - } - } - } - } - } - `, - v2Query: ` - { - cicd_DeploymentChangeset { - deployment { - id - uid - } - commit { - id - sha - } - } - } - `, - fieldPaths: { - cicd_DeploymentChangeset: { - path: 'cicd.deploymentChangesets.nodes', - nestedPaths: { - 'deployment.id': { - path: 'deployment.id', - type: 'string' - }, - 'deployment.uid': { - path: 'deployment.uid', - type: 'string' - }, - 'commit.id': { - path: 'commit.id', - type: 'string' - }, - 'commit.sha': { - path: 'commit.sha', - type: 'string' - } - } - } - } - }); - }); - - test('flatten metadata fields', () => { - expectConversion({ - v1Query: ` - { - cicd { - deploymentChangesets { - nodes { - deployment { - uid - metadata { - origin - isPhantom - refreshedAt - } - } - commit { - sha - metadata { - origin - isPhantom - refreshedAt - } - } - } - } - } - } - `, - v2Query: ` - { - cicd_DeploymentChangeset { - deployment { - uid - origin - isPhantom - refreshedAt - } - commit { - sha - origin - isPhantom - refreshedAt - } - } - } - `, - fieldPaths: { - cicd_DeploymentChangeset: { - path: 'cicd.deploymentChangesets.nodes', - nestedPaths: { - 'deployment.uid': { - path: 'deployment.uid', - type: 'string' - }, - 'deployment.origin': { - path: 'deployment.metadata.origin', - type: 'string' - }, - 'deployment.isPhantom': { - path: 'deployment.metadata.isPhantom', - type: 'boolean' - }, - 'deployment.refreshedAt': { - path: 'deployment.metadata.refreshedAt', - type: 'epoch_millis_string' - }, - 'commit.sha': { - path: 'commit.sha', - type: 'string' - }, - 'commit.origin': { - path: 'commit.metadata.origin', - type: 'string' - }, - 'commit.isPhantom': { - path: 'commit.metadata.isPhantom', - type: 'boolean' - }, - 'commit.refreshedAt': { - path: 'commit.metadata.refreshedAt', - type: 'epoch_millis_string' - } - } - } - } - }); - }); - - test('flatten embedded fields', () => { - expectConversion({ - v1Query: ` - { - cicd { - deployments { - nodes { - uid - env { - category - detail - } - } - } - } - } - `, - v2Query: ` - { - cicd_Deployment { - uid - envCategory - envDetail - } - } - `, - fieldPaths: { - cicd_Deployment: { - path: 'cicd.deployments.nodes', - nestedPaths: { - uid: { - path: 'uid', - type: 'string' - }, - envCategory: { - path: 'env.category', - type: 'string' - }, - envDetail: { - path: 'env.detail', - type: 'string' - } - } - } - } - }); - }); - - test('remove nodes', () => { - expectConversion({ - v1Query: ` - { - vcs { - commits { - nodes { - sha - deployments { - nodes { - deployment { - uid - } - } - } - } - } - } - } - `, - v2Query: ` - { - vcs_Commit { - sha - deployments { - deployment { - uid - } - } - } - } - `, - fieldPaths: { - vcs_Commit: { - path: 'vcs.commits.nodes', - nestedPaths: { - sha: { - path: 'sha', - type: 'string' - }, - deployments: { - path: 'deployments.nodes', - nestedPaths: { - 'deployment.uid': { - path: 'deployment.uid', - type: 'string' - } - } - } - } - } - } - }); - }); - - test('remove nested fields from embedded object lists', () => { - expectConversion({ - v1Query: ` - { - tms { - tasks { - nodes { - uid - additionalFields { - name - value - } - statusChangelog { - changedAt - status { - category - detail - } - } - } - } - } - } - `, - v2Query: ` - { - tms_Task { - uid - additionalFields - statusChangelog - } - } - `, - fieldPaths: { - tms_Task: { - path: 'tms.tasks.nodes', - nestedPaths: { - uid: { - path: 'uid', - type: 'string' - }, - additionalFields: { - path: 'additionalFields', - nestedPaths: { - name: { - path: 'name', - type: 'string' - }, - value: { - path: 'value', - type: 'string' - } - } - }, - statusChangelog: { - path: 'statusChangelog', - nestedPaths: { - changedAt: { - path: 'changedAt', - type: 'epoch_millis' - }, - 'status.category': { - path: 'status.category', - type: 'string' - }, - 'status.detail': { - path: 'status.detail', - type: 'string' - } - } - } - } - } - } - }); - }); - - test('rename first field argument to limit', () => { - expectConversion({ - v1Query: ` - { - cicd { - deployments { - nodes { - uid - changeset(first: 1) { - nodes { - commit { - sha - } - } - } - } - } - } - } - `, - v2Query: ` - { - cicd_Deployment { - uid - changeset(limit: 1) { - commit { - sha - } - } - } - } - `, - fieldPaths: { - cicd_Deployment: { - path: 'cicd.deployments.nodes', - nestedPaths: { - uid: { - path: 'uid', - type: 'string' - }, - changeset: { - path: 'changeset.nodes', - nestedPaths: { - 'commit.sha': { - path: 'commit.sha', - type: 'string' - } - } - } - } - } - } - }); - }); - - test('all rules', () => { - expectConversion({ - v1Query: ` - { - tms { - projects { - nodes { - uid - description - createdAt - metadata { - origin - refreshedAt - } - releases { - nodes { - release { - uid - releasedAt - metadata { - origin - refreshedAt - } - } - } - } - tasks { - nodes { - task { - uid - type { - category - detail - } - metadata { - origin - refreshedAt - } - parent { - uid - createdAt - } - additionalFields { - name - value - } - statusChangelog { - changedAt - status { - category - detail - } - } - } - } - } - } - } - } - } - `, - v2Query: ` - { - tms_Project { - uid - description - createdAt - origin - refreshedAt - releases { - release { - uid - releasedAt - origin - refreshedAt - } - } - tasks { - task { - uid - typeCategory - typeDetail - origin - refreshedAt - parent { - uid - createdAt - } - additionalFields - statusChangelog - } - } - } - } - `, - fieldPaths: { - tms_Project: { - path: 'tms.projects.nodes', - nestedPaths: { - uid: {path: 'uid', type: 'string'}, - description: {path: 'description', type: 'string'}, - createdAt: {path: 'createdAt', type: 'epoch_millis_string'}, - origin: {path: 'metadata.origin', type: 'string'}, - refreshedAt: { - path: 'metadata.refreshedAt', - type: 'epoch_millis_string', - }, - releases: { - path: 'releases.nodes', - nestedPaths: { - 'release.uid': { - path: 'release.uid', - type: 'string', - }, - 'release.releasedAt': { - path: 'release.releasedAt', - type: 'epoch_millis_string', - }, - 'release.origin': { - path: 'release.metadata.origin', - type: 'string', - }, - 'release.refreshedAt': { - path: 'release.metadata.refreshedAt', - type: 'epoch_millis_string', - }, - }, - }, - tasks: { - path: 'tasks.nodes', - nestedPaths: { - 'task.uid': { - path: 'task.uid', - type: 'string', - }, - 'task.typeCategory': { - path: 'task.type.category', - type: 'string', - }, - 'task.typeDetail': { - path: 'task.type.detail', - type: 'string', - }, - 'task.origin': { - path: 'task.metadata.origin', - type: 'string', - }, - 'task.refreshedAt': { - path: 'task.metadata.refreshedAt', - type: 'epoch_millis_string', - }, - 'task.parent.uid': { - path: 'task.parent.uid', - type: 'string', - }, - 'task.parent.createdAt': { - path: 'task.parent.createdAt', - type: 'epoch_millis_string', - }, - 'task.additionalFields': { - path: 'task.additionalFields', - nestedPaths: { - name: {path: 'name', type: 'string'}, - value: {path: 'value', type: 'string'}, - }, - }, - 'task.statusChangelog': { - path: 'task.statusChangelog', - nestedPaths: { - changedAt: { - path: 'changedAt', - type: 'epoch_millis', - }, - 'status.category': { - path: 'status.category', - type: 'string', - }, - 'status.detail': { - path: 'status.detail', - type: 'string', - }, - }, - }, - }, - }, - }, - }, - }, - }); - }); -}); - -describe('query adapter', () => { - interface RunV1Query { - readonly v1Query: string; - readonly v2Nodes: any[]; - } - - function asV1Nodes(run: RunV1Query): AsyncIterable { - const nodeIterable = () => toIterator(run.v2Nodes); - const faros: FarosClient = {graphVersion: 'v2', nodeIterable} as any; - const adapter = new sut.QueryAdapter(faros, v1Schema); - return adapter.nodes('default', run.v1Query); - } - - test('query', async () => { - const v1Nodes = asV1Nodes({ - v1Query: ` - { - cicd { - deploymentChangesets { - nodes { - deployment { - uid - } - commit { - sha - } - } - } - } - } - `, - v2Nodes: [ - {deployment: {uid: 'u1'}, commit: {sha: 's1'}}, - {deployment: {uid: 'u2'}, commit: {sha: 's2'}} - ] - }); - await expect(toArray(v1Nodes)).resolves.toEqual([ - {deployment: {uid: 'u1'}, commit: {sha: 's1'}}, - {deployment: {uid: 'u2'}, commit: {sha: 's2'}} - ]); - }); - - test('query with embedded fields', async () => { - const v1Nodes = asV1Nodes({ - v1Query: ` - { - cicd { - deployments { - nodes { - uid - env { - category - detail - } - } - } - } - } - `, - v2Nodes: [ - { - uid: 'u1', - envCategory: 'c1', - envDetail: 'd1' - }, - { - uid: 'u2', - envCategory: 'c2', - envDetail: 'd2' - }, - ] - }); - await expect(toArray(v1Nodes)).resolves.toEqual([ - {uid: 'u1', env: {category: 'c1', detail: 'd1'}}, - {uid: 'u2', env: {category: 'c2', detail: 'd2'}}, - ]); - }); - - test('query with embedded object list fields', async () => { - const v1Nodes = asV1Nodes({ - v1Query: ` - { - tms { - tasks { - nodes { - uid - additionalFields { - name - value - } - statusChangelog { - changedAt - status { - category - detail - } - } - } - } - } - } - `, - v2Nodes: [ - { - uid: 'u1', - additionalFields: [ - {name: 'n1a', value: 'v1a'}, - {name: 'n1b', value: 'v1b'}, - ], - statusChangelog: [ - { - changedAt: 1667871145261, - status: {category: 'c1a', detail: 'd1a'} - }, - { - changedAt: '2022-11-08T01:32:25.261Z', - status: {category: 'c1b', detail: 'd1b'} - } - ] - }, - { - uid: 'u2', - additionalFields: [ - {name: 'n2a', value: 'v2a'}, - {name: 'n2b', value: 'v2b'}, - ], - statusChangelog: [ - { - changedAt: 1667871145261, - status: {category: 'c2a', detail: 'd2a'} - }, - { - changedAt: '2022-11-08T01:32:25.261Z', - status: {category: 'c2b', detail: 'd2b'} - } - ] - }, - ] - }); - await expect(toArray(v1Nodes)).resolves.toEqual([ - { - uid: 'u1', - additionalFields: [ - {name: 'n1a', value: 'v1a'}, - {name: 'n1b', value: 'v1b'}, - ], - statusChangelog: [ - { - changedAt: 1667871145261, - status: {category: 'c1a', detail: 'd1a'} - }, - { - changedAt: 1667871145261, - status: {category: 'c1b', detail: 'd1b'} - } - ] - }, - { - uid: 'u2', - additionalFields: [ - {name: 'n2a', value: 'v2a'}, - {name: 'n2b', value: 'v2b'}, - ], - statusChangelog: [ - { - changedAt: 1667871145261, - status: {category: 'c2a', detail: 'd2a'} - }, - { - changedAt: 1667871145261, - status: {category: 'c2b', detail: 'd2b'} - } - ] - }, - ]); - }); - - test('query with nested nodes', async () => { - const v1Nodes = asV1Nodes({ - v1Query: ` - { - tms { - projects { - nodes { - uid - tasks { - nodes { - task { - uid - createdAt - } - } - } - } - } - } - } - `, - v2Nodes: [ - { - uid: 'u1', - tasks: [ - {task: {uid: 'u1a', createdAt: '2022-11-08T01:32:25.261Z'}}, - {task: {uid: 'u1b', createdAt: '2022-11-08T01:32:25.261Z'}} - ], - }, - { - uid: 'u2', - tasks: [ - {task: {uid: 'u2a', createdAt: '2022-11-08T01:32:25.261Z'}}, - {task: {uid: 'u2b', createdAt: '2022-11-08T01:32:25.261Z'}} - ] - } - ] - }); - await expect(toArray(v1Nodes)).resolves.toEqual([ - { - uid: 'u1', - tasks: { - nodes: [ - {task: {uid: 'u1a', createdAt: '1667871145261'}}, - {task: {uid: 'u1b', createdAt: '1667871145261'}} - ] - } - }, - { - uid: 'u2', - tasks: { - nodes: [ - {task: {uid: 'u2a', createdAt: '1667871145261'}}, - {task: {uid: 'u2b', createdAt: '1667871145261'}} - ] - } - } - ]); - }); - - test('query with metadata', async () => { - const v1Nodes = asV1Nodes({ - v1Query: ` - { - tms { - tasks { - nodes { - uid - metadata { - origin - isPhantom - refreshedAt - } - } - } - } - } - `, - v2Nodes: [ - { - uid: 'u1', - origin: 'o1', - isPhantom: true, - refreshedAt: '2022-11-08T01:32:25.261Z' - }, - { - uid: 'u2', - origin: 'o2', - isPhantom: false, - refreshedAt: '2022-11-08T01:32:25.261Z' - } - ] - }); - await expect(toArray(v1Nodes)).resolves.toEqual([ - { - uid: 'u1', - metadata: { - origin: 'o1', - isPhantom: true, - refreshedAt: '1667871145261' - } - }, - { - uid: 'u2', - metadata: { - origin: 'o2', - isPhantom: false, - refreshedAt: '1667871145261' - } - } - ]); - }); - - test('query with primitive list', async () => { - const v1Nodes = asV1Nodes({ - v1Query: ` - { - vcs { - repositories { - nodes { - topics - } - } - } - } - `, - v2Nodes: [ - {topics: ['t1a', 't1b']}, - {topics: ['t2a', 't2b']} - ] - }); - await expect(toArray(v1Nodes)).resolves.toEqual([ - {topics: ['t1a', 't1b']}, - {topics: ['t2a', 't2b']}, - ]); - }); - - test('query with timestamp type', async () => { - const v1Nodes = asV1Nodes({ - v1Query: ` - { - cicd { - deployments { - nodes { - startedAt - } - } - } - } - `, - v2Nodes: [{startedAt: '2022-11-08T01:32:25.261Z'}], - }); - await expect(toArray(v1Nodes)).resolves.toEqual([ - {startedAt: '1667871145261'}, - ]); - }); - - test('throws error for invalid timestamp value', () => { - const v1Nodes = asV1Nodes({ - v1Query: ` - { - cicd { - deployments { - nodes { - startedAt - } - } - } - } - `, - v2Nodes: [{startedAt: 'invalid'}], - }); - return expect(() => toArray(v1Nodes)).rejects.toThrow(/failed to convert/); - }); - - test('query with double type', async () => { - const v1Nodes = asV1Nodes({ - v1Query: ` - { - geo { - coordinates { - nodes { - lat - lon - } - } - } - } - `, - v2Nodes: [ - {lat: '1.23456789', lon: '1.23456789'}, - {lat: '41.8839113', lon: '-87.6340954'} - ] - }); - await expect(toArray(v1Nodes)).resolves.toEqual([ - {lat: 1.23456789, lon: 1.23456789}, - {lat: 41.8839113, lon: -87.6340954} - ]); - }); - - test('throws error for invalid double value', () => { - const v1Nodes = asV1Nodes({ - v1Query: ` - { - geo { - coordinates { - nodes { - lat - lon - } - } - } - } - `, - v2Nodes: [ - {lat: '1.23456789', lon: 'invalid'}, - ] - }); - return expect(() => toArray(v1Nodes)).rejects.toThrow(/failed to convert/); - }); - - test('query with long type', async () => { - const v1Nodes = asV1Nodes({ - v1Query: ` - { - cal { - events { - nodes { - durationMs - } - } - } - } - `, - v2Nodes: [{durationMs: '123456789'}], - }); - await expect(toArray(v1Nodes)).resolves.toEqual([ - {durationMs: '123456789'}, - ]); - }); - - test('throws error for invalid long value', () => { - const v1Nodes = asV1Nodes({ - v1Query: ` - { - cal { - events { - nodes { - durationMs - } - } - } - } - `, - v2Nodes: [{durationMs: 'invalid'}], - }); - return expect(() => toArray(v1Nodes)).rejects.toThrow(/failed to convert/); - }); - - test('edge case for v1 long and v2 int', async () => { - const v1Nodes = asV1Nodes({ - v1Query: ` - { - vcs { - pullRequestComments { - nodes { - number - } - } - } - } - `, - v2Nodes: [{number: 123456789}], - }); - await expect(toArray(v1Nodes)).resolves.toEqual([ - {number: '123456789'}, - ]); - }); -}); diff --git a/test/graphql.test.ts b/test/graphql.test.ts index d908afe..7f7c37f 100644 --- a/test/graphql.test.ts +++ b/test/graphql.test.ts @@ -4,9 +4,6 @@ import * as gql from 'graphql'; import * as sut from '../src/graphql/graphql'; import { - graphSchema, - graphSchemaForEmbeddedFieldsTest, - graphSchemaForPrimaryKeysTest, graphSchemaV2, graphSchemaV2ForForeignKeyExclusionTest, graphSchemaV2ForPrimaryKeysTest, @@ -15,27 +12,6 @@ import { } from './helpers'; describe('graphql', () => { - test('query nodes paths', async () => { - const query = await loadQueryFile('deployments.gql'); - expect(sut.queryNodesPaths(query)).toIncludeSameMembers([ - ['cicd', 'deployments', 'nodes'], - ['cicd', 'deployments', 'nodes', 'changeset', 'nodes'], - ]); - }); - - test('paginated query', async () => { - const query = await loadQueryFile('deployments.gql'); - const expectedQuery = await loadQueryFile('paginated-deployments.gql'); - const paginatedQuery = sut.paginatedQuery(query); - expect(paginatedQuery.edgesPath).toEqual(['cicd', 'deployments', 'edges']); - expect(paginatedQuery.pageInfoPath).toEqual([ - 'cicd', - 'deployments', - 'pageInfo', - ]); - expect(paginatedQuery.query).toEqual(expectedQuery); - }); - test('flatten nodes V2', async () => { const nodes = [ { @@ -92,12 +68,13 @@ describe('graphql', () => { case 'uid': expect(type).toEqual(gql.GraphQLString); break; - case 'tags': { - expect(type.constructor.name).toEqual('GraphQLList'); - const ofType = - (type as gql.GraphQLList).ofType; - expect(ofType.name).toEqual('jsonb'); - } + case 'tags': + { + expect(type.constructor.name).toEqual('GraphQLList'); + const ofType = (type as gql.GraphQLList) + .ofType; + expect(ofType.name).toEqual('jsonb'); + } break; default: fail(`unexpected field ${id}`); @@ -219,20 +196,20 @@ describe('graphql', () => { test('paginated offset/limit v2 query', async () => { const query = await loadQueryFile('commits-v2.gql'); const paginatedQuery = sut.paginateWithOffsetLimitV2(query); - const expectedQuery = - await loadQueryFile('paginated-commits-offset-limit-v2.gql'); + const expectedQuery = await loadQueryFile( + 'paginated-commits-offset-limit-v2.gql' + ); expect(paginatedQuery.query).toEqual(expectedQuery); - expect(paginatedQuery.edgesPath).toEqual([ - 'vcs_Commit', - ]); + expect(paginatedQuery.edgesPath).toEqual(['vcs_Commit']); expect(paginatedQuery.pageInfoPath).toBeEmpty(); }); test('paginated keyset v2 query', async () => { const query = await loadQueryFile('incidents-v2.gql'); const paginatedQuery = sut.paginateWithKeysetV2(query); - const expectedQuery = - await loadQueryFile('paginated-incidents-keyset-v2.gql'); + const expectedQuery = await loadQueryFile( + 'paginated-incidents-keyset-v2.gql' + ); expect(paginatedQuery.query).toEqual(expectedQuery); expect(paginatedQuery.edgesPath).toEqual(['ims_Incident']); expect(paginatedQuery.edgeIdPath).toEqual(['_id']); @@ -242,8 +219,9 @@ describe('graphql', () => { test('paginated keyset v2 query with existing where clause', async () => { const query = await loadQueryFile('commits-v2.gql'); const paginatedQuery = sut.paginateWithKeysetV2(query); - const expectedQuery = - await loadQueryFile('paginated-commits-keyset-v2.gql'); + const expectedQuery = await loadQueryFile( + 'paginated-commits-keyset-v2.gql' + ); expect(paginatedQuery.query).toEqual(expectedQuery); expect(paginatedQuery.edgesPath).toEqual(['vcs_Commit']); expect(paginatedQuery.edgeIdPath).toEqual(['_id']); @@ -251,24 +229,13 @@ describe('graphql', () => { }); test('build incremental V2', () => { - const type = graphSchemaV2.getType('cicd_Build'); - const query1 = sut.buildIncrementalQueryV2(type as gql.GraphQLObjectType); - expect(query1).toMatchSnapshot(); - const query2 = sut.buildIncrementalQueryV2( - type as gql.GraphQLObjectType, - false - ); - expect(query2).toMatchSnapshot(); - }); - - test('build incremental V1', () => { - const type = graphSchema.getType('cicd_Build'); - const query1 = sut.buildIncrementalQueryV1(type as gql.GraphQLObjectType); + const type = graphSchemaV2.getType('cicd_Build') as gql.GraphQLObjectType; + const query1 = sut.buildIncrementalQueryV2({type}); expect(query1).toMatchSnapshot(); - const query2 = sut.buildIncrementalQueryV1( - type as gql.GraphQLObjectType, - false - ); + const query2 = sut.buildIncrementalQueryV2({ + type: type as gql.GraphQLObjectType, + avoidCollisions: false, + }); expect(query2).toMatchSnapshot(); }); @@ -336,495 +303,6 @@ describe('graphql', () => { ]); }); - test('flatten nodes', async () => { - const nodes = [ - { - deploymentUid: 'deploy1', - application: {appName: 'app1'}, - changeset: { - nodes: [{commit: {sha: 'sha1'}}, {commit: {sha: 'sha2'}}], - }, - }, - { - deploymentUid: 'deploy2', - application: {appName: 'app2'}, - changeset: { - nodes: [{commit: {sha: 'sha3'}}, {commit: {sha: 'sha4'}}], - }, - }, - { - deploymentUid: 'deploy3', - application: {appName: 'app3'}, - changeset: {nodes: []}, - }, - { - deploymentUid: 'deploy4', - application: {appName: 'app4'}, - changeset: {nodes: null}, - }, - ]; - - const query = await loadQueryFile('deployments.gql'); - const ctx = sut.flatten(query, graphSchema); - const flattenedNodes = sut.flattenIterable(ctx, nodes); - expect(ctx.fieldTypes).toEqual( - new Map([ - ['deployment_uid', gql.GraphQLID], - ['app_name', gql.GraphQLString], - ['sha', gql.GraphQLString], - ]) - ); - expect(Array.from(ctx.params.keys())).toIncludeSameMembers(['from']); - function assertTypeAndValue(fld: string, type: string) { - expect(gql.isScalarType(ctx.params.get(fld))).toBeTrue(); - expect((ctx.params.get(fld) as gql.GraphQLScalarType).name).toEqual(type); - } - assertTypeAndValue('from', 'builtin_BigInt'); - expect(await toArray(flattenedNodes)).toIncludeSameMembers([ - {deployment_uid: 'deploy1', app_name: 'app1', sha: 'sha1'}, - {deployment_uid: 'deploy1', app_name: 'app1', sha: 'sha2'}, - {deployment_uid: 'deploy2', app_name: 'app2', sha: 'sha3'}, - {deployment_uid: 'deploy2', app_name: 'app2', sha: 'sha4'}, - {deployment_uid: 'deploy3', app_name: 'app3'}, - {deployment_uid: 'deploy4', app_name: 'app4'}, - ]); - }); - - test('flatten nodes with cross join', async () => { - const nodes = [ - { - uid: 'task1', - creator: {creatorUid: 'creator1'}, - projects: { - nodes: [ - {project: {projectUid: 'project1'}}, - { - project: { - projectUid: 'project2', - boards: { - nodes: [ - {board: {boardUid: 'board1'}}, - {board: {boardUid: 'board2'}}, - ], - }, - }, - }, - ], - }, - assignees: { - nodes: [ - {assignee: {assigneeUid: 'assignee1'}}, - {assignee: {assigneeUid: 'assignee2'}}, - ], - }, - }, - { - uid: 'task2', - creator: {creatorUid: 'creator2'}, - projects: { - nodes: [{project: {projectUid: 'project3'}}], - }, - assignees: { - nodes: [{assignee: {assigneeUid: 'assignee3'}}], - }, - }, - { - uid: 'task3', - creator: {creatorUid: 'creator3'}, - projects: { - nodes: [{project: {projectUid: 'project4'}}], - }, - }, - { - uid: 'task4', - creator: {creatorUid: 'creator4'}, - projects: {nodes: []}, - }, - { - uid: 'task5', - creator: {creatorUid: 'creator5'}, - projects: {nodes: null}, - }, - ]; - - const query = await loadQueryFile('tasks.gql'); - const ctx = sut.flatten(query, graphSchema); - const flattenedNodes = sut.flattenIterable(ctx, nodes); - expect(ctx.fieldTypes).toEqual( - new Map([ - ['uid', gql.GraphQLID], - ['creator_uid', gql.GraphQLID], - ['project_uid', gql.GraphQLID], - ['board_uid', gql.GraphQLString], - ['assignee_uid', gql.GraphQLID], - ]) - ); - expect(await toArray(flattenedNodes)).toIncludeSameMembers([ - { - uid: 'task1', - creator_uid: 'creator1', - project_uid: 'project1', - assignee_uid: 'assignee1', - }, - { - uid: 'task1', - creator_uid: 'creator1', - project_uid: 'project1', - assignee_uid: 'assignee2', - }, - { - uid: 'task1', - creator_uid: 'creator1', - project_uid: 'project2', - board_uid: 'board1', - assignee_uid: 'assignee1', - }, - { - uid: 'task1', - creator_uid: 'creator1', - project_uid: 'project2', - board_uid: 'board2', - assignee_uid: 'assignee1', - }, - { - uid: 'task1', - creator_uid: 'creator1', - project_uid: 'project2', - board_uid: 'board1', - assignee_uid: 'assignee2', - }, - { - uid: 'task1', - creator_uid: 'creator1', - project_uid: 'project2', - board_uid: 'board2', - assignee_uid: 'assignee2', - }, - { - uid: 'task2', - creator_uid: 'creator2', - project_uid: 'project3', - assignee_uid: 'assignee3', - }, - { - uid: 'task3', - creator_uid: 'creator3', - project_uid: 'project4', - }, - { - uid: 'task4', - creator_uid: 'creator4', - }, - { - uid: 'task5', - creator_uid: 'creator5', - }, - ]); - }); - - test('flatten empty nodes', async () => { - const nodes: any[] = []; - const query = await loadQueryFile('deployments.gql'); - const ctx = sut.flatten(query, graphSchema); - const flattenedNodes = sut.flattenIterable(ctx, nodes); - expect(ctx.fieldTypes).toEqual( - new Map([ - ['deployment_uid', gql.GraphQLID], - ['app_name', gql.GraphQLString], - ['sha', gql.GraphQLString], - ]) - ); - expect(await toArray(flattenedNodes)).toBeEmpty(); - }); - - // Schema used for default value tests - const graphDefaultSchema = new gql.GraphQLSchema({ - query: new gql.GraphQLObjectType({ - name: 'root', - fields: { - nodes: { - type: new gql.GraphQLList( - new gql.GraphQLObjectType({ - name: 'nodes', - fields: { - boolField: {type: gql.GraphQLBoolean}, - doubleField: { - type: new gql.GraphQLScalarType({ - name: 'Double', - serialize(value) { - return value; - }, - }), - }, - floatField: {type: gql.GraphQLFloat}, - intField: {type: gql.GraphQLInt}, - longField: { - type: new gql.GraphQLScalarType({ - name: 'Long', - serialize(value) { - return value; - }, - }), - }, - strField: {type: gql.GraphQLString}, - // Nested nodes field - nodesField: { - type: new gql.GraphQLObjectType({ - name: 'nestedNodes', - fields: { - nodes: { - type: new gql.GraphQLList( - new gql.GraphQLObjectType({ - name: 'nestedNode', - fields: { - nestedStrField: {type: gql.GraphQLString}, - }, - }) - ), - }, - }, - }), - }, - }, - }) - ), - }, - }, - }), - }); - - test('default values', async () => { - const query = ` - query { - nodes { - boolField @default(value: "true") - doubleField @default(value: "1.234") - floatField @default(value: "1.23") - intField @default(value: "123") - longField @default(value: "1234") - strField @default(value: "str") - nodesField { - nodes { - nestedStrField @default(value: "str") - } - } - } - } - `; - const nodes = [ - // All fields undefined - {}, - // All fields null - { - boolField: null, - doubleField: null, - floatField: null, - intField: null, - longField: null, - strField: null, - nodesFields: { - nodes: [{nestedStrField: null}], - }, - }, - // All fields "empty" values. Defaults should NOT be used. - { - boolField: false, - doubleField: 0.0, - floatField: 0.0, - intField: 0, - longField: 0, - strField: '', - nodesField: { - nodes: [{nestedStrField: ''}], - }, - }, - ]; - const ctx = sut.flatten(query, graphDefaultSchema); - const flattenedNodes = sut.flattenIterable(ctx, nodes); - expect(await toArray(flattenedNodes)).toIncludeSameMembers([ - { - bool_field: true, - double_field: 1.234, - float_field: 1.23, - int_field: 123, - long_field: 1234, - str_field: 'str', - nested_str_field: 'str', - }, - { - bool_field: true, - double_field: 1.234, - float_field: 1.23, - int_field: 123, - long_field: 1234, - str_field: 'str', - nested_str_field: 'str', - }, - { - bool_field: false, - double_field: 0.0, - float_field: 0.0, - int_field: 0, - long_field: 0, - str_field: '', - nested_str_field: '', - }, - ]); - }); - - test('invalid default values', () => { - const nullQuery = ` - query { - nodes { - strField @default(value: null) - } - } - `; - const noValueQuery = ` - query { - nodes { - strField @default - } - } - `; - const boolQuery = ` - query { - nodes { - boolField @default(value: "123") - } - } - `; - const doubleQuery = ` - query { - nodes { - doubleField @default(value: "") - } - } - `; - const floatQuery = ` - query { - nodes { - floatField @default(value: "true") - } - } - `; - const intQuery = ` - query { - nodes { - intField @default(value: "1.23") - } - } - `; - const longQuery = ` - query { - nodes { - longField @default(value: "1.234") - } - } - `; - const strQuery = ` - query { - nodes { - strField @default(value: 123) - } - } - `; - expect(() => sut.flatten(nullQuery, graphDefaultSchema)).toThrow( - 'invalid default on field \'nodes.strField\'' - ); - expect(() => sut.flatten(noValueQuery, graphDefaultSchema)).toThrow( - 'invalid default on field \'nodes.strField\'' - ); - expect(() => sut.flatten(boolQuery, graphDefaultSchema)).toThrow( - 'Boolean field \'nodes.boolField\' has invalid default' - ); - expect(() => sut.flatten(doubleQuery, graphDefaultSchema)).toThrow( - 'Double field \'nodes.doubleField\' has invalid default' - ); - expect(() => sut.flatten(floatQuery, graphDefaultSchema)).toThrow( - 'Float field \'nodes.floatField\' has invalid default' - ); - expect(() => sut.flatten(intQuery, graphDefaultSchema)).toThrow( - 'Int field \'nodes.intField\' has invalid default' - ); - expect(() => sut.flatten(longQuery, graphDefaultSchema)).toThrow( - 'Long field \'nodes.longField\' has invalid default' - ); - expect(() => sut.flatten(strQuery, graphDefaultSchema)).toThrow( - 'invalid default on field \'nodes.strField\'' - ); - }); - - test('query exceeds max node depth', async () => { - const query = await loadQueryFile('max-node-depth.gql'); - expect(() => sut.flatten(query, graphSchema)).toThrow( - 'query exceeds max node depth of 10' - ); - }); - - test('convert to incremental V1', () => { - const query = `query MyQuery { - vcs { - pullRequests { - nodes { - metadata { - refreshedAt - } - title - number - reviews { - nodes { - reviewer { - name - } - } - } - } - } - } - }`; - expect(sut.toIncrementalV1(query)).toMatchSnapshot(); - const queryWithout_refreshedAt = `query MyQuery { - vcs { - pullRequests { - nodes { - metadata { - origin - } - title - number - reviews { - nodes { - reviewer { - name - } - } - } - } - } - } - }`; - expect(sut.toIncrementalV1(queryWithout_refreshedAt)).toMatchSnapshot(); - const queryWithout_metadata = `query MyQuery { - vcs { - pullRequests { - nodes { - title - number - reviews { - nodes { - reviewer { - name - } - } - } - } - } - } - }`; - expect(sut.toIncrementalV1(queryWithout_metadata)).toMatchSnapshot(); - }); - test('convert to incremental V2', () => { const query = `query MyQuery { vcs_PullRequest { @@ -843,58 +321,22 @@ describe('graphql', () => { expect(sut.toIncrementalV2(query_without_refreshed_at)).toMatchSnapshot(); }); - test('create incremental queries V1', () => { - expect(sut.createIncrementalQueriesV1(graphSchema)).toMatchSnapshot(); - expect( - sut.createIncrementalQueriesV1(graphSchema, undefined, false) - ).toMatchSnapshot(); - }); - - test('create incremental queries V1 with embedded fields', () => { - expect( - sut.createIncrementalQueriesV1(graphSchemaForEmbeddedFieldsTest) - ).toMatchSnapshot(); - expect( - sut.createIncrementalQueriesV1( - graphSchemaForEmbeddedFieldsTest, - undefined, - false - ) - ).toMatchSnapshot(); - }); - - test('create incremental queries V1 with primary keys info', () => { - const primaryKeys = { - cicd_Build: ['pipeline', 'uid'], - cicd_Deployment: ['source', 'uid'], - cicd_Organization: ['source', 'uid'], - cicd_Pipeline: ['organization', 'uid'], - cicd_Repository: ['organization', 'uid'], - }; - expect( - sut.createIncrementalQueriesV1(graphSchemaForPrimaryKeysTest, primaryKeys) - ).toMatchSnapshot(); + test('create incremental queries V2', () => { expect( - sut.createIncrementalQueriesV1( - graphSchemaForPrimaryKeysTest, - primaryKeys, - false - ) + sut.createIncrementalQueriesV2({graphSchema: graphSchemaV2}) ).toMatchSnapshot(); - }); - - test('create incremental queries V2', () => { - expect(sut.createIncrementalQueriesV2(graphSchemaV2)).toMatchSnapshot(); expect( - sut.createIncrementalQueriesV2(graphSchemaV2, undefined, undefined, false) + sut.createIncrementalQueriesV2({ + graphSchema: graphSchemaV2, + avoidCollisions: false, + }) ).toMatchSnapshot(); expect( - sut.createIncrementalQueriesV2(graphSchemaV2, - undefined, - undefined, - false, - true, - ), + sut.createIncrementalQueriesV2({ + graphSchema: graphSchemaV2, + avoidCollisions: false, + scalarsOnly: true, + }) ).toMatchSnapshot(); }); @@ -923,12 +365,12 @@ describe('graphql', () => { cicd_Repository: organizationReferences, }; expect( - sut.createIncrementalQueriesV2( - graphSchemaV2ForPrimaryKeysTest, + sut.createIncrementalQueriesV2({ + graphSchema: graphSchemaV2ForPrimaryKeysTest, primaryKeys, references, - false - ) + avoidCollisions: false + }) ).toMatchSnapshot(); }); @@ -941,10 +383,10 @@ describe('graphql', () => { cicd_Repository: ['organizationId', 'uid'], }; expect(() => - sut.createIncrementalQueriesV2( - graphSchemaV2ForPrimaryKeysTest, + sut.createIncrementalQueriesV2({ + graphSchema: graphSchemaV2ForPrimaryKeysTest, primaryKeys - ) + }) ).toThrowErrorMatchingInlineSnapshot( '"expected organizationId to be a field of cicd_Pipeline"' ); @@ -965,11 +407,11 @@ describe('graphql', () => { cicd_Repository: organizationReferences, }; expect(() => - sut.createIncrementalQueriesV2( - graphSchemaV2ForPrimaryKeysTest, + sut.createIncrementalQueriesV2({ + graphSchema: graphSchemaV2ForPrimaryKeysTest, primaryKeys, references - ) + }) ).toThrowErrorMatchingSnapshot(); }); @@ -994,32 +436,15 @@ describe('graphql', () => { cicd_Pipeline: organizationReferences, }; expect( - sut.createIncrementalQueriesV2( - graphSchemaV2ForForeignKeyExclusionTest, + sut.createIncrementalQueriesV2({ + graphSchema: graphSchemaV2ForForeignKeyExclusionTest, primaryKeys, references, - false - ) + avoidCollisions: false + }) ).toMatchSnapshot(); }); - test('path to model V1', () => { - const query = `query MyQuery { - cicd { - deployments { - nodes { - url - id - } - } - } - }`; - expect(sut.pathToModelV1(query, graphSchema)).toEqual({ - modelName: 'cicd_Deployment', - path: ['cicd', 'deployments', 'nodes'], - }); - }); - test('path to model V2', () => { const query = `query MyQuery { cicd_Build { diff --git a/test/helpers.ts b/test/helpers.ts index 8772dac..8638b0b 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -6,29 +6,10 @@ export const SCHEMA_DIR = path.join(__dirname, 'resources', 'schemas'); // This is a subset of our schema. Make sure to extend if needed // when adding tests. -// This schema is NOT a V1 copy of the V2 schema below -export const graphSchema = gql.buildSchema( - fs.readFileSync(path.join(SCHEMA_DIR, 'schema.gql'), 'utf-8') -); - -// This is a subset of our schema. Make sure to extend if needed -// when adding tests. -// This schema is NOT a V2 copy of the V1 schema above export const graphSchemaV2 = gql.buildSchema( fs.readFileSync(path.join(SCHEMA_DIR, 'schema-v2.gql'), 'utf-8') ); -export const graphSchemaForPrimaryKeysTest = gql.buildSchema( - fs.readFileSync(path.join(SCHEMA_DIR, 'v1_schema_for_pk_test.gql'), 'utf-8') -); - -export const graphSchemaForEmbeddedFieldsTest = gql.buildSchema( - fs.readFileSync( - path.join(SCHEMA_DIR, 'v1_schema_for_embedded_test.gql'), - 'utf-8' - ) -); - export const graphSchemaV2ForPrimaryKeysTest = gql.buildSchema( fs.readFileSync(path.join(SCHEMA_DIR, 'v2_schema_for_pk_test.gql'), 'utf-8') ); @@ -40,13 +21,6 @@ export const graphSchemaV2ForForeignKeyExclusionTest = gql.buildSchema( ) ); -export const graphSchemaForAdapterTest = gql.buildSchema( - fs.readFileSync( - path.join(SCHEMA_DIR, 'v1_schema_for_adapter_test.gql'), - 'utf-8' - ) -); - export const graphSchemaV2ForAdapterTest = gql.buildSchema( fs.readFileSync( path.join(SCHEMA_DIR, 'v2_schema_for_adapter_test.gql'), @@ -63,7 +37,7 @@ export async function loadQueryFile(name: string): Promise { } export async function toArray(it: AsyncIterable): Promise { - const arr = []; + const arr: T[] = []; for await (const i of it) { arr.push(i); } diff --git a/test/index.test.ts b/test/index.test.ts index 6cf7169..461771a 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -34,75 +34,60 @@ describe('index', () => { { data: { data: { - cicd: { - deployments: { - edges: [ - { - cursor: 'abc', - node: { - uid: 'deployment1', - application: {name: 'app1'}, - changeset: { - nodes: [{commit: {sha: 'sha1'}}], - }, - }, - }, - ], - pageInfo: { - hasNextPage: true, - }, + cicd_Deployment: [ + { + _id: 'abc', + uid: 'deployment1', + application: {appName: 'app1'}, + changeset: [{commit: {sha: 'sha1'}}], }, - }, + ], }, }, }, { data: { data: { - cicd: { - deployments: { - edges: [ - { - cursor: 'def', - node: { - uid: 'deployment2', - application: {name: 'app2'}, - changeset: { - nodes: [{commit: {sha: 'sha2'}}], - }, - }, - }, - ], - pageInfo: { - hasNextPage: false, - }, + cicd_Deployment: [ + { + _id: 'def', + uid: 'deployment2', + application: {appName: 'app2'}, + changeset: [{commit: {sha: 'sha2'}}], }, - }, + ], + }, + }, + }, + { + data: { + data: { + cicd_Deployment: [], }, }, }, ]); - mockedAxios.create.mockImplementation(() => ({post: mockPost} as any)); + mockedAxios.create.mockImplementation(() => ({post: mockPost}) as any); const faros = new FarosClient({ url: 'https://prod.api.faros.ai', apiKey: 'apiKey', }); - const query = await loadQueryFile('deployments.gql'); - const nodeUids = []; + const query = await loadQueryFile('deployments-v2.gql'); + const nodeUids: any[] = []; for await (const node of faros.nodeIterable('graph', query, 1)) { nodeUids.push(node.uid); } expect(nodeUids).toEqual(['deployment1', 'deployment2']); - const expectedQuery = await loadQueryFile('paginated-deployments.gql'); + const expectedQuery = await loadQueryFile('paginated-deployments-v2.gql'); expect(mockPost).toHaveBeenNthCalledWith( 1, '/graphs/graph/graphql', { query: expectedQuery, - variables: {pageSize: 1}, + variables: {id: '', limit: 1}, }, expect.anything() ); @@ -111,7 +96,7 @@ describe('index', () => { '/graphs/graph/graphql', { query: expectedQuery, - variables: {cursor: 'abc', pageSize: 1}, + variables: {id: 'abc', limit: 1}, }, expect.anything() ); @@ -127,13 +112,14 @@ describe('index', () => { expect(() => faros.nodeIterable('graph', query)).toThrow(/invalid query/); }); - test('query with multiple selections', async () => { - const faros = new FarosClient({ - url: 'https://prod.api.faros.ai', - apiKey: 'apiKey', - }); + // TODO: The v1 paginator doesn't detect this. Is it a mistake? + //test('query with multiple selections', async () => { + // const faros = new FarosClient({ + // url: 'https://prod.api.faros.ai', + // apiKey: 'apiKey', + // }); - const query = await loadQueryFile('multiple-selections.gql'); - expect(() => faros.nodeIterable('graph', query)).toThrow(/invalid query/); - }); + // const query = await loadQueryFile('multiple-selections-v2.gql'); + // expect(() => faros.nodeIterable('graph', query)).toThrow(/invalid query/); + //}); }); diff --git a/test/resources/queries/deployments-v2.gql b/test/resources/queries/deployments-v2.gql new file mode 100644 index 0000000..7a55a3f --- /dev/null +++ b/test/resources/queries/deployments-v2.gql @@ -0,0 +1,13 @@ +query basic($from: timestamptz) { + cicd_Deployment(where: {refreshedAt: {_gte: $from}}) { + deploymentUid: uid + application { + appName: name + } + changeset { + commit { + sha + } + } + } +} diff --git a/test/resources/queries/deployments.gql b/test/resources/queries/deployments.gql deleted file mode 100644 index cd066eb..0000000 --- a/test/resources/queries/deployments.gql +++ /dev/null @@ -1,19 +0,0 @@ -query basic($from: builtin_BigInt) { - cicd { - deployments(filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from}}) { - nodes { - deploymentUid: uid - application { - appName: name - } - changeset { - nodes { - commit { - sha - } - } - } - } - } - } -} diff --git a/test/resources/queries/max-node-depth.gql b/test/resources/queries/max-node-depth.gql deleted file mode 100644 index f432498..0000000 --- a/test/resources/queries/max-node-depth.gql +++ /dev/null @@ -1,69 +0,0 @@ -{ - tms { - tasks { - nodes { - projects { - nodes { - task { - projects { - nodes { - task { - projects { - nodes { - task { - projects { - nodes { - task { - projects { - nodes { - task { - projects { - nodes { - task { - projects { - nodes { - task { - projects { - nodes { - task { - projects { - nodes { - task { - projects { - nodes { - task { - uid - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } -} diff --git a/test/resources/queries/multiple-selections-v2.gql b/test/resources/queries/multiple-selections-v2.gql new file mode 100644 index 0000000..785bcfc --- /dev/null +++ b/test/resources/queries/multiple-selections-v2.gql @@ -0,0 +1,8 @@ +{ + cicd_Build { + uid + } + cicd_Deployment { + uid + } +} diff --git a/test/resources/queries/multiple-selections.gql b/test/resources/queries/multiple-selections.gql deleted file mode 100644 index 201a1df..0000000 --- a/test/resources/queries/multiple-selections.gql +++ /dev/null @@ -1,14 +0,0 @@ -{ - cicd { - builds { - nodes { - uid - } - } - deployments { - nodes { - uid - } - } - } -} diff --git a/test/resources/queries/paginated-deployments-v2.gql b/test/resources/queries/paginated-deployments-v2.gql new file mode 100644 index 0000000..79078ab --- /dev/null +++ b/test/resources/queries/paginated-deployments-v2.gql @@ -0,0 +1,18 @@ +query paginatedQuery($id: String, $limit: Int, $from: timestamptz) { + cicd_Deployment( + where: {_and: [{refreshedAt: {_gte: $from}}, {id: {_gt: $id}}]} + order_by: {id: asc} + limit: $limit + ) { + _id: id + deploymentUid: uid + application { + appName: name + } + changeset { + commit { + sha + } + } + } +} diff --git a/test/resources/queries/paginated-deployments.gql b/test/resources/queries/paginated-deployments.gql deleted file mode 100644 index 76acfbf..0000000 --- a/test/resources/queries/paginated-deployments.gql +++ /dev/null @@ -1,25 +0,0 @@ -query paginatedQuery($pageSize: Int, $cursor: Cursor, $from: builtin_BigInt) { - cicd { - deployments(filter: {refreshedAtMillis: {greaterThanOrEqualTo: $from}}, first: $pageSize, after: $cursor) { - edges { - cursor - node { - deploymentUid: uid - application { - appName: name - } - changeset { - nodes { - commit { - sha - } - } - } - } - } - pageInfo { - hasNextPage - } - } - } -} diff --git a/test/resources/queries/tasks.gql b/test/resources/queries/tasks.gql deleted file mode 100644 index b071c9b..0000000 --- a/test/resources/queries/tasks.gql +++ /dev/null @@ -1,33 +0,0 @@ -{ - tms { - tasks { - nodes { - uid - creator { - creatorUid: uid - } - projects { - nodes { - project { - projectUid: uid - boards { - nodes { - board { - boardUid: uid - } - } - } - } - } - } - assignees { - nodes { - assignee { - assigneeUid: uid - } - } - } - } - } - } -} diff --git a/test/resources/schemas/schema.gql b/test/resources/schemas/schema.gql deleted file mode 100644 index c16c435..0000000 --- a/test/resources/schemas/schema.gql +++ /dev/null @@ -1,590 +0,0 @@ -type Query { - node(id: ID!): Node - cicd: cicd_Query - # compute: compute_Query - tms: tms_Query - # vcs: vcs_Query -} - -type cicd_Query { - deployments( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: cicd_DeploymentFilter - ): cicd_DeploymentConnection -} - -"""A connection to a list of `cicd_Deployment` values.""" -type cicd_DeploymentConnection { - """A list of `cicd_Deployment` objects.""" - nodes: [cicd_Deployment]! - - """ - A list of edges which contains the `cicd_Deployment` and cursor to aid in pagination. - """ - edges: [cicd_DeploymentEdge!]! - - """Information to aid in pagination.""" - pageInfo: builtin_PageInfo! -} - -"""A `cicd_Deployment` edge in the connection.""" -type cicd_DeploymentEdge { - """A cursor for use in pagination.""" - cursor: Cursor - - """The `cicd_Deployment` at the end of the edge.""" - node: cicd_Deployment -} - -""" -A filter to be used against `cicd_Deployment` object types. All fields are combined with a logical ‘and.’ -""" -input cicd_DeploymentFilter { - """Filter by the object’s `refreshedAtMillis` field.""" - refreshedAtMillis: builtin_BigIntFilter -} - -scalar builtin_BigInt - -""" -A filter to be used against builtin_BigInt fields. All fields are combined with a logical ‘and.’ -""" -input builtin_BigIntFilter { - """ - Is null (if `true` is specified) or is not null (if `false` is specified). - """ - isNull: Boolean - - """Equal to the specified value.""" - equalTo: builtin_BigInt - - """Not equal to the specified value.""" - notEqualTo: builtin_BigInt - - """Included in the specified list.""" - in: [builtin_BigInt!] - - """Not included in the specified list.""" - notIn: [builtin_BigInt!] - - """Less than the specified value.""" - lessThan: builtin_BigInt - - """Less than or equal to the specified value.""" - lessThanOrEqualTo: builtin_BigInt - - """Greater than the specified value.""" - greaterThan: builtin_BigInt - - """Greater than or equal to the specified value.""" - greaterThanOrEqualTo: builtin_BigInt -} - -type builtin_PageInfo { - """When paginating forwards, are there more items?""" - hasNextPage: Boolean! - - """When paginating backwards, are there more items?""" - hasPreviousPage: Boolean! -} - -scalar Cursor - -interface Node { - id: ID! -} - -"""Generic node metadata""" -type NodeMetadata { - """The origin of the entry which created the node""" - origin: String - - """The latest timestamp of the entries affecting this node""" - refreshedAt: String -} - -type cicd_Deployment implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - - """Deployment uid is unique within the source system, e.g CodeDeploy""" - uid: ID - url: String - requestedAt: Timestamp - startedAt: Timestamp - endedAt: Timestamp - - """ - Reads a single `compute_Application` that is related to this `cicd_Deployment`. - """ - application: compute_Application - - """Reads a single `cicd_Build` that is related to this `cicd_Deployment`.""" - build: cicd_Build - - """ - Backwards reference generated from `cicd_DeploymentChangeset`'s `deployment` field - """ - changeset( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: cicd_DeploymentChangesetFilter - ): cicd_DeploymentChangesetConnection! -} - -type cicd_DeploymentChangeset implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - - """ - Reads a single `cicd_Deployment` that is related to this `cicd_DeploymentChangeset`. - """ - deployment: cicd_Deployment - - """ - Reads a single `vcs_Commit` that is related to this `cicd_DeploymentChangeset`. - """ - commit: vcs_Commit -} - -"""A connection to a list of `cicd_DeploymentChangeset` values.""" -type cicd_DeploymentChangesetConnection { - """A list of `cicd_DeploymentChangeset` objects.""" - nodes: [cicd_DeploymentChangeset]! - - """ - A list of edges which contains the `cicd_DeploymentChangeset` and cursor to aid in pagination. - """ - edges: [cicd_DeploymentChangesetEdge!]! - - """Information to aid in pagination.""" - pageInfo: builtin_PageInfo! -} - -"""A `cicd_DeploymentChangeset` edge in the connection.""" -type cicd_DeploymentChangesetEdge { - """A cursor for use in pagination.""" - cursor: Cursor - - """The `cicd_DeploymentChangeset` at the end of the edge.""" - node: cicd_DeploymentChangeset -} - -""" -A filter to be used against `cicd_DeploymentChangeset` object types. All fields are combined with a logical ‘and.’ -""" -input cicd_DeploymentChangesetFilter { - """Filter by the object’s `refreshedAtMillis` field.""" - refreshedAtMillis: builtin_BigIntFilter -} - -type vcs_Commit implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - sha: String -} - -type cicd_Build implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - - """Build uid is unique within the context of the pipeline""" - uid: ID - pipeline: cicd_Pipeline -} - -type cicd_Pipeline implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - - """Pipeline uid is unique within the context of the organization""" - uid: ID -} - -type compute_Application implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - name: String -} - -scalar Timestamp - -type tms_Query { - tasks( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: tms_TaskFilter - ): tms_TaskConnection -} - -type tms_TaskConnection { - """A list of `tms_Task` objects.""" - nodes: [tms_Task]! - - """ - A list of edges which contains the `tms_Task` and cursor to aid in pagination. - """ - edges: [tms_TaskEdge!]! - - """Information to aid in pagination.""" - pageInfo: builtin_PageInfo! -} - -type tms_TaskEdge { - """A cursor for use in pagination.""" - cursor: Cursor - - """The `tms_Task` at the end of the edge.""" - node: tms_Task -} - -input tms_TaskFilter { - """Filter by the object’s `refreshedAtMillis` field.""" - refreshedAtMillis: builtin_BigIntFilter -} - -type tms_Task implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - uid: ID - - """Reads a single `tms_User` that is related to this `tms_Task`.""" - creator: tms_User - - """ - Backwards reference generated from `tms_TaskProjectRelationship`'s `task` field - """ - projects( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: tms_TaskProjectRelationshipFilter - ): tms_TaskProjectRelationshipConnection! - - """Backwards reference generated from `tms_TaskAssignment`'s `task` field""" - assignees( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: tms_TaskAssignmentFilter - ): tms_TaskAssignmentConnection! -} - -type tms_TaskProjectRelationship implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - - """ - Reads a single `tms_Task` that is related to this `tms_TaskProjectRelationship`. - """ - task: tms_Task - - """ - Reads a single `tms_Project` that is related to this `tms_TaskProjectRelationship`. - """ - project: tms_Project -} - -"""A connection to a list of `tms_TaskProjectRelationship` values.""" -type tms_TaskProjectRelationshipConnection { - """A list of `tms_TaskProjectRelationship` objects.""" - nodes: [tms_TaskProjectRelationship]! - - """ - A list of edges which contains the `tms_TaskProjectRelationship` and cursor to aid in pagination. - """ - edges: [tms_TaskProjectRelationshipEdge!]! - - """Information to aid in pagination.""" - pageInfo: builtin_PageInfo! -} - -"""A `tms_TaskProjectRelationship` edge in the connection.""" -type tms_TaskProjectRelationshipEdge { - """A cursor for use in pagination.""" - cursor: Cursor - - """The `tms_TaskProjectRelationship` at the end of the edge.""" - node: tms_TaskProjectRelationship -} - -""" -A filter to be used against `tms_TaskProjectRelationship` object types. All fields are combined with a logical ‘and.’ -""" -input tms_TaskProjectRelationshipFilter { - """Filter by the object’s `refreshedAtMillis` field.""" - refreshedAtMillis: builtin_BigIntFilter -} - -type tms_Project implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - uid: ID - - """ - Backwards reference generated from `tms_TaskBoardProjectRelationship`'s `project` field - """ - boards( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: tms_TaskBoardProjectRelationshipFilter - ): tms_TaskBoardProjectRelationshipConnection! -} - -type tms_TaskBoardProjectRelationship implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - - """ - Reads a single `tms_TaskBoard` that is related to this `tms_TaskBoardProjectRelationship`. - """ - board: tms_TaskBoard - - """ - Reads a single `tms_Project` that is related to this `tms_TaskBoardProjectRelationship`. - """ - project: tms_Project -} - -"""A connection to a list of `tms_TaskBoardProjectRelationship` values.""" -type tms_TaskBoardProjectRelationshipConnection { - """A list of `tms_TaskBoardProjectRelationship` objects.""" - nodes: [tms_TaskBoardProjectRelationship]! - - """ - A list of edges which contains the `tms_TaskBoardProjectRelationship` and cursor to aid in pagination. - """ - edges: [tms_TaskBoardProjectRelationshipEdge!]! - - """Information to aid in pagination.""" - pageInfo: builtin_PageInfo! -} - -"""A `tms_TaskBoardProjectRelationship` edge in the connection.""" -type tms_TaskBoardProjectRelationshipEdge { - """A cursor for use in pagination.""" - cursor: Cursor - - """The `tms_TaskBoardProjectRelationship` at the end of the edge.""" - node: tms_TaskBoardProjectRelationship -} - -""" -A filter to be used against `tms_TaskBoardProjectRelationship` object types. All fields are combined with a logical ‘and.’ -""" -input tms_TaskBoardProjectRelationshipFilter { - """Filter by the object’s `refreshedAtMillis` field.""" - refreshedAtMillis: builtin_BigIntFilter -} - -type tms_TaskBoard implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - uid: String -} - -type tms_User implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - uid: ID -} - -type tms_TaskAssignment implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - assignedAt: Timestamp - - """ - Reads a single `tms_Task` that is related to this `tms_TaskAssignment`. - """ - task: tms_Task - - """ - Reads a single `tms_User` that is related to this `tms_TaskAssignment`. - """ - assignee: tms_User -} - -"""A connection to a list of `tms_TaskAssignment` values.""" -type tms_TaskAssignmentConnection { - """A list of `tms_TaskAssignment` objects.""" - nodes: [tms_TaskAssignment]! - - """ - A list of edges which contains the `tms_TaskAssignment` and cursor to aid in pagination. - """ - edges: [tms_TaskAssignmentEdge!]! - - """Information to aid in pagination.""" - pageInfo: builtin_PageInfo! -} - -"""A `tms_TaskAssignment` edge in the connection.""" -type tms_TaskAssignmentEdge { - """A cursor for use in pagination.""" - cursor: Cursor - - """The `tms_TaskAssignment` at the end of the edge.""" - node: tms_TaskAssignment -} - -""" -A filter to be used against `tms_TaskAssignment` object types. All fields are combined with a logical ‘and.’ -""" -input tms_TaskAssignmentFilter { - """Filter by the object’s `refreshedAtMillis` field.""" - refreshedAtMillis: builtin_BigIntFilter -} - diff --git a/test/resources/schemas/v1_schema_for_adapter_test.gql b/test/resources/schemas/v1_schema_for_adapter_test.gql deleted file mode 100644 index 722ba80..0000000 --- a/test/resources/schemas/v1_schema_for_adapter_test.gql +++ /dev/null @@ -1,1152 +0,0 @@ -type Query { - node(id: ID!): Node - cal: cal_Query - cicd: cicd_Query - geo: geo_Query - tms: tms_Query - vcs: vcs_Query -} - -type cal_Query { - events( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: cal_EventFilter - ): cal_EventConnection -} - -scalar Long - -type cal_Event implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - durationMs: Long -} - -"""A connection to a list of `cal_Event` values.""" -type cal_EventConnection { - """A list of `cal_Event` objects.""" - nodes: [cal_Event]! - - """ - A list of edges which contains the `cal_Event` and cursor to aid in pagination. - """ - edges: [cal_EventEdge!]! - - """Information to aid in pagination.""" - pageInfo: builtin_PageInfo! -} - -"""A `cal_Event` edge in the connection.""" -type cal_EventEdge { - """A cursor for use in pagination.""" - cursor: Cursor - - """The `cal_Event` at the end of the edge.""" - node: cal_Event -} - -input cal_EventFilter { - """Filter by the object’s `refreshedAtMillis` field.""" - refreshedAtMillis: builtin_BigIntFilter -} - -type geo_Query { - coordinates( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: geo_CoordinatesFilter - ): geo_CoordinatesConnection -} - -scalar Double - -type geo_Coordinates implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - lat: Double - lon: Double -} - -input geo_CoordinatesFilter { - """Filter by the object’s `refreshedAtMillis` field.""" - refreshedAtMillis: builtin_BigIntFilter -} - -type geo_CoordinatesConnection { - """A list of `geo_Coordinates` objects.""" - nodes: [geo_Coordinates]! - - """ - A list of edges which contains the `geo_Coordinates` and cursor to aid in pagination. - """ - edges: [geo_CoordinatesEdge!]! - - """Information to aid in pagination.""" - pageInfo: builtin_PageInfo! -} - -"""A `geo_Coordinates` edge in the connection.""" -type geo_CoordinatesEdge { - """A cursor for use in pagination.""" - cursor: Cursor - - """The `geo_Coordinates` at the end of the edge.""" - node: geo_Coordinates -} - -type cicd_Query { - deployments( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: cicd_DeploymentFilter - ): cicd_DeploymentConnection - deploymentChangesets( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: cicd_DeploymentChangesetFilter - ): cicd_DeploymentChangesetConnection -} - -"""A connection to a list of `cicd_Deployment` values.""" -type cicd_DeploymentConnection { - """A list of `cicd_Deployment` objects.""" - nodes: [cicd_Deployment]! - - """ - A list of edges which contains the `cicd_Deployment` and cursor to aid in pagination. - """ - edges: [cicd_DeploymentEdge!]! - - """Information to aid in pagination.""" - pageInfo: builtin_PageInfo! -} - -"""A `cicd_Deployment` edge in the connection.""" -type cicd_DeploymentEdge { - """A cursor for use in pagination.""" - cursor: Cursor - - """The `cicd_Deployment` at the end of the edge.""" - node: cicd_Deployment -} - -""" -A filter to be used against `cicd_Deployment` object types. All fields are combined with a logical ‘and.’ -""" -input cicd_DeploymentFilter { - """Filter by the object’s `refreshedAtMillis` field.""" - refreshedAtMillis: builtin_BigIntFilter -} - -scalar builtin_BigInt - -""" -A filter to be used against builtin_BigInt fields. All fields are combined with a logical ‘and.’ -""" -input builtin_BigIntFilter { - """ - Is null (if `true` is specified) or is not null (if `false` is specified). - """ - isNull: Boolean - - """Equal to the specified value.""" - equalTo: builtin_BigInt - - """Not equal to the specified value.""" - notEqualTo: builtin_BigInt - - """Included in the specified list.""" - in: [builtin_BigInt!] - - """Not included in the specified list.""" - notIn: [builtin_BigInt!] - - """Less than the specified value.""" - lessThan: builtin_BigInt - - """Less than or equal to the specified value.""" - lessThanOrEqualTo: builtin_BigInt - - """Greater than the specified value.""" - greaterThan: builtin_BigInt - - """Greater than or equal to the specified value.""" - greaterThanOrEqualTo: builtin_BigInt -} - -type builtin_PageInfo { - """When paginating forwards, are there more items?""" - hasNextPage: Boolean! - - """When paginating backwards, are there more items?""" - hasPreviousPage: Boolean! -} - -scalar Cursor - -interface Node { - id: ID! -} - -"""Generic node metadata""" -type NodeMetadata { - isPhantom: Boolean - """The origin of the entry which created the node""" - origin: String - - """The latest timestamp of the entries affecting this node""" - refreshedAt: String -} - -type cicd_Deployment implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - - """Deployment uid is unique within the source system, e.g CodeDeploy""" - uid: ID - url: String - requestedAt: Timestamp - startedAt: Timestamp - endedAt: Timestamp - env: cicd_Environment - - """ - Reads a single `compute_Application` that is related to this `cicd_Deployment`. - """ - application: compute_Application - - """Reads a single `cicd_Build` that is related to this `cicd_Deployment`.""" - build: cicd_Build - - """ - Backwards reference generated from `cicd_DeploymentChangeset`'s `deployment` field - """ - changeset( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: cicd_DeploymentChangesetFilter - ): cicd_DeploymentChangesetConnection! -} - -type cicd_Environment { - category: cicd_EnvironmentCategory - detail: String -} - -enum cicd_EnvironmentCategory { - Prod - Dev -} - -type cicd_DeploymentChangeset implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - - """ - Reads a single `cicd_Deployment` that is related to this `cicd_DeploymentChangeset`. - """ - deployment: cicd_Deployment - - """ - Reads a single `vcs_Commit` that is related to this `cicd_DeploymentChangeset`. - """ - commit: vcs_Commit -} - -"""A connection to a list of `cicd_DeploymentChangeset` values.""" -type cicd_DeploymentChangesetConnection { - """A list of `cicd_DeploymentChangeset` objects.""" - nodes: [cicd_DeploymentChangeset]! - - """ - A list of edges which contains the `cicd_DeploymentChangeset` and cursor to aid in pagination. - """ - edges: [cicd_DeploymentChangesetEdge!]! - - """Information to aid in pagination.""" - pageInfo: builtin_PageInfo! -} - -"""A `cicd_DeploymentChangeset` edge in the connection.""" -type cicd_DeploymentChangesetEdge { - """A cursor for use in pagination.""" - cursor: Cursor - - """The `cicd_DeploymentChangeset` at the end of the edge.""" - node: cicd_DeploymentChangeset -} - -""" -A filter to be used against `cicd_DeploymentChangeset` object types. All fields are combined with a logical ‘and.’ -""" -input cicd_DeploymentChangesetFilter { - """Filter by the object’s `refreshedAtMillis` field.""" - refreshedAtMillis: builtin_BigIntFilter -} - -type vcs_Commit implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - sha: String - - deployments( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: cicd_DeploymentChangesetFilter - ): cicd_DeploymentChangesetConnection! -} - -type cicd_Build implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - - """Build uid is unique within the context of the pipeline""" - uid: ID - pipeline: cicd_Pipeline -} - -type cicd_Pipeline implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - - """Pipeline uid is unique within the context of the organization""" - uid: ID -} - -type compute_Application implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - name: String -} - -scalar Timestamp - -type tms_Query { - tasks( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: tms_TaskFilter - ): tms_TaskConnection - projects( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: tms_ProjectFilter - ): tms_ProjectConnection -} - -input tms_ProjectFilter { - """Filter by the object’s `refreshedAtMillis` field.""" - refreshedAtMillis: builtin_BigIntFilter -} - -type tms_ProjectConnection { - """A list of `tms_Project` objects.""" - nodes: [tms_Project]! - - """ - A list of edges which contains the `tms_Project` and cursor to aid in pagination. - """ - edges: [tms_ProjectEdge!]! - - """Information to aid in pagination.""" - pageInfo: builtin_PageInfo! -} - -"""A `tms_Project` edge in the connection.""" -type tms_ProjectEdge { - """A cursor for use in pagination.""" - cursor: Cursor - - """The `tms_Project` at the end of the edge.""" - node: tms_Project -} - -type tms_TaskConnection { - """A list of `tms_Task` objects.""" - nodes: [tms_Task]! - - """ - A list of edges which contains the `tms_Task` and cursor to aid in pagination. - """ - edges: [tms_TaskEdge!]! - - """Information to aid in pagination.""" - pageInfo: builtin_PageInfo! -} - -type tms_TaskEdge { - """A cursor for use in pagination.""" - cursor: Cursor - - """The `tms_Task` at the end of the edge.""" - node: tms_Task -} - -input tms_TaskFilter { - """Filter by the object’s `refreshedAtMillis` field.""" - refreshedAtMillis: builtin_BigIntFilter -} - -type tms_TaskField { - name: String - value: String -} - -type tms_TaskStatus { - category: tms_TaskStatusCategory - detail: String -} - -enum tms_TaskStatusCategory { - Custom -} - -type tms_TaskStatusChange { - status: tms_TaskStatus - changedAt: Timestamp -} - -type tms_Task implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - uid: ID - - """Reads a single `tms_User` that is related to this `tms_Task`.""" - creator: tms_User - createdAt: Timestamp - additionalFields: [tms_TaskField] - statusChangelog: [tms_TaskStatusChange] - - type: tms_TaskType - parent: tms_Task - """ - Backwards reference generated from `tms_TaskProjectRelationship`'s `task` field - """ - projects( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: tms_TaskProjectRelationshipFilter - ): tms_TaskProjectRelationshipConnection! - - """Backwards reference generated from `tms_TaskAssignment`'s `task` field""" - assignees( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: tms_TaskAssignmentFilter - ): tms_TaskAssignmentConnection! -} - -type tms_TaskType { - category: tms_TaskCategory - detail: String -} - -enum tms_TaskCategory { - Custom -} - -type tms_TaskProjectRelationship implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - - """ - Reads a single `tms_Task` that is related to this `tms_TaskProjectRelationship`. - """ - task: tms_Task - - """ - Reads a single `tms_Project` that is related to this `tms_TaskProjectRelationship`. - """ - project: tms_Project -} - -"""A connection to a list of `tms_TaskProjectRelationship` values.""" -type tms_TaskProjectRelationshipConnection { - """A list of `tms_TaskProjectRelationship` objects.""" - nodes: [tms_TaskProjectRelationship]! - - """ - A list of edges which contains the `tms_TaskProjectRelationship` and cursor to aid in pagination. - """ - edges: [tms_TaskProjectRelationshipEdge!]! - - """Information to aid in pagination.""" - pageInfo: builtin_PageInfo! -} - -"""A `tms_TaskProjectRelationship` edge in the connection.""" -type tms_TaskProjectRelationshipEdge { - """A cursor for use in pagination.""" - cursor: Cursor - - """The `tms_TaskProjectRelationship` at the end of the edge.""" - node: tms_TaskProjectRelationship -} - -""" -A filter to be used against `tms_TaskProjectRelationship` object types. All fields are combined with a logical ‘and.’ -""" -input tms_TaskProjectRelationshipFilter { - """Filter by the object’s `refreshedAtMillis` field.""" - refreshedAtMillis: builtin_BigIntFilter -} - -type tms_Project implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - uid: ID - - createdAt: Timestamp - description: String - - """ - Backwards reference generated from `tms_TaskBoardProjectRelationship`'s `project` field - """ - boards( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: tms_TaskBoardProjectRelationshipFilter - ): tms_TaskBoardProjectRelationshipConnection! - tasks( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: tms_TaskProjectRelationshipFilter - ): tms_TaskProjectRelationshipConnection! - releases( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: tms_ProjectReleaseRelationshipFilter - ): tms_ProjectReleaseRelationshipConnection! -} - -input tms_ProjectReleaseRelationshipFilter { - """Filter by the object’s `refreshedAtMillis` field.""" - refreshedAtMillis: builtin_BigIntFilter -} - -type tms_ProjectReleaseRelationshipConnection { - """A list of `tms_ProjectReleaseRelationship` objects.""" - nodes: [tms_ProjectReleaseRelationship]! - - """ - A list of edges which contains the `tms_ProjectReleaseRelationship` and cursor to aid in pagination. - """ - edges: [tms_ProjectReleaseRelationshipEdge!]! - - """Information to aid in pagination.""" - pageInfo: builtin_PageInfo! -} - -"""A `tms_ProjectReleaseRelationship` edge in the connection.""" -type tms_ProjectReleaseRelationshipEdge { - """A cursor for use in pagination.""" - cursor: Cursor - - """The `tms_ProjectReleaseRelationship` at the end of the edge.""" - node: tms_ProjectReleaseRelationship -} - -type tms_ProjectReleaseRelationship implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - - """ - Reads a single `tms_Project` that is related to this `tms_ProjectReleaseRelationship`. - """ - project: tms_Project - - """ - Reads a single `tms_Release` that is related to this `tms_ProjectReleaseRelationship`. - """ - release: tms_Release -} - -type tms_Release implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - uid: ID - releasedAt: Timestamp -} - -type tms_TaskBoardProjectRelationship implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - - """ - Reads a single `tms_TaskBoard` that is related to this `tms_TaskBoardProjectRelationship`. - """ - board: tms_TaskBoard - - """ - Reads a single `tms_Project` that is related to this `tms_TaskBoardProjectRelationship`. - """ - project: tms_Project -} - -"""A connection to a list of `tms_TaskBoardProjectRelationship` values.""" -type tms_TaskBoardProjectRelationshipConnection { - """A list of `tms_TaskBoardProjectRelationship` objects.""" - nodes: [tms_TaskBoardProjectRelationship]! - - """ - A list of edges which contains the `tms_TaskBoardProjectRelationship` and cursor to aid in pagination. - """ - edges: [tms_TaskBoardProjectRelationshipEdge!]! - - """Information to aid in pagination.""" - pageInfo: builtin_PageInfo! -} - -"""A `tms_TaskBoardProjectRelationship` edge in the connection.""" -type tms_TaskBoardProjectRelationshipEdge { - """A cursor for use in pagination.""" - cursor: Cursor - - """The `tms_TaskBoardProjectRelationship` at the end of the edge.""" - node: tms_TaskBoardProjectRelationship -} - -""" -A filter to be used against `tms_TaskBoardProjectRelationship` object types. All fields are combined with a logical ‘and.’ -""" -input tms_TaskBoardProjectRelationshipFilter { - """Filter by the object’s `refreshedAtMillis` field.""" - refreshedAtMillis: builtin_BigIntFilter -} - -type tms_TaskBoard implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - uid: String -} - -type tms_User implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - uid: ID -} - -type tms_TaskAssignment implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - assignedAt: Timestamp - - """ - Reads a single `tms_Task` that is related to this `tms_TaskAssignment`. - """ - task: tms_Task - - """ - Reads a single `tms_User` that is related to this `tms_TaskAssignment`. - """ - assignee: tms_User -} - -"""A connection to a list of `tms_TaskAssignment` values.""" -type tms_TaskAssignmentConnection { - """A list of `tms_TaskAssignment` objects.""" - nodes: [tms_TaskAssignment]! - - """ - A list of edges which contains the `tms_TaskAssignment` and cursor to aid in pagination. - """ - edges: [tms_TaskAssignmentEdge!]! - - """Information to aid in pagination.""" - pageInfo: builtin_PageInfo! -} - -"""A `tms_TaskAssignment` edge in the connection.""" -type tms_TaskAssignmentEdge { - """A cursor for use in pagination.""" - cursor: Cursor - - """The `tms_TaskAssignment` at the end of the edge.""" - node: tms_TaskAssignment -} - -""" -A filter to be used against `tms_TaskAssignment` object types. All fields are combined with a logical ‘and.’ -""" -input tms_TaskAssignmentFilter { - """Filter by the object’s `refreshedAtMillis` field.""" - refreshedAtMillis: builtin_BigIntFilter -} - -type vcs_Query { - commits( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: vcs_CommitFilter - ): vcs_CommitConnection - repositories( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: vcs_RepositoryFilter - ): vcs_RepositoryConnection - pullRequestComments( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: vcs_PullRequestCommentFilter - ): vcs_PullRequestCommentConnection -} - -type vcs_PullRequestComment implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - number: Long -} - -"""A connection to a list of `vcs_PullRequestComment` values.""" -type vcs_PullRequestCommentConnection { - """A list of `vcs_PullRequestComment` objects.""" - nodes: [vcs_PullRequestComment]! - - """ - A list of edges which contains the `vcs_PullRequestComment` and cursor to aid in pagination. - """ - edges: [vcs_PullRequestCommentEdge!]! - - """Information to aid in pagination.""" - pageInfo: builtin_PageInfo! -} - -"""A `vcs_PullRequestComment` edge in the connection.""" -type vcs_PullRequestCommentEdge { - """A cursor for use in pagination.""" - cursor: Cursor - - """The `vcs_PullRequestComment` at the end of the edge.""" - node: vcs_PullRequestComment -} - -""" -A filter to be used against `vcs_PullRequestComment` object types. All fields are combined with a logical ‘and.’ -""" -input vcs_PullRequestCommentFilter { - """Filter by the object’s `refreshedAtMillis` field.""" - refreshedAtMillis: builtin_BigIntFilter -} - -input vcs_RepositoryFilter { - """Filter by the object’s `refreshedAtMillis` field.""" - refreshedAtMillis: builtin_BigIntFilter -} - -type vcs_RepositoryConnection { - """A list of `vcs_Repository` objects.""" - nodes: [vcs_Repository]! - - """ - A list of edges which contains the `vcs_Repository` and cursor to aid in pagination. - """ - edges: [vcs_RepositoryEdge!]! - - """Information to aid in pagination.""" - pageInfo: builtin_PageInfo! -} - -type vcs_RepositoryEdge { - """A cursor for use in pagination.""" - cursor: Cursor - - """The `vcs_Repository` at the end of the edge.""" - node: vcs_Repository -} - -type vcs_Repository implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - topics: [String!] -} - -input vcs_CommitFilter { - """Filter by the object’s `refreshedAtMillis` field.""" - refreshedAtMillis: builtin_BigIntFilter -} - -"""A connection to a list of `vcs_Commit` values.""" -type vcs_CommitConnection { - """A list of `vcs_Commit` objects.""" - nodes: [vcs_Commit]! - - """ - A list of edges which contains the `vcs_Commit` and cursor to aid in pagination. - """ - edges: [vcs_CommitEdge!]! - - """Information to aid in pagination.""" - pageInfo: builtin_PageInfo! -} - -"""A `vcs_Commit` edge in the connection.""" -type vcs_CommitEdge { - """A cursor for use in pagination.""" - cursor: Cursor - - """The `vcs_Commit` at the end of the edge.""" - node: vcs_Commit -} - diff --git a/test/resources/schemas/v1_schema_for_embedded_test.gql b/test/resources/schemas/v1_schema_for_embedded_test.gql deleted file mode 100644 index 2eff724..0000000 --- a/test/resources/schemas/v1_schema_for_embedded_test.gql +++ /dev/null @@ -1,233 +0,0 @@ -type Query { - node(id: ID!): Node - cicd: cicd_Query -} - -type cicd_Query { - deployments( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: cicd_DeploymentFilter - ): cicd_DeploymentConnection -} - -"""A connection to a list of `cicd_Deployment` values.""" -type cicd_DeploymentConnection { - """A list of `cicd_Deployment` objects.""" - nodes: [cicd_Deployment]! - - """ - A list of edges which contains the `cicd_Deployment` and cursor to aid in pagination. - """ - edges: [cicd_DeploymentEdge!]! - - """Information to aid in pagination.""" - pageInfo: builtin_PageInfo! -} - -"""A `cicd_Deployment` edge in the connection.""" -type cicd_DeploymentEdge { - """A cursor for use in pagination.""" - cursor: Cursor - - """The `cicd_Deployment` at the end of the edge.""" - node: cicd_Deployment -} - -""" -A filter to be used against `cicd_Deployment` object types. All fields are combined with a logical ‘and.’ -""" -input cicd_DeploymentFilter { - """Filter by the object’s `refreshedAtMillis` field.""" - refreshedAtMillis: builtin_BigIntFilter -} - -scalar builtin_BigInt - -""" -A filter to be used against builtin_BigInt fields. All fields are combined with a logical ‘and.’ -""" -input builtin_BigIntFilter { - """ - Is null (if `true` is specified) or is not null (if `false` is specified). - """ - isNull: Boolean - - """Equal to the specified value.""" - equalTo: builtin_BigInt - - """Not equal to the specified value.""" - notEqualTo: builtin_BigInt - - """Included in the specified list.""" - in: [builtin_BigInt!] - - """Not included in the specified list.""" - notIn: [builtin_BigInt!] - - """Less than the specified value.""" - lessThan: builtin_BigInt - - """Less than or equal to the specified value.""" - lessThanOrEqualTo: builtin_BigInt - - """Greater than the specified value.""" - greaterThan: builtin_BigInt - - """Greater than or equal to the specified value.""" - greaterThanOrEqualTo: builtin_BigInt -} - -type builtin_PageInfo { - """When paginating forwards, are there more items?""" - hasNextPage: Boolean! - - """When paginating backwards, are there more items?""" - hasPreviousPage: Boolean! -} - -scalar Cursor - -interface Node { - id: ID! -} - -"""Generic node metadata""" -type NodeMetadata { - """The origin of the entry which created the node""" - origin: String - - """The latest timestamp of the entries affecting this node""" - refreshedAt: String -} - -type Person { - name: String - contact: Contact -} - -type Contact { - email: String - phone: String -} - -type deeply_Embedded { - env: cicd_Environment - envs: [cicd_Environment] - name: String - topics: [String!] -} - -type cicd_Environment { - category: cicd_EnvironmentCategory - detail: String -} - -enum cicd_EnvironmentCategory { - Prod - Dev -} - -type fake_Model implements Node { - deep: deeply_Embedded - person: Person -} - -type cicd_Deployment implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - - """Deployment uid is unique within the source system, e.g CodeDeploy""" - uid: ID - source: String - - env: cicd_Environment - envs: [cicd_Environment] - - """ - Reads a single `compute_Application` that is related to this `cicd_Deployment`. - """ - application: compute_Application - - """Reads a single `cicd_Build` that is related to this `cicd_Deployment`.""" - build: cicd_Build -} - -type compute_Application implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - name: String - displayName: String - platform: String -} - -type cicd_Build implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - - """Build uid is unique within the context of the pipeline""" - uid: ID - name: String - number: Int - - """Reads a single `cicd_Pipeline` that is related to this `cicd_Build`.""" - pipeline: cicd_Pipeline -} - -type cicd_Pipeline implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - - """Pipeline uid is unique within the context of the organization""" - uid: ID - name: String - - """ - Reads a single `cicd_Organization` that is related to this `cicd_Pipeline`. - """ - organization: cicd_Organization -} - -type cicd_Organization implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - - """Organization uid is unique within the source system, e.g Jenkins""" - uid: ID - name: String - source: String -} diff --git a/test/resources/schemas/v1_schema_for_pk_test.gql b/test/resources/schemas/v1_schema_for_pk_test.gql deleted file mode 100644 index 51f4245..0000000 --- a/test/resources/schemas/v1_schema_for_pk_test.gql +++ /dev/null @@ -1,198 +0,0 @@ -type Query { - node(id: ID!): Node - cicd: cicd_Query -} - -type cicd_Query { - deployments( - """Only read the first `n` values of the set.""" - first: Int - - """Only read the last `n` values of the set.""" - last: Int - - """ - Skip the first `n` values from our `after` cursor, an alternative to cursor - based pagination. May not be used with `last`. - """ - offset: Int - - """Read all values in the set before (above) this cursor.""" - before: Cursor - - """Read all values in the set after (below) this cursor.""" - after: Cursor - - """ - A filter to be used in determining which values should be returned by the collection. - """ - filter: cicd_DeploymentFilter - ): cicd_DeploymentConnection -} - -"""A connection to a list of `cicd_Deployment` values.""" -type cicd_DeploymentConnection { - """A list of `cicd_Deployment` objects.""" - nodes: [cicd_Deployment]! - - """ - A list of edges which contains the `cicd_Deployment` and cursor to aid in pagination. - """ - edges: [cicd_DeploymentEdge!]! - - """Information to aid in pagination.""" - pageInfo: builtin_PageInfo! -} - -"""A `cicd_Deployment` edge in the connection.""" -type cicd_DeploymentEdge { - """A cursor for use in pagination.""" - cursor: Cursor - - """The `cicd_Deployment` at the end of the edge.""" - node: cicd_Deployment -} - -""" -A filter to be used against `cicd_Deployment` object types. All fields are combined with a logical ‘and.’ -""" -input cicd_DeploymentFilter { - """Filter by the object’s `refreshedAtMillis` field.""" - refreshedAtMillis: builtin_BigIntFilter -} - -scalar builtin_BigInt - -""" -A filter to be used against builtin_BigInt fields. All fields are combined with a logical ‘and.’ -""" -input builtin_BigIntFilter { - """ - Is null (if `true` is specified) or is not null (if `false` is specified). - """ - isNull: Boolean - - """Equal to the specified value.""" - equalTo: builtin_BigInt - - """Not equal to the specified value.""" - notEqualTo: builtin_BigInt - - """Included in the specified list.""" - in: [builtin_BigInt!] - - """Not included in the specified list.""" - notIn: [builtin_BigInt!] - - """Less than the specified value.""" - lessThan: builtin_BigInt - - """Less than or equal to the specified value.""" - lessThanOrEqualTo: builtin_BigInt - - """Greater than the specified value.""" - greaterThan: builtin_BigInt - - """Greater than or equal to the specified value.""" - greaterThanOrEqualTo: builtin_BigInt -} - -type builtin_PageInfo { - """When paginating forwards, are there more items?""" - hasNextPage: Boolean! - - """When paginating backwards, are there more items?""" - hasPreviousPage: Boolean! -} - -scalar Cursor - -interface Node { - id: ID! -} - -"""Generic node metadata""" -type NodeMetadata { - """The origin of the entry which created the node""" - origin: String - - """The latest timestamp of the entries affecting this node""" - refreshedAt: String -} - -type cicd_Deployment implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - - """Deployment uid is unique within the source system, e.g CodeDeploy""" - uid: ID - source: String - - """ - Reads a single `compute_Application` that is related to this `cicd_Deployment`. - """ - application: compute_Application - - """Reads a single `cicd_Build` that is related to this `cicd_Deployment`.""" - build: cicd_Build -} - -type compute_Application implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - name: String - displayName: String - platform: String -} - -type cicd_Build implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - - """Build uid is unique within the context of the pipeline""" - uid: ID - name: String - number: Int - - """Reads a single `cicd_Pipeline` that is related to this `cicd_Build`.""" - pipeline: cicd_Pipeline -} - -type cicd_Pipeline implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - - """Pipeline uid is unique within the context of the organization""" - uid: ID - name: String - - """ - Reads a single `cicd_Organization` that is related to this `cicd_Pipeline`. - """ - organization: cicd_Organization -} - -type cicd_Organization implements Node { - """A globally unique identifier for the node""" - id: ID! - - """Generic node metadata""" - metadata: NodeMetadata! - - """Organization uid is unique within the source system, e.g Jenkins""" - uid: ID - name: String - source: String -} \ No newline at end of file