diff --git a/modules/server/src/network/health.ts b/modules/server/src/network/health.ts new file mode 100644 index 000000000..c558501b3 --- /dev/null +++ b/modules/server/src/network/health.ts @@ -0,0 +1,36 @@ +import axios from 'axios'; +import urljoin from 'url-join'; +import { CONNECTION_STATUS } from './resolvers/networkNode'; +import { ConnectionStatus } from './types/types'; + +/** + * Check the status of remote connections + * + * @param url - Remote connection url + * @returns A connection status + */ +export const gqlHealthCheck = async (url: string): Promise => { + /* + * recommended way to health check gql server is to run the `__typename` query that every server has. + * very small query with no additional params, so using GET is not a concern for the GQL server. + * adds recommended pre flight header to make sure Apollo doesn't block request by CSRF protection. + */ + const healthCheckQuery = '?query=%7B__typename%7D'; + const fullUrl = urljoin(url, healthCheckQuery); + + try { + const pong = await axios.get(fullUrl, { + headers: { 'apollo-require-preflight': 'true', 'Content-Type': 'application/json' }, + }); + + // only need to check the server returns successfully (just a health check) + if (pong.data.data?.__typename) { + return CONNECTION_STATUS.OK; + } else { + throw Error('no data object returned from GQL server __typname query'); + } + } catch (error) { + console.error(error); + return CONNECTION_STATUS.ERROR; + } +}; diff --git a/modules/server/src/network/index.ts b/modules/server/src/network/index.ts index c7c59f06e..a82648023 100644 --- a/modules/server/src/network/index.ts +++ b/modules/server/src/network/index.ts @@ -1,160 +1,32 @@ import { makeExecutableSchema } from '@graphql-tools/schema'; import { SUPPORTED_AGGREGATIONS_LIST } from './common'; -import { NetworkAggregationError } from './errors'; -import { fetchGql } from './gql'; -import { gqlAggregationTypeQuery, GQLFieldType, GQLTypeQueryResponse } from './queries'; import { createResolvers } from './resolvers'; +import { getAllFieldTypes } from './setup/fields'; +import { fetchRemoteSchemas } from './setup/query'; import { createTypeDefs } from './typeDefs'; -import { - NetworkAggregationConfig, - NetworkAggregationConfigInput, - NetworkFieldType, - SupportedNetworkFieldType, -} from './types'; -import { fulfilledPromiseFilter, getAllTypes } from './util'; - -/** - * GQL query remote connection with __type query to retrieve list of types - * - * @param config - network config from env - * @returns a promise containing network config and the gql query result - * - * @throws Fetch failed error - * - * @throws JSON parse error - * - * @throws Unexpected data error - */ -const fetchRemoteSchema = async ( - config: NetworkAggregationConfigInput, -): Promise => { - const { graphqlUrl, documentType, timeout } = config; - try { - const response = await fetchGql({ - url: graphqlUrl, - gqlQuery: gqlAggregationTypeQuery, - variables: { documentName: documentType }, - options: { - timeout, - }, - }); - - // axios response "data" field, graphql response "data" field - const responseData = response.data?.data; - if (response.status === 200 && response.statusText === 'OK') { - return responseData; - } - - console.error('unexpected response data in fetchRemoteSchema'); - throw new NetworkAggregationError( - `Unexpected data in response object. Please verify the endpoint at ${graphqlUrl} is returning a valid GQL Schema.`, - ); - } catch (error) { - console.error(`Failed to retrieve schema from url: ${config.graphqlUrl}`); - return; - } -}; - -const isSupportedType = ( - fieldObject: NetworkFieldType, - supportedList: string[], -): fieldObject is SupportedNetworkFieldType => { - return supportedList.includes(fieldObject.type); -}; - -/** - * Reduce response into simplified object, returning both supported and unsupported types - * @param fields - * @returns { supportedAggregations: [], unsupportedAggregations: [] } - */ -export const getFieldTypes = (fields: GQLFieldType[], supportedAggregationsList: string[]) => { - const fieldTypes = fields.reduce( - ( - aggregations: Pick< - NetworkAggregationConfig, - 'supportedAggregations' | 'unsupportedAggregations' - >, - field, - ) => { - const fieldType = field.type.name; - const fieldObject = { name: field.name, type: fieldType }; - if (isSupportedType(fieldObject, supportedAggregationsList)) { - return { - ...aggregations, - supportedAggregations: aggregations.supportedAggregations.concat(fieldObject), - }; - } else { - return { - ...aggregations, - unsupportedAggregations: aggregations.unsupportedAggregations.concat(fieldObject), - }; - } - }, - { - supportedAggregations: [], - unsupportedAggregations: [], - }, - ); - - return fieldTypes; -}; - -type NetworkQueryResult = PromiseFulfilledResult<{ - config: NetworkAggregationConfigInput; - gqlResponse: GQLTypeQueryResponse; -}>; - -/** - * Fetch fields and types from remote connections - * @param { networkConfigs } - * @returns GQL object type to be used in functions - */ -const fetchRemoteSchemas = async ({ - networkConfigs, -}: { - networkConfigs: NetworkAggregationConfigInput[]; -}): Promise => { - // query remote connection types - const networkQueryPromises = networkConfigs.map(async (config) => { - const gqlResponse = await fetchRemoteSchema(config); - return { config, gqlResponse }; - }); - - const networkQueries = await Promise.allSettled(networkQueryPromises); - const configs = networkQueries - .filter(fulfilledPromiseFilter) - .map((networkResult) => { - const { config, gqlResponse } = networkResult.value; - const fields = gqlResponse.__type.fields; - - const { supportedAggregations, unsupportedAggregations } = getFieldTypes( - fields, - SUPPORTED_AGGREGATIONS_LIST, - ); - return { ...config, supportedAggregations, unsupportedAggregations }; - }); - - return configs; -}; +import { NetworkConfig } from './types/setup'; /** + * GQL Federated Search schema setup * Connects to remote network connections, looks up field types, add field/type pairs to configs + * * @param { networkConfigs } * @returns graphql schema for the network - types and resolvers combined */ export const createSchemaFromNetworkConfig = async ({ networkConfigs, }: { - networkConfigs: NetworkAggregationConfigInput[]; + networkConfigs: NetworkConfig[]; }) => { - const configs = await fetchRemoteSchemas({ + const networkFields = await fetchRemoteSchemas({ networkConfigs, }); - const networkFieldTypes = getAllTypes(configs); + const networkFieldTypes = getAllFieldTypes(networkFields, SUPPORTED_AGGREGATIONS_LIST); const typeDefs = createTypeDefs(networkFieldTypes); - const resolvers = createResolvers(configs); + + const resolvers = createResolvers(networkConfigs); const networkSchema = makeExecutableSchema({ typeDefs, resolvers }); diff --git a/modules/server/src/network/queries/index.ts b/modules/server/src/network/queries/index.ts index 99cfe3d2b..5d027bb9c 100644 --- a/modules/server/src/network/queries/index.ts +++ b/modules/server/src/network/queries/index.ts @@ -5,8 +5,7 @@ * These are the GQL queries for the supported GQL types */ -import { SupportedAggregation, SUPPORTED_AGGREGATIONS } from '../common'; -import { Aggregations, NumericAggregations } from '../types'; +import { Aggregations } from '../types/aggregations'; export type GQLFieldType = { name: string; @@ -46,22 +45,3 @@ export const gqlAggregationTypeQuery = `#graphql } } `; - -export const aggregationsQuery = /* GraphQL */ ` - #graphql - { - __typename - bucket_count - buckets { - key - doc_count - } - } -`; - -/** - * Returns gql query for gql type - */ -export const supportedAggregationQueries = new Map(); -supportedAggregationQueries.set(SUPPORTED_AGGREGATIONS.Aggregations, aggregationsQuery); -supportedAggregationQueries.set(SUPPORTED_AGGREGATIONS.NumericAggregations, ''); diff --git a/modules/server/src/network/resolvers/aggregations.ts b/modules/server/src/network/resolvers/aggregations.ts index 15452999c..19f2df7e7 100644 --- a/modules/server/src/network/resolvers/aggregations.ts +++ b/modules/server/src/network/resolvers/aggregations.ts @@ -4,10 +4,9 @@ import { DocumentNode } from 'graphql'; import { resolveAggregations } from '../aggregations'; import { fetchGql } from '../gql'; import { failure, isSuccess, success } from '../httpResponses'; -import { supportedAggregationQueries } from '../queries'; -import { NetworkAggregation, NetworkAggregationConfig } from '../types'; +import { NetworkAggregation } from '../types/types'; import { ASTtoString } from '../util'; -import { CONNECTION_STATUS, RemoteConnection } from './remoteConnections'; +import { CONNECTION_STATUS, NetworkNode } from './networkNode'; /** * Query remote connections and handle network responses @@ -66,12 +65,12 @@ export const aggregationPipeline = async ( return { ...accumulator, [field]: emptyAggregation }; }, {}); - const nodeInfo: any = []; + const nodeInfo: NetworkNode[] = []; const aggregationResultPromises = queries.map< Promise<{ aggregations: any; - remoteConnection: RemoteConnection; + remoteConnection: NetworkNode; }> >(async (query) => { const name = query.url; // TODO: use readable name not url @@ -111,54 +110,58 @@ type NetworkQuery = { }; /** - * Find requested field in remote connection supported fields - * all nodes may not have all fields + * Converts info field object into string * - * @param config - * @param fieldName - * @returns - */ -const findMatchedAggregationField = (config: NetworkAggregationConfig, fieldName: string) => { - return config.supportedAggregations.find((agg) => agg.name === fieldName); -}; - -/** - * construct gql string { [fieldName] { [Aggregation query fields] } } + * @param requestedFields * - * @param requestedAggregations + * @example + * ### Input + * ``` + * { donors: { + * buckets: { + * bucket_count: {}, + * } + * }} + * ``` + * + * ### Output + * ``` + * ` + * { donors { + * buckets { + * bucket_count + * } + * }} + * ` + * ``` */ -const createGqlFieldsString = (config: NetworkAggregationConfig, requestedAggregations: any[]) => { - return requestedAggregations.reduce((gqlString, fieldName) => { - const matchedAggregationField = findMatchedAggregationField(config, fieldName); - if (matchedAggregationField) { - const { name, type } = matchedAggregationField; - // get gql query string for supported aggregation - // TODO: only query requested fields + bucket_count if nodes is requested - const aggregationFieldQueryString = supportedAggregationQueries.get(type); - return gqlString + `${name}${aggregationFieldQueryString}`; - } - return gqlString; - }, ''); +const createGqlFieldsString = (requestedFields: {}, documentName: string) => { + const gqlFieldsString = JSON.stringify(requestedFields) + .replaceAll('"', '') + .replaceAll(':', '') + .replaceAll('{}', '') + .replaceAll(',', ' '); + + // add top level field for query and format with correct brackets + const gqlString = `{${documentName} ${gqlFieldsString}}`; + + return gqlString; }; /** * Create queries for remote nodes based on requested fields * * @param configs - * @param requestedAggregations + * @param requestedFields * @returns */ export const createNetworkQueries = ( - configs: NetworkAggregationConfig[], - requestedAggregations: string[], + configs: NetworkConfig[], + requestedFields: {}, ): NetworkQuery[] => { - // TODO: what if there are no matched findMatchedAggregationField const queries = configs .map((config) => { - const gqlStringFields = createGqlFieldsString(config, requestedAggregations); - - // add top level field for query and format with correct brackets - const gqlString = `{${config.documentName} { ${gqlStringFields} }}`; + const gqlString = createGqlFieldsString(requestedFields, config.documentName); /* * convert string to AST object to use as query diff --git a/modules/server/src/network/resolvers/index.ts b/modules/server/src/network/resolvers/index.ts index acae11cba..c4336dc18 100644 --- a/modules/server/src/network/resolvers/index.ts +++ b/modules/server/src/network/resolvers/index.ts @@ -1,11 +1,12 @@ import { type GraphQLResolveInfo } from 'graphql'; -import { NetworkAggregationConfig, RemoteConnectionData } from '../types'; -import { getRequestedFields } from '../util'; +import { NetworkConfig } from '../types/setup'; +import { resolveInfoToMap } from '../util'; import { aggregationPipeline, createNetworkQueries } from './aggregations'; +import { NetworkNode } from './networkNode'; import { createResponse } from './response'; -type NetworkSearchRoot = { - nodes: RemoteConnectionData[]; +export type NetworkSearchRoot = { + nodes: NetworkNode[]; aggregations: Record; }; @@ -19,7 +20,7 @@ type NetworkSearchRoot = { * @param networkFieldTypes * @returns */ -export const createResolvers = (configs: NetworkAggregationConfig[]) => { +export const createResolvers = (configs: NetworkConfig[]) => { return { Query: { network: async ( @@ -28,13 +29,14 @@ export const createResolvers = (configs: NetworkAggregationConfig[]) => { context: unknown, info: GraphQLResolveInfo, ) => { - const { requestedAggregations } = getRequestedFields(info); - const networkQueries = createNetworkQueries(configs, requestedAggregations); + const requestedFieldsMap = resolveInfoToMap(info, 'aggregations'); + const networkQueries = createNetworkQueries(configs, requestedFieldsMap); // Query remote connections and aggregate results + const requestedFields = Object.keys(requestedFieldsMap); const { aggregationResults, nodeInfo } = await aggregationPipeline( networkQueries, - requestedAggregations, + requestedFields, ); const response = createResponse({ aggregationResults, nodeInfo }); diff --git a/modules/server/src/network/resolvers/networkNode.ts b/modules/server/src/network/resolvers/networkNode.ts new file mode 100644 index 000000000..1ea67bbfc --- /dev/null +++ b/modules/server/src/network/resolvers/networkNode.ts @@ -0,0 +1,15 @@ +export const CONNECTION_STATUS = { + OK: 'OK', + ERROR: 'ERROR', +} as const; + +/* + * Querying from resolvers remote connections + */ + +export type NetworkNode = { + name: string; + count: number; + status: keyof typeof CONNECTION_STATUS; + errors: string; +}; diff --git a/modules/server/src/network/resolvers/remoteConnections.ts b/modules/server/src/network/resolvers/remoteConnections.ts deleted file mode 100644 index 51c594795..000000000 --- a/modules/server/src/network/resolvers/remoteConnections.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { ConnectionStatus, NetworkAggregationConfig, RemoteConnectionData } from '@/network/types'; -import axios from 'axios'; -import urljoin from 'url-join'; - -/* - * Setup config connections - */ - -/** - * Check the status of remote connections - * - * @param url - Remote connection url - * @returns A connection status - */ -const gqlHealthCheck = async (url: string): Promise => { - /* - * recommended way to health check gql server is to run the `__typename` query that every server has - * very small query with no additional params to, so using GET is not a concern for the GQL server - * adds recommended pre flight header to make sure Apollo doesn't block request by CSRF protection - */ - const healthCheckQuery = '?query=%7B__typename%7D'; - const fullUrl = urljoin(url, healthCheckQuery); - - try { - const pong = await axios.get(fullUrl, { - headers: { 'apollo-require-preflight': 'true', 'Content-Type': 'application/json' }, - }); - - // only need to check the server returns successfully (just a health check) - if (pong.data.data?.__typename) { - return CONNECTION_STATUS.OK; - } else { - throw Error('no data object returned from GQL server __typname query'); - } - } catch (error) { - console.error(error); - return CONNECTION_STATUS.ERROR; - } -}; - -export const CONNECTION_STATUS = { - OK: 'OK', - ERROR: 'ERROR', -} as const; - -/** - * Returns available remote configuration data from in memory config - * Adds connection status data, and available GQL types from the remote servers - * - * @param networkConfigs - * @returns remote connection metadata - */ -export const resolveRemoteConnectionNodes = async ( - networkConfigs: NetworkAggregationConfig[], -): Promise => { - /** - * Promise.all is safe because we handle errors in checkRemoteConnectionStatus - */ - return await Promise.all( - networkConfigs.map(async (config) => { - const status = await gqlHealthCheck(config.graphqlUrl); - - const remoteConnectionData: RemoteConnectionData = { - url: config.graphqlUrl, - name: config.displayName, - description: '', - documentName: config.documentType, - availableAggregations: config.supportedAggregations, - status, - totalHits: 0, - errors: [], - }; - return remoteConnectionData; - }), - ); -}; - -/* - * Querying from resolvers remote connections - */ - -export type RemoteConnection = { - name: string; - count: number; - status: keyof typeof CONNECTION_STATUS; - errors: string; -}; diff --git a/modules/server/src/network/setup/fields.ts b/modules/server/src/network/setup/fields.ts new file mode 100644 index 000000000..627bef5bd --- /dev/null +++ b/modules/server/src/network/setup/fields.ts @@ -0,0 +1,74 @@ +import { SupportedAggregation } from '../common'; +import { GQLFieldType } from '../queries'; +import { + NetworkFieldType, + SupportedAggregations, + SupportedNetworkFieldType, + UnsupportedAggregations, +} from '../types/types'; + +export type NetworkFields = { name: string; fields: GQLFieldType[] }; + +const isSupportedType = ( + fieldObject: NetworkFieldType, + supportedList: string[], +): fieldObject is SupportedNetworkFieldType => { + return supportedList.includes(fieldObject.type); +}; + +/** + * Parse network fields into supported and unsupported + * + * @param fields + * @returns { supportedAggregations: [], unsupportedAggregations: [] } + */ +export const getFieldTypes = ( + fields: GQLFieldType[], + supportedAggregationsList: SupportedAggregation[], +) => { + const fieldTypes = fields.reduce( + ( + aggregations: { + supportedAggregations: SupportedAggregations; + unsupportedAggregations: UnsupportedAggregations; + }, + field, + ) => { + const fieldType = field.type.name; + const fieldObject = { name: field.name, type: fieldType }; + if (isSupportedType(fieldObject, supportedAggregationsList)) { + return { + ...aggregations, + supportedAggregations: aggregations.supportedAggregations.concat(fieldObject), + }; + } else { + return { + ...aggregations, + unsupportedAggregations: aggregations.unsupportedAggregations.concat(fieldObject), + }; + } + }, + { + supportedAggregations: [], + unsupportedAggregations: [], + }, + ); + + return fieldTypes; +}; + +export const getAllFieldTypes = ( + networkFields: NetworkFields[], + supportedTypes: SupportedAggregation[], +) => { + const nodeFieldTypes = networkFields.map((networkField) => { + const { supportedAggregations, unsupportedAggregations } = getFieldTypes( + networkField.fields, + supportedTypes, + ); + + return { supportedAggregations, unsupportedAggregations }; + }); + + return nodeFieldTypes.flatMap((fieldType) => fieldType.supportedAggregations); +}; diff --git a/modules/server/src/network/setup/query.ts b/modules/server/src/network/setup/query.ts new file mode 100644 index 000000000..a8fecce36 --- /dev/null +++ b/modules/server/src/network/setup/query.ts @@ -0,0 +1,75 @@ +import { NetworkAggregationError } from '../errors'; +import { fetchGql } from '../gql'; +import { gqlAggregationTypeQuery, GQLTypeQueryResponse } from '../queries'; +import { NetworkConfig } from '../types/setup'; +import { fulfilledPromiseFilter } from '../util'; +import { NetworkFields } from './fields'; + +type NetworkQueryResult = PromiseFulfilledResult<{ + config: NetworkConfig; + gqlResponse: GQLTypeQueryResponse; +}>; + +/** + * GQL query remote connection with __type query to retrieve list of types + * + * @param config - network config from env + * @returns a promise containing network config and the gql query result + * + * @throws Fetch failed error + * + * @throws JSON parse error + * + * @throws Unexpected data error + */ +const fetchRemoteSchema = async ( + config: NetworkConfig, +): Promise => { + const { graphqlUrl, documentType } = config; + try { + const response = await fetchGql({ + url: graphqlUrl, + gqlQuery: gqlAggregationTypeQuery, + variables: { documentName: documentType }, + }); + + // axios response "data" field, graphql response "data" field + const responseData = response.data?.data; + if (response.status === 200 && response.statusText === 'OK') { + return responseData; + } + + console.error('unexpected response data in fetchRemoteSchema'); + throw new NetworkAggregationError( + `Unexpected data in response object. Please verify the endpoint at ${graphqlUrl} is returning a valid GQL Schema.`, + ); + } catch (error) { + console.error(`Failed to retrieve schema from url: ${config.graphqlUrl}`); + return; + } +}; + +/** + * Fetch fields and types from remote connections + * @param { networkConfigs } + * @returns GQL object type to be used in functions + */ +export const fetchRemoteSchemas = async ({ + networkConfigs, +}: { + networkConfigs: NetworkConfig[]; +}): Promise => { + // query remote connection types + const networkQueryPromises = networkConfigs.map(async (config) => { + const gqlResponse = await fetchRemoteSchema(config); + return { config, gqlResponse }; + }); + + const networkQueries = await Promise.allSettled(networkQueryPromises); + + return networkQueries.filter(fulfilledPromiseFilter).map((networkResult) => { + const { config, gqlResponse } = networkResult.value; + const fields = gqlResponse.__type.fields; + return { name: config.displayName, fields }; + }); +}; diff --git a/modules/server/src/network/typeDefs/index.ts b/modules/server/src/network/typeDefs/index.ts index 879a8bf43..a37d979a6 100644 --- a/modules/server/src/network/typeDefs/index.ts +++ b/modules/server/src/network/typeDefs/index.ts @@ -1,5 +1,5 @@ import { mergeTypeDefs } from '@graphql-tools/merge'; -import { SupportedNetworkFieldType } from '../types'; +import { SupportedNetworkFieldType } from '../types/types'; import { createNetworkAggregationTypeDefs } from './aggregations'; import { remoteConnectionTypes } from './remoteConnections'; diff --git a/modules/server/src/network/typeDefs/remoteConnections.ts b/modules/server/src/network/typeDefs/remoteConnections.ts index 350efc648..946400872 100644 --- a/modules/server/src/network/typeDefs/remoteConnections.ts +++ b/modules/server/src/network/typeDefs/remoteConnections.ts @@ -4,12 +4,8 @@ export const remoteConnectionTypes = `#graphql } type RemoteConnectionNode { - url: String name: String - description: String - documentName: String - availableAggregations: [String] - totalHits: Int + count: Int status: String errors: [String] } diff --git a/modules/server/src/network/types.ts b/modules/server/src/network/types.ts deleted file mode 100644 index 626678488..000000000 --- a/modules/server/src/network/types.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { ObjectValues } from '@/utils/types'; -import { SupportedAggregation } from './common'; -import { CONNECTION_STATUS } from './resolvers/remoteConnections'; - -// environment config -export type NetworkAggregationConfigInput = { - graphqlUrl: string; - documentType: string; - documentName: string; - displayName: string; - options: { - timeout: number; - }; -}; - -export type NetworkFieldType = { - name: string; - type: T; -}; - -export type SupportedNetworkFieldType = NetworkFieldType; - -/** - * Complete aggregation config that defines the network search setup. - * This includes the original config information plus computed fields - * that are generated in the network search initialization process. - */ -export type NetworkAggregationConfig = NetworkAggregationConfigInput & { - supportedAggregations: SupportedNetworkFieldType[]; - unsupportedAggregations: NetworkFieldType[]; -}; - -export type ConnectionStatus = ObjectValues; - -export type RemoteConnectionData = { - url: string; - name: string; - description: string; - documentName: string; - availableAggregations: SupportedNetworkFieldType[]; - totalHits: number; - errors: string[]; - status: ConnectionStatus; -}; - -export type Bucket = { - doc_count: number; - key: string; -}; - -export type Aggregations = { - bucket_count: number; - buckets: Bucket[]; -}; - -export type NumericAggregations = {}; - -export type RemoteAggregation = { [key: string]: Aggregations | NumericAggregations }; - -export type NetworkAggregation = { - bucket_count: number; - buckets: Bucket[]; -}; diff --git a/modules/server/src/network/types/aggregations.ts b/modules/server/src/network/types/aggregations.ts new file mode 100644 index 000000000..0411e2554 --- /dev/null +++ b/modules/server/src/network/types/aggregations.ts @@ -0,0 +1,15 @@ +/* + * Typescript types for aggregations + */ + +export type Bucket = { + doc_count: number; + key: string; +}; + +export type Aggregations = { + bucket_count: number; + buckets: Bucket[]; +}; + +export type NumericAggregations = {}; diff --git a/modules/server/src/network/types/setup.ts b/modules/server/src/network/types/setup.ts new file mode 100644 index 000000000..5176a7d0a --- /dev/null +++ b/modules/server/src/network/types/setup.ts @@ -0,0 +1,10 @@ +/* + * Setup of network types + */ + +export type NetworkConfig = { + graphqlUrl: string; + documentType: string; + documentName: string; + displayName: string; +}; diff --git a/modules/server/src/network/types/types.ts b/modules/server/src/network/types/types.ts new file mode 100644 index 000000000..0cf4a4f3f --- /dev/null +++ b/modules/server/src/network/types/types.ts @@ -0,0 +1,34 @@ +import { ObjectValues } from '@/utils/types'; +import { SupportedAggregation } from '../common'; +import { CONNECTION_STATUS } from '../resolvers/networkNode'; +import { Aggregations, Bucket, NumericAggregations } from './aggregations'; + +// environment config +export type NetworkAggregationConfigInput = { + graphqlUrl: string; + documentType: string; + documentName: string; + displayName: string; + options: { + timeout: number; + }; +}; + +export type NetworkFieldType = { + name: string; + type: T; +}; + +export type SupportedNetworkFieldType = NetworkFieldType; + +export type SupportedAggregations = SupportedNetworkFieldType[]; +export type UnsupportedAggregations = NetworkFieldType[]; + +export type ConnectionStatus = ObjectValues; + +export type RemoteAggregation = { [key: string]: Aggregations | NumericAggregations }; + +export type NetworkAggregation = { + bucket_count: number; + buckets: Bucket[]; +}; diff --git a/modules/server/src/network/util.ts b/modules/server/src/network/util.ts index def87618d..aba6011f1 100644 --- a/modules/server/src/network/util.ts +++ b/modules/server/src/network/util.ts @@ -1,15 +1,5 @@ import { DocumentNode, GraphQLResolveInfo, print } from 'graphql'; import graphqlFields from 'graphql-fields'; -import { NetworkAggregationConfig, SupportedNetworkFieldType } from './types'; - -/** - * Returns an array of all field/type - * @param configs - * @returns Returns an array of all field/type - */ -export const getAllTypes = (configs: NetworkAggregationConfig[]): SupportedNetworkFieldType[] => { - return configs.flatMap((config) => config.supportedAggregations); -}; /* * GraphQL AST => String @@ -31,12 +21,21 @@ export const fulfilledPromiseFilter = (result: unknown): result is Resul }; /** - * Returns requested fields + * Turns GraphQLResolveInfo into a map of the requested fields * * @param info GQL request info object - * @returns */ -export const getRequestedFields = (info: GraphQLResolveInfo) => { +export const resolveInfoToMap = (info: GraphQLResolveInfo, key: string) => { const requestedFields = graphqlFields(info); - return { requestedAggregations: Object.keys(requestedFields.aggregations) }; + const aggregations = requestedFields[key]; + + // ensure __typename will be queried to network nodes + const aggs = Object.keys(aggregations).reduce((aggs, key) => { + const element = aggregations[key]; + if (!element.hasOwnProperty('__typename')) { + element['__typename'] = {}; + } + return { ...aggs, [key]: element }; + }, {}); + return aggs; };