diff --git a/src/client/components/forms/field-types/Array/index.js b/src/client/components/forms/field-types/Array/index.js index 2870075494..d052eca569 100644 --- a/src/client/components/forms/field-types/Array/index.js +++ b/src/client/components/forms/field-types/Array/index.js @@ -1,4 +1,4 @@ -import React, { useEffect, useReducer, useCallback } from 'react'; +import React, { useEffect, useReducer, useCallback, useState } from 'react'; import PropTypes from 'prop-types'; import { DragDropContext, Droppable } from 'react-beautiful-dnd'; import { v4 as uuidv4 } from 'uuid'; @@ -17,7 +17,7 @@ import './index.scss'; const baseClass = 'field-type array'; -const Array = (props) => { +const ArrayFieldType = (props) => { const { label, name, @@ -48,6 +48,8 @@ const Array = (props) => { return validationResult; }, [validate, maxRows, minRows, required]); + const [disableFormData, setDisableFormData] = useState(false); + const { showError, errorMessage, @@ -56,7 +58,7 @@ const Array = (props) => { } = useFieldType({ path, validate: memoizedValidate, - disableFormData: true, + disableFormData, initialData: initialData?.length, defaultValue: defaultValue?.length, required, @@ -113,6 +115,16 @@ const Array = (props) => { }); }, [dataToInitialize]); + useEffect(() => { + if (value === 0 && dataToInitialize.length > 0 && disableFormData) { + setDisableFormData(false); + setValue(value); + } else if (value > 0 && !disableFormData) { + setDisableFormData(true); + setValue(value); + } + }, [value, setValue, disableFormData, dataToInitialize]); + return (
@@ -171,7 +183,7 @@ const Array = (props) => { ); }; -Array.defaultProps = { +ArrayFieldType.defaultProps = { label: '', defaultValue: [], initialData: [], @@ -185,7 +197,7 @@ Array.defaultProps = { permissions: {}, }; -Array.propTypes = { +ArrayFieldType.propTypes = { defaultValue: PropTypes.arrayOf( PropTypes.shape({}), ), @@ -211,4 +223,4 @@ Array.propTypes = { }), }; -export default withCondition(Array); +export default withCondition(ArrayFieldType); diff --git a/src/client/components/forms/field-types/Blocks/index.js b/src/client/components/forms/field-types/Blocks/index.js index 4d2d1ede8b..3f9d631f65 100644 --- a/src/client/components/forms/field-types/Blocks/index.js +++ b/src/client/components/forms/field-types/Blocks/index.js @@ -1,5 +1,5 @@ import React, { - useEffect, useReducer, useCallback, + useEffect, useReducer, useCallback, useState, } from 'react'; import PropTypes from 'prop-types'; import { DragDropContext, Droppable } from 'react-beautiful-dnd'; @@ -50,6 +50,8 @@ const Blocks = (props) => { return validationResult; }, [validate, maxRows, minRows, singularLabel, blocks, required]); + const [disableFormData, setDisableFormData] = useState(false); + const { showError, errorMessage, @@ -58,7 +60,7 @@ const Blocks = (props) => { } = useFieldType({ path, validate: memoizedValidate, - disableFormData: true, + disableFormData, initialData: initialData?.length, defaultValue: defaultValue?.length, required, @@ -126,6 +128,17 @@ const Blocks = (props) => { }); }, [dataToInitialize]); + useEffect(() => { + if (value === 0 && dataToInitialize.length > 0 && disableFormData) { + setDisableFormData(false); + setValue(value); + } else if (value > 0 && !disableFormData) { + setDisableFormData(true); + setValue(value); + } + }, [value, setValue, disableFormData, dataToInitialize]); + + return (
@@ -237,6 +250,9 @@ Blocks.defaultProps = { }; Blocks.propTypes = { + blocks: PropTypes.arrayOf( + PropTypes.shape({}), + ).isRequired, defaultValue: PropTypes.arrayOf( PropTypes.shape({}), ), diff --git a/src/collections/graphql/init.js b/src/collections/graphql/init.js index 46c5f1f22d..f2b28213a1 100644 --- a/src/collections/graphql/init.js +++ b/src/collections/graphql/init.js @@ -28,6 +28,7 @@ function registerCollections() { plural, }, fields: initialFields, + timestamps, }, } = collection; @@ -48,18 +49,48 @@ function registerCollections() { collection.graphQL = {}; + const baseFields = { + id: { + type: new GraphQLNonNull(GraphQLString), + }, + }; + + const whereInputFields = [ + ...fields, + ]; + + if (timestamps) { + baseFields.createdAt = { + type: new GraphQLNonNull(GraphQLString), + }; + + baseFields.updatedAt = { + type: new GraphQLNonNull(GraphQLString), + }; + + whereInputFields.push({ + name: 'createdAt', + label: 'Created At', + type: 'date', + }); + + whereInputFields.push({ + name: 'updatedAt', + label: 'Upated At', + type: 'date', + }); + } + collection.graphQL.type = this.buildObjectType( singularLabel, fields, singularLabel, - { - id: { type: GraphQLString }, - }, + baseFields, ); collection.graphQL.whereInputType = this.buildWhereInputType( singularLabel, - fields, + whereInputFields, singularLabel, ); diff --git a/src/fields/performFieldOperations.js b/src/fields/performFieldOperations.js index e853e71148..2ad4fce517 100644 --- a/src/fields/performFieldOperations.js +++ b/src/fields/performFieldOperations.js @@ -113,6 +113,11 @@ module.exports = async (config, entityConfig, operation) => { const hasRowsOfData = Array.isArray(data[field.name]); const rowCount = hasRowsOfData ? data[field.name].length : 0; + if (data[field.name] === '0' || data[field.name] === 0 || data[field.name] === null) { + const updatedData = data; + updatedData[field.name] = []; + } + validationPromises.push(createValidationPromise(rowCount, field, path)); } else { validationPromises.push(createValidationPromise(data[field.name], field, path)); diff --git a/src/graphql/index.js b/src/graphql/index.js index 2df06f8dfb..2a9d324987 100644 --- a/src/graphql/index.js +++ b/src/graphql/index.js @@ -14,11 +14,9 @@ const errorHandler = require('./errorHandler'); const { access } = require('../auth/graphql/resolvers'); class GraphQL { - constructor(init, req, res) { + constructor(init) { Object.assign(this, init); this.init = this.init.bind(this); - this.req = req; - this.res = res; this.types = { blockTypes: {}, @@ -46,9 +44,7 @@ class GraphQL { this.buildPoliciesType = buildPoliciesType.bind(this); this.initCollections = initCollections.bind(this); this.initGlobals = initGlobals.bind(this); - } - init() { this.initCollections(); this.initGlobals(); @@ -69,34 +65,37 @@ class GraphQL { const query = new GraphQLObjectType(this.Query); const mutation = new GraphQLObjectType(this.Mutation); - const schema = new GraphQLSchema({ + + this.schema = new GraphQLSchema({ query, mutation, }); - let errorExtensions = []; - let errorExtensionIteration = 0; + this.errorExtensions = []; + this.errorExtensionIteration = 0; - const extensions = async (info) => { + this.extensions = async (info) => { const { result } = info; if (result.errors) { const afterErrorHook = typeof this.config.hooks.afterError === 'function' ? this.config.hooks.afterError : null; - errorExtensions = await errorHandler(info, this.config.debug, afterErrorHook); + this.errorExtensions = await errorHandler(info, this.config.debug, afterErrorHook); } return null; }; + } + init(req, res) { return graphQLHTTP({ - schema, + schema: this.schema, customFormatErrorFn: () => { const response = { - ...errorExtensions[errorExtensionIteration], + ...this.errorExtensions[this.errorExtensionIteration], }; - errorExtensionIteration += 1; + this.errorExtensionIteration += 1; return response; }, - extensions, - context: { req: this.req, res: this.res }, + extensions: this.extensions, + context: { req, res }, }); } } diff --git a/src/graphql/schema/withNullableType.js b/src/graphql/schema/withNullableType.js index 70ff7d2c48..739c382a38 100644 --- a/src/graphql/schema/withNullableType.js +++ b/src/graphql/schema/withNullableType.js @@ -1,7 +1,9 @@ const { GraphQLNonNull } = require('graphql'); const withNullableType = (field, type) => { - if (field.required && !field.localized) { + const hasReadAccessControl = field.access && field.access.read; + + if (field.required && !field.localized && !hasReadAccessControl) { return new GraphQLNonNull(type); } diff --git a/src/index.js b/src/index.js index 6b3dfdb50b..473f0f8b1a 100644 --- a/src/index.js +++ b/src/index.js @@ -55,10 +55,12 @@ class Payload { this.router.get('/access', access(this.config)); + const graphQLHandler = new GraphQL(this); + this.router.use( this.config.routes.graphQL, identifyAPI('GraphQL'), - (req, res) => new GraphQL(this, req, res).init()(req, res), + (req, res) => graphQLHandler.init(req, res)(req, res), ); this.router.get(this.config.routes.graphQLPlayground, graphQLPlayground({ diff --git a/src/mongoose/connect.js b/src/mongoose/connect.js index 8ddc26e97c..3e11c90464 100644 --- a/src/mongoose/connect.js +++ b/src/mongoose/connect.js @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ const mongoose = require('mongoose'); const connectMongoose = async (url) => { @@ -12,16 +13,16 @@ const connectMongoose = async (url) => { successfulConnectionMessage = 'Connected to in-memory Mongo server successfully!'; } - mongoose.connect(urlToConnect, { - useNewUrlParser: true, - useUnifiedTopology: true, - }, (err) => { - if (err) { - console.log('Unable to connect to the Mongo server. Please start the server. Error:', err); - } else { - console.log(successfulConnectionMessage); - } - }); + try { + await mongoose.connect(urlToConnect, { + useNewUrlParser: true, + useUnifiedTopology: true, + }); + console.log(successfulConnectionMessage); + } catch (err) { + console.error('Error: cannot connect to MongoDB. Details: ', err); + process.exit(1); + } }; module.exports = connectMongoose;