Adapting withMulti and withSingle

This commit is contained in:
SachaG 2018-06-05 11:51:25 +09:00
parent dbabb98ed4
commit 809bdd05de
7 changed files with 147 additions and 135 deletions

View file

@ -2,7 +2,7 @@ import { registerComponent, Components, getCollection, Utils } from 'meteor/vulc
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import withCurrentUser from '../containers/withCurrentUser.js';
import withList from '../containers/withList.js';
import withList from '../containers/withMulti.js';
import { FormattedMessage, intlShape } from 'meteor/vulcan:i18n';
import { getFieldValue } from './Card.jsx';

View file

@ -38,29 +38,32 @@ import React, { Component } from 'react';
import { withApollo, graphql } from 'react-apollo';
import gql from 'graphql-tag';
import update from 'immutability-helper';
import { getSetting, getFragment, getFragmentName, getCollection } from 'meteor/vulcan:core';
import {
getSetting,
getFragment,
getFragmentName,
getCollection,
Utils,
multiClientTemplate,
} from 'meteor/vulcan:lib';
import Mingo from 'mingo';
import compose from 'recompose/compose';
import withState from 'recompose/withState';
const withList = (options) => {
export default function withMulti (options) {
// console.log(options)
const {
collectionName,
limit = 10,
pollInterval = getSetting('pollInterval', 20000),
totalResolver = true,
enableCache = false,
extraQueries,
} = options;
const collection = options.collection || getCollection(collectionName);
const queryName = options.queryName || `${collection.options.collectionName}ListQuery`;
const listResolverName = collection.options.resolvers.list && collection.options.resolvers.list.name;
const totalResolverName = collection.options.resolvers.total && collection.options.resolvers.total.name;
const typeName = collection.options.typeName;
const resolverName = `${Utils.camelCaseify(typeName)}s`;
let fragment;
@ -75,28 +78,16 @@ const withList = (options) => {
const fragmentName = getFragmentName(fragment);
// build graphql query from options
const query = gql`
query ${queryName}($terms: JSON, $enableCache: Boolean) {
${totalResolver ? `${totalResolverName}(terms: $terms, enableCache: $enableCache)` : ``}
${listResolverName}(terms: $terms, enableCache: $enableCache) {
__typename
...${fragmentName}
}
${extraQueries || ''}
}
${fragment}
`;
const query = gql`${multiClientTemplate({ typeName, fragment, extraQueries })}${fragment}`;
return compose(
// wrap component with Apollo HoC to give it access to the store
withApollo,
// wrap component with HoC that manages the terms object via its state
withState('paginationTerms', 'setPaginationTerms', props => {
// get initial limit from props, or else options
const paginationLimit = props.terms && props.terms.limit || limit;
const paginationLimit = (props.terms && props.terms.limit) || limit;
const paginationTerms = {
limit: paginationLimit,
itemsPerPage: paginationLimit,
@ -107,16 +98,15 @@ const withList = (options) => {
// wrap component with graphql HoC
graphql(
query,
{
alias: 'withList',
alias: `with${typeName}s`,
// graphql query options
options({terms, paginationTerms, client: apolloClient, currentUser}) {
options({ terms, paginationTerms, client: apolloClient, currentUser }) {
// get terms from options, then props, then pagination
const mergedTerms = {...options.terms, ...terms, ...paginationTerms};
const mergedTerms = { ...options.terms, ...terms, ...paginationTerms };
const graphQLOptions = {
variables: {
@ -125,21 +115,21 @@ const withList = (options) => {
},
// note: pollInterval can be set to 0 to disable polling (20s by default)
pollInterval,
reducer: (previousResults, action) => {
// reducer: (previousResults, action) => {
// see queryReducer function defined below
return queryReducer(previousResults, action, collection, mergedTerms, listResolverName, totalResolverName, queryName, apolloClient);
// // see queryReducer function defined below
// return queryReducer(previousResults, action, collection, mergedTerms, listResolverName, totalResolverName, queryName, apolloClient);
},
// },
};
if (options.fetchPolicy) {
graphQLOptions.fetchPolicy = options.fetchPolicy
graphQLOptions.fetchPolicy = options.fetchPolicy;
}
// set to true if running into https://github.com/apollographql/apollo-client/issues/1186
if (options.notifyOnNetworkStatusChange) {
graphQLOptions.notifyOnNetworkStatusChange = options.notifyOnNetworkStatusChange
graphQLOptions.notifyOnNetworkStatusChange = options.notifyOnNetworkStatusChange;
}
return graphQLOptions;
@ -147,18 +137,17 @@ const withList = (options) => {
// define props returned by graphql HoC
props(props) {
// see https://github.com/apollographql/apollo-client/blob/master/packages/apollo-client/src/core/networkStatus.ts
const refetch = props.data.refetch,
// results = Utils.convertDates(collection, props.data[listResolverName]),
results = props.data[listResolverName],
totalCount = props.data[totalResolverName],
networkStatus = props.data.networkStatus,
loadingInitial = props.data.networkStatus === 1,
loading = props.data.networkStatus === 1,
loadingMore = props.data.networkStatus === 2,
error = props.data.error,
propertyName = options.propertyName || 'results';
// results = Utils.convertDates(collection, props.data[listResolverName]),
results = props.data[resolverName].results,
totalCount = props.data[resolverName].totalCount,
networkStatus = props.data.networkStatus,
loadingInitial = props.data.networkStatus === 1,
loading = props.data.networkStatus === 1,
loadingMore = props.data.networkStatus === 2,
error = props.data.error,
propertyName = options.propertyName || 'results';
if (error) {
// eslint-disable-next-line no-console
@ -171,7 +160,7 @@ const withList = (options) => {
loading,
loadingInitial,
loadingMore,
[ propertyName ]: results,
[propertyName]: results,
totalCount,
refetch,
networkStatus,
@ -181,7 +170,13 @@ const withList = (options) => {
// regular load more (reload everything)
loadMore(providedTerms) {
// if new terms are provided by presentational component use them, else default to incrementing current limit once
const newTerms = typeof providedTerms === 'undefined' ? { /*...props.ownProps.terms,*/ ...props.ownProps.paginationTerms, limit: results.length + props.ownProps.paginationTerms.itemsPerPage } : providedTerms;
const newTerms =
typeof providedTerms === 'undefined'
? {
/*...props.ownProps.terms,*/ ...props.ownProps.paginationTerms,
limit: results.length + props.ownProps.paginationTerms.itemsPerPage,
}
: providedTerms;
props.ownProps.setPaginationTerms(newTerms);
},
@ -189,9 +184,11 @@ const withList = (options) => {
// incremental loading version (only load new content)
// note: not compatible with polling
loadMoreInc(providedTerms) {
// get terms passed as argument or else just default to incrementing the offset
const newTerms = typeof providedTerms === 'undefined' ? { ...props.ownProps.terms, ...props.ownProps.paginationTerms, offset: results.length } : providedTerms;
const newTerms =
typeof providedTerms === 'undefined'
? { ...props.ownProps.terms, ...props.ownProps.paginationTerms, offset: results.length }
: providedTerms;
return props.data.fetchMore({
variables: { terms: newTerms }, // ??? not sure about 'terms: newTerms'
@ -201,9 +198,12 @@ const withList = (options) => {
return previousResults;
}
const newResults = {};
newResults[listResolverName] = [...previousResults[listResolverName], ...fetchMoreResult.data[listResolverName]];
newResults[resolverName] = [
...previousResults[resolverName],
...fetchMoreResult.data[resolverName],
];
// return the previous results "augmented" with more
return {...previousResults, ...newResults};
return { ...previousResults, ...newResults };
},
});
},
@ -217,12 +217,19 @@ const withList = (options) => {
}
)
);
}
};
// define query reducer separately
const queryReducer = (previousResults, action, collection, mergedTerms, listResolverName, totalResolverName, queryName, apolloClient) => {
const queryReducer = (
previousResults,
action,
collection,
mergedTerms,
listResolverName,
totalResolverName,
queryName,
apolloClient
) => {
// if collection has no mutations defined, just return previous results
if (!collection.options.mutations) {
return previousResults;
@ -245,19 +252,18 @@ const queryReducer = (previousResults, action, collection, mergedTerms, listReso
const listWithoutDocument = results[listResolverName].filter(doc => doc._id !== document._id);
const newResults = update(results, {
[listResolverName]: { $set: listWithoutDocument }, // ex: postsList
[totalResolverName]: { $set: results[totalResolverName] - 1 } // ex: postsListTotal
[totalResolverName]: { $set: results[totalResolverName] - 1 }, // ex: postsListTotal
});
return newResults;
}
};
// add document to a results object
const addToResults = (results, document) => {
return update(results, {
[listResolverName]: { $unshift: [document] },
[totalResolverName]: { $set: results[totalResolverName] + 1 }
[totalResolverName]: { $set: results[totalResolverName] + 1 },
});
}
};
// reorder results according to a sort
const reorderResults = (results, sort) => {
@ -268,7 +274,7 @@ const queryReducer = (previousResults, action, collection, mergedTerms, listReso
const sortedList = cursor.sort(sort).all();
results[listResolverName] = sortedList;
return results;
}
};
// console.log('// withList reducer');
// console.log('queryName: ', queryName);
@ -280,7 +286,6 @@ const queryReducer = (previousResults, action, collection, mergedTerms, listReso
// console.log('action: ', action);
switch (action.operationName) {
case newMutationName:
// if new document belongs to current list (based on view selector), add it
const newDocument = action.result.data[newMutationName];
@ -297,7 +302,7 @@ const queryReducer = (previousResults, action, collection, mergedTerms, listReso
const editedDocument = action.result.data[editMutationName];
if (mingoQuery.test(editedDocument)) {
// edited document belongs to the list
if (!_.findWhere(previousResults[listResolverName], {_id: editedDocument._id})) {
if (!_.findWhere(previousResults[listResolverName], { _id: editedDocument._id })) {
// if document wasn't already in list, add it
newResults = addToResults(previousResults, editedDocument);
}
@ -332,8 +337,5 @@ const queryReducer = (previousResults, action, collection, mergedTerms, listReso
return {
[listResolverName]: [...newResults[listResolverName]],
[totalResolverName]: newResults[totalResolverName],
}
}
export default withList;
};
};

View file

@ -2,15 +2,20 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import { getSetting, getFragment, getFragmentName, getCollection } from 'meteor/vulcan:core';
import { getSetting, getFragment, getFragmentName, getCollection, singleClientTemplate } from 'meteor/vulcan:lib';
export default function withDocument (options) {
export default function withSingle(options) {
const { collectionName, pollInterval = getSetting('pollInterval', 20000), enableCache = false, extraQueries } = options;
const {
collectionName,
pollInterval = getSetting('pollInterval', 20000),
enableCache = false,
extraQueries,
} = options;
const collection = options.collection || getCollection(collectionName);
const queryName = options.queryName || `${collection.options.collectionName}SingleQuery`;
const singleResolverName = collection.options.resolvers.single && collection.options.resolvers.single.name;
const typeName = collection.options.typeName;
const resolverName = typeName;
let fragment;
@ -24,17 +29,10 @@ export default function withDocument (options) {
const fragmentName = getFragmentName(fragment);
return graphql(gql`
query ${queryName}($documentId: String, $slug: String, $enableCache: Boolean) {
${singleResolverName}(documentId: $documentId, slug: $slug, enableCache: $enableCache) {
__typename
...${fragmentName}
}
${extraQueries || ''}
}
${fragment}
`, {
alias: 'withDocument',
const query = gql`${singleClientTemplate({ typeName, fragment, extraQueries })}${fragment}`;
return graphql(query, {
alias: `with${typeName}`,
options({ documentId, slug }) {
const graphQLOptions = {
@ -59,7 +57,7 @@ export default function withDocument (options) {
const props = {
loading: data.loading,
// document: Utils.convertDates(collection, data[singleResolverName]),
[ propertyName ]: data[singleResolverName],
[propertyName]: data[resolverName].result,
fragmentName,
fragment,
data,

View file

@ -69,8 +69,11 @@ export const getDefaultResolvers = (collectionName, resolverOptions = defaultOpt
debug(`--------------- end \x1b[35m${typeName} list\x1b[0m resolver ---------------`);
debug('');
// get total count of documents matching the selector
const totalCount = await Connectors.count(collection, selector);
// return results
return restrictedDocs;
return { results: restrictedDocs, totalCount };
},
},
@ -118,31 +121,9 @@ export const getDefaultResolvers = (collectionName, resolverOptions = defaultOpt
debug('');
// filter out disallowed properties and return resulting document
return restrictedDoc;
return { result: restrictedDoc };
},
},
// resolver for returning the total number of documents matching a set of query terms
// total: {
// name: resolverOptions.legacy ? `${typeName}Total` : `total${typeName}s`,
// description: `The total count of ${typeName} documents matching a set of query terms`,
// async resolver(root, { terms, enableCache }, context, { cacheControl }) {
// if (cacheControl && enableCache) {
// const maxAge = resolverOptions.cacheMaxAge || defaultOptions.cacheMaxAge;
// cacheControl.setCacheHint({ maxAge });
// }
// const collection = context[collectionName];
// const { selector } = await collection.getParameters(terms, {}, context);
// const total = await Connectors.count(collection, selector);
// return total;
// },
// },
};
};

View file

@ -26,8 +26,8 @@ export { default as RouterHook } from './components/RouterHook.jsx';
export { default as withAccess } from "./containers/withAccess.js";
export { default as withMessages } from "./containers/withMessages.js";
export { default as withList } from './containers/withList.js';
export { default as withDocument } from './containers/withDocument.js';
export { default as withMulti } from './containers/withMulti.js';
export { default as withSingle } from './containers/withSingle.js';
export { default as withCreate } from './containers/withCreate.js';
export { default as withUpdate } from './containers/withUpdate.js';
export { default as withDelete } from './containers/withDelete.js';
@ -40,3 +40,5 @@ export { default as withUpsert } from './containers/withUpsert.js';
export { default as withNew } from './containers/withCreate.js';
export { default as withEdit } from './containers/withUpdate.js';
export { default as withRemove } from './containers/withDelete.js';
export { default as withList } from './containers/withMulti.js';
export { default as withDocument } from './containers/withSingle.js';

View file

@ -1,4 +1,5 @@
import { Utils } from './utils';
import { getFragmentName } from './fragments';
export const convertToGraphQL = (fields, indentation) => {
return fields.length > 0 ? fields.map(f => fieldTemplate(f, indentation)).join(`\n`) : '';
@ -193,13 +194,13 @@ export const multiInputTemplate = ({ typeName }) =>
The type for the return value when querying for a single document
type SingleMovieOuput{
data: Movie
result: Movie
}
*/
export const singleOutputTemplate = ({ typeName }) =>
`type Single${typeName}Output{
data: ${typeName}
result: ${typeName}
}`;
/*
@ -207,17 +208,42 @@ export const singleOutputTemplate = ({ typeName }) =>
The type for the return value when querying for multiple documents
type MultiMovieOuput{
data: [Movie]
results: [Movie]
totalCount: Int
}
*/
export const multiOutputTemplate = ({ typeName }) =>
`type Multi${typeName}Output{
data: [${typeName}]
results: [${typeName}]
totalCount: Int
}`;
/* ------------------------------------- Query Queries ------------------------------------- */
export const singleClientTemplate = ({ typeName, fragment, extraQueries }) =>
`query Single${typeName}Query($input: Single${typeName}Input) {
${Utils.camelCaseify(typeName)}(input: $input) {
__typename
result {
...${getFragmentName(fragment)}
}
}
${extraQueries ? extraQueries : ''}
}`;
export const multiClientTemplate = ({ typeName, fragment, extraQueries }) =>
`query Multi${typeName}Query($input: Multi${typeName}Input) {
${Utils.camelCaseify(typeName)}s(input: $input) {
__typename
results {
...${getFragmentName(fragment)}
}
totalCount
}
${extraQueries ? extraQueries : ''}
}`;
/* ------------------------------------- Mutation Types ------------------------------------- */
/*
@ -369,3 +395,5 @@ export const mutationOutputTemplate = ({ typeName }) =>
`type ${typeName}Output{
data: ${typeName}
}`;
/* ------------------------------------- Mutation Queries ------------------------------------- */

View file

@ -29,4 +29,5 @@ export * from './startup.js';
export * from './errors.js';
export * from './intl.js';
export * from './detect_locale.js';
export * from './graphql_templates.js';
// export * from './resolvers.js';