Skip to content

Commit

Permalink
TECH-1631: Better identification of types and include deprecations (#65)
Browse files Browse the repository at this point in the history
  • Loading branch information
Fgerthoffert authored Feb 12, 2024
1 parent 11294f1 commit 6c5bf2c
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 14 deletions.
20 changes: 20 additions & 0 deletions fixtures/graphql/introspection.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,27 @@ query IntrospectionQuery($typeName: String!) {
kind
name
description
ofType {
name
description
}
}
}
interfaces {
name
description
ofType {
name
description
}
}
possibleTypes {
name
description
ofType {
name
description
}
}
enumValues(includeDeprecated: true) {
name
Expand All @@ -43,11 +55,19 @@ query IntrospectionQuery($typeName: String!) {
kind
name
description
ofType {
name
description
}
}
}
ofType {
name
description
ofType {
name
description
}
}
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@jahia/cypress",
"version": "3.17.8",
"version": "3.17.9",
"scripts": {
"build": "tsc",
"lint": "eslint src -c .eslintrc.json --ext .ts"
Expand Down
53 changes: 40 additions & 13 deletions src/utils/GraphQLHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,31 @@ interface GraphQLDescription {
description: string; // Description of the element in the schema
schemaType: string; // Type of the element in the schema according to the GraphQL spec
schemaNode: GraphQLField; // The actual node fetched from the schema
nodePath: string[] // The path to the node in the schema
nodePath: string[]; // The path to the node in the schema
isDeprecated?: boolean;
deprecationReason?: string;
}

// The type name to be used for further introspection can be located either in
// the type object itself or in the ofType object
const addTypes = ((type, atPath) => {
const newTypes = [];
if (type.name !== null) {
newTypes.push({typeName: type.name, atPath: atPath});
}

if (type.ofType && type.ofType.name !== null) {
newTypes.push({typeName: type.ofType.name, atPath: atPath});
}

return newTypes;
});

// This returns a flat list of all children fields and args for a given GraphQL node
// This list can then be used in Cypress tests to look for missing descriptions
export const getDescriptions = (rootNode: string): Cypress.Chainable => {
cy.log('Starting analysis from GraphQL node: ' + rootNode);
return execIntrospection(rootNode, [], []).then(descriptions => {
return execIntrospection(rootNode, [], [rootNode]).then(descriptions => {
return descriptions;
});
};
Expand All @@ -36,7 +53,7 @@ const execIntrospection = (typeName: string, descriptions: GraphQLDescription[],
if (responseDataType) {
// This array will be populated with types identified in the introspection query
// These will then be further introspected to get their children fields and args
const fetchSubTypes: {typeName: string, atPath: string[]}[] = [];
let fetchSubTypes: {typeName: string, atPath: string[]}[] = [];

descriptions.push({
name: responseDataType.name,
Expand All @@ -50,15 +67,17 @@ const execIntrospection = (typeName: string, descriptions: GraphQLDescription[],
// spec available at https://github.com/graphql/graphql-spec/blob/main/spec/Section%204%20--%20Introspection.md
if (responseDataType.fields) {
for (const graphqlField of responseDataType.fields) {
const fieldPath = [...nodePath, responseDataType.name, graphqlField.name];
const fieldPath = [...nodePath, graphqlField.name];
descriptions.push({
name: graphqlField.name,
description: graphqlField.description,
schemaType: '__Field',
schemaNode: graphqlField,
isDeprecated: graphqlField.isDeprecated,
deprecationReason: graphqlField.deprecationReason,
nodePath: fieldPath
});
fetchSubTypes.push({typeName: graphqlField.type.name, atPath: fieldPath});
fetchSubTypes = [...fetchSubTypes, ...addTypes(graphqlField.type, fieldPath)];

if (graphqlField.args) {
for (const graphQLInputValue of graphqlField.args) {
Expand All @@ -68,25 +87,26 @@ const execIntrospection = (typeName: string, descriptions: GraphQLDescription[],
description: graphQLInputValue.description,
schemaType: '__InputValue',
schemaNode: graphQLInputValue,
isDeprecated: graphQLInputValue.isDeprecated,
deprecationReason: graphQLInputValue.deprecationReason,
nodePath: inputValuePath
});
fetchSubTypes.push({typeName: graphQLInputValue.type.name, atPath: inputValuePath});
fetchSubTypes = [...fetchSubTypes, ...addTypes(graphQLInputValue.type, inputValuePath)];
}
}
}
}

if (responseDataType.interfaces) {
for (const graphQLInterfaceType of responseDataType.interfaces) {
const fieldPath = [...nodePath, responseDataType.name, graphQLInterfaceType.name];
descriptions.push({
name: graphQLInterfaceType.name,
description: graphQLInterfaceType.description,
schemaType: '__Type',
schemaNode: graphQLInterfaceType,
nodePath: fieldPath
nodePath
});
fetchSubTypes.push({typeName: graphQLInterfaceType.name, atPath: fieldPath});
fetchSubTypes = [...fetchSubTypes, ...addTypes(graphQLInterfaceType, nodePath)];
}
}

Expand All @@ -100,7 +120,7 @@ const execIntrospection = (typeName: string, descriptions: GraphQLDescription[],
schemaNode: graphQLType,
nodePath: fieldPath
});
fetchSubTypes.push({typeName: graphQLType.name, atPath: fieldPath});
fetchSubTypes = [...fetchSubTypes, ...addTypes(graphQLType, fieldPath)];
}
}

Expand All @@ -112,7 +132,9 @@ const execIntrospection = (typeName: string, descriptions: GraphQLDescription[],
description: graphQLEnumValue.description,
schemaType: '__EnumValue',
schemaNode: graphQLEnumValue,
nodePath: enumPath
nodePath: enumPath,
isDeprecated: graphQLEnumValue.isDeprecated,
deprecationReason: graphQLEnumValue.deprecationReason
});
}
}
Expand All @@ -127,12 +149,12 @@ const execIntrospection = (typeName: string, descriptions: GraphQLDescription[],
schemaNode: graphQLInputValue,
nodePath: inputValuePath
});
fetchSubTypes.push({typeName: graphQLInputValue.type.name, atPath: inputValuePath});
fetchSubTypes = [...fetchSubTypes, ...addTypes(graphQLInputValue.type, inputValuePath)];
}
}

if (responseDataType.ofType) {
fetchSubTypes.push({typeName: responseDataType.ofType.name, atPath: nodePath});
fetchSubTypes = [...fetchSubTypes, ...addTypes(responseDataType.ofType, nodePath)];
}

const uniqueSubTypes = fetchSubTypes
Expand All @@ -143,6 +165,11 @@ const execIntrospection = (typeName: string, descriptions: GraphQLDescription[],
// Remove types that might have already been introspected
.filter(subtype => descriptions.find(d => d.schemaType === '__Type' && d.name === subtype.typeName) === undefined);

// If there are no subtypes to introspect, we still need to return the descriptions
if (uniqueSubTypes.length === 0) {
return descriptions;
}

return Cypress.Promise.each(uniqueSubTypes, subType => {
return execIntrospection(subType.typeName, descriptions, [...subType.atPath, subType.typeName]);
}).then(() => descriptions); // Return descriptions after all recursive calls have completed
Expand Down

0 comments on commit 6c5bf2c

Please sign in to comment.