diff --git a/client/coral-framework/graphql/mutations/index.js b/client/coral-framework/graphql/mutations/index.js index 56f63b372..c9152e904 100644 --- a/client/coral-framework/graphql/mutations/index.js +++ b/client/coral-framework/graphql/mutations/index.js @@ -8,7 +8,6 @@ import REMOVE_COMMENT_TAG from './removeCommentTag.graphql'; import IGNORE_USER from './ignoreUser.graphql'; import STOP_IGNORING_USER from './stopIgnoringUser.graphql'; import withMutation from '../../hocs/withMutation'; -import {getFragmentDocument} from '../../services/registry'; export const withPostComment = withMutation( gql` @@ -17,7 +16,6 @@ export const withPostComment = withMutation( ...CreateCommentResponse } } - ${getFragmentDocument('CreateCommentResponse')} `, { props: ({mutate}) => ({ postComment: comment => { diff --git a/client/coral-framework/helpers/plugins.js b/client/coral-framework/helpers/plugins.js index a21eaba4b..bcf6e4c50 100644 --- a/client/coral-framework/helpers/plugins.js +++ b/client/coral-framework/helpers/plugins.js @@ -5,8 +5,7 @@ import flattenDeep from 'lodash/flattenDeep'; import uniq from 'lodash/uniq'; import pick from 'lodash/pick'; import plugins from 'pluginsConfig'; -import {gql} from 'react-apollo'; -import {getDefinitionName} from 'coral-framework/utils'; +import {getDefinitionName, mergeDocuments} from 'coral-framework/utils'; export const pluginReducers = merge( ...plugins @@ -44,8 +43,7 @@ function getComponentFragments(components) { // Assemble arguments for `gql` to call it directly without using template literals. res[key].spreads = `...${res[key].spreads.join('\n...')}\n`; - const literals = ['', ...res[key].definitions.map(() => '\n')]; - res[key].definitions = gql.apply(null, [literals, ...res[key].definitions]); + res[key].definitions = mergeDocuments(res[key].definitions); }); return res; @@ -86,7 +84,7 @@ export function getSlotsFragments(slots) { export function getGraphQLConfigs() { return plugins - .map(o => o.module.mutations && pick(o.module, ['mutations', 'queries', 'fragments'])) + .map(o => pick(o.module, ['mutations', 'queries', 'fragments'])) .filter(o => o); } diff --git a/client/coral-framework/hocs/withMutation.js b/client/coral-framework/hocs/withMutation.js index 9c5bad8fd..f3f092187 100644 --- a/client/coral-framework/hocs/withMutation.js +++ b/client/coral-framework/hocs/withMutation.js @@ -1,7 +1,8 @@ +import * as React from 'react'; import {graphql} from 'react-apollo'; import merge from 'lodash/merge'; import uniq from 'lodash/uniq'; -import {getMutationOptions} from 'coral-framework/services/registry'; +import {getMutationOptions, resolveFragments} from 'coral-framework/services/registry'; import {store} from 'coral-framework/services/store'; import {getDefinitionName} from '../utils'; @@ -73,6 +74,18 @@ export default (document, config) => WrappedComponent => { }; return config.props({...data, mutate}); }; - const wrapped = graphql(document, {...config, props: wrappedProps})(WrappedComponent); - return wrapped; + + // Lazily resolve fragments from registry to support circular dependencies. + let memoized = null; + const getWrapped = () => { + if (!memoized) { + memoized = graphql(resolveFragments(document), {...config, props: wrappedProps})(WrappedComponent); + } + return memoized; + }; + + return (props) => { + const Wrapped = getWrapped(); + return ; + }; }; diff --git a/client/coral-framework/hocs/withQuery.js b/client/coral-framework/hocs/withQuery.js index 42dfe7b35..6a5fa7c14 100644 --- a/client/coral-framework/hocs/withQuery.js +++ b/client/coral-framework/hocs/withQuery.js @@ -1,5 +1,6 @@ +import * as React from 'react'; import {graphql} from 'react-apollo'; -import {getQueryOptions} from 'coral-framework/services/registry'; +import {getQueryOptions, resolveFragments} from 'coral-framework/services/registry'; import {getDefinitionName, separateDataAndRoot} from '../utils'; /** @@ -33,6 +34,16 @@ export default (document, config) => WrappedComponent => { }; }; - const wrapped = graphql(document, {...config, options: wrappedOptions})(WrappedComponent); - return wrapped; + let memoized = null; + const getWrapped = () => { + if (!memoized) { + memoized = graphql(resolveFragments(document), {...config, options: wrappedOptions})(WrappedComponent); + } + return memoized; + }; + + return (props) => { + const Wrapped = getWrapped(); + return ; + }; }; diff --git a/client/coral-framework/services/registry.js b/client/coral-framework/services/registry.js index d35861663..8fe934730 100644 --- a/client/coral-framework/services/registry.js +++ b/client/coral-framework/services/registry.js @@ -1,7 +1,7 @@ -import {gql} from 'react-apollo'; -import {getDefinitionName} from 'coral-framework/utils'; +import {getDefinitionName, mergeDocuments} from 'coral-framework/utils'; import {getGraphQLConfigs} from 'coral-framework/helpers/plugins'; import globalFragments from 'coral-framework/graphql/fragments'; +import uniq from 'lodash/uniq'; const fragments = {}; const mutationOptions = {}; @@ -144,6 +144,11 @@ export function getQueryOptions(key) { */ export function getFragmentDocument(key) { init(); + + if (!(key in fragments)) { + return ''; + } + let documents = fragments[key] ? fragments[key].documents : []; let fields = fragments[key] ? `...${fragments[key].names.join('\n...')}\n` : ' __typename'; @@ -153,8 +158,7 @@ export function getFragmentDocument(key) { ${fields} } `; - const literals = [main, ...documents.map(() => '\n')]; - return gql.apply(null, [literals, ...documents]); + return mergeDocuments([main, ...documents]); } // The fragments and configs are lazily loaded to allow circular dependencies to work. @@ -174,3 +178,21 @@ function init() { getGraphQLConfigs().forEach(cfg => registerConfig(cfg)); } +export function resolveFragments(document) { + if (document.loc.source) { + + // resolve fragments from registry + const matchedSubFragments = document.loc.source.body.match(/\.\.\.(.*)/g) || []; + const subFragments = + uniq(matchedSubFragments.map(f => f.replace('...', ''))) + .map(key => getFragmentDocument(key)) + .filter(i => i); + + if (subFragments.length > 0) { + return mergeDocuments([document, ...subFragments]); + } + } else { + console.warn('Can only resolve fragments from documents definied using the gql tag.'); + } + return document; +} diff --git a/client/coral-framework/utils/index.js b/client/coral-framework/utils/index.js index 598fb59ab..70ce89bf7 100644 --- a/client/coral-framework/utils/index.js +++ b/client/coral-framework/utils/index.js @@ -1,3 +1,5 @@ +import {gql} from 'react-apollo'; + export const getTotalActionCount = (type, comment) => { return comment.action_summaries .filter(s => s.__typename === type) @@ -61,3 +63,11 @@ export function separateDataAndRoot( root, }; } + +export function mergeDocuments(documents) { + const main = typeof documents[0] === 'string' ? documents[0] : documents[0].loc.source.body; + const substitutions = documents.slice(1); + const literals = [main, ...substitutions.map(() => '\n')]; + return gql.apply(null, [literals, ...substitutions]); +} +