diff --git a/app.js b/app.js index b65126aa0..c180d2c04 100644 --- a/app.js +++ b/app.js @@ -1,8 +1,10 @@ const express = require('express'); const morgan = require('morgan'); const path = require('path'); +const uuid = require('uuid'); const merge = require('lodash/merge'); const helmet = require('helmet'); +const plugins = require('./services/plugins'); const compression = require('compression'); const {HELMET_CONFIGURATION} = require('./config'); const {MOUNT_PATH} = require('./url'); @@ -11,6 +13,25 @@ const debug = require('debug')('talk:app'); const app = express(); +// Request Identity Middleware +app.use((req, res, next) => { + req.id = uuid.v4(); + + next(); +}); + +//============================================================================== +// PLUGIN PRE APPLICATION MIDDLEWARE +//============================================================================== + +// Inject server route plugins. +plugins.get('server', 'app').forEach(({plugin, app: callback}) => { + debug(`added plugin '${plugin.name}'`); + + // Pass the app to the plugin to mount it's routes. + callback(app); +}); + //============================================================================== // APPLICATION WIDE MIDDLEWARE //============================================================================== diff --git a/client/coral-admin/src/routes/Moderation/graphql.js b/client/coral-admin/src/routes/Moderation/graphql.js index 5758315eb..86bc524b8 100644 --- a/client/coral-admin/src/routes/Moderation/graphql.js +++ b/client/coral-admin/src/routes/Moderation/graphql.js @@ -152,6 +152,10 @@ export function cleanUpQueue(root, queue, sortOrder, queueConfig) { let nodes = root[queue].nodes; let hasNextPage = root[queue].hasNextPage; + if (!nodes.length) { + return root; + } + if (queueConfig) { nodes = root[queue].nodes.filter((comment) => commentBelongToQueue(queue, comment, queueConfig)); } @@ -161,8 +165,8 @@ export function cleanUpQueue(root, queue, sortOrder, queueConfig) { sortOrder, ); - if (nodes.length > 2) { - nodes = nodes.slice(0, 2); + if (nodes.length > 100) { + nodes = nodes.slice(0, 100); hasNextPage = true; } diff --git a/client/coral-ui/components/Dialog.css b/client/coral-ui/components/Dialog.css new file mode 100644 index 000000000..47bc43918 --- /dev/null +++ b/client/coral-ui/components/Dialog.css @@ -0,0 +1,13 @@ + +.dialog { +} + +:global(.backdrop) { + left: 0; + top: 0; + width: 100%; + height: 100%; + position: absolute; + background-color: black; + opacity: 0.1; +} diff --git a/client/coral-ui/components/Dialog.js b/client/coral-ui/components/Dialog.js index a7d34a512..bba386026 100644 --- a/client/coral-ui/components/Dialog.js +++ b/client/coral-ui/components/Dialog.js @@ -2,6 +2,8 @@ import React, {Component} from 'react'; import PropTypes from 'prop-types'; import dialogPolyfill from 'dialog-polyfill'; import 'dialog-polyfill/dialog-polyfill.css'; +import styles from './Dialog.css'; +import {Portal} from 'react-portal'; export default class Dialog extends Component { static propTypes = { @@ -52,13 +54,15 @@ export default class Dialog extends Component { const {children, className = '', onClose, onCancel, open, ...rest} = this.props; // eslint-disable-line return ( - { this.dialog = el; }} - className={`mdl-dialog ${className}`} - {...rest} - > - {children} - + + { this.dialog = el; }} + className={`mdl-dialog ${className} ${styles.dialog}`} + {...rest} + > + {children} + + ); } } diff --git a/graph/context.js b/graph/context.js index d6d85a651..e894c1534 100644 --- a/graph/context.js +++ b/graph/context.js @@ -42,14 +42,15 @@ const decorateContextPlugins = (context, contextPlugins) => { * Stores the request context. */ class Context { - constructor({user = null}) { + constructor(parent) { - // Generate a new context id for the request. - this.id = uuid.v4(); + // Generate a new context id for the request if the parent doesn't provide + // one. + this.id = parent.id || uuid.v4(); - // Load the current logged in user to `user`, otherwise this'll be null. - if (user) { - this.user = user; + // Load the current logged in user to `user`, otherwise this will be null. + if (parent.user) { + this.user = parent.user; } // Attach the connectors. @@ -66,6 +67,9 @@ class Context { // Bind the publish/subscribe to the context. this.pubsub = pubsub.getClient(); + + // Bind the parent context. + this.parent = parent; } } diff --git a/package.json b/package.json index d2bae9089..10626267e 100644 --- a/package.json +++ b/package.json @@ -160,6 +160,7 @@ "react-mdl": "^1.7.2", "react-mdl-selectfield": "^0.2.0", "react-paginate": "^5.0.0", + "react-portal": "^4.1.2", "react-recaptcha": "^2.2.6", "react-redux": "^4.4.5", "react-router": "^3.0.0", diff --git a/plugins.js b/plugins.js index dfe3d3312..487825be7 100644 --- a/plugins.js +++ b/plugins.js @@ -55,7 +55,8 @@ const hookSchemas = { loaders: Joi.func().maxArity(1), mutators: Joi.func().maxArity(1), resolvers: Joi.object().pattern(/\w/, Joi.object().pattern(/(?:__resolveType|\w+)/, Joi.func())), - typeDefs: Joi.string() + typeDefs: Joi.string(), + schemaLevelResolveFunction: Joi.func(), }; /** @@ -172,7 +173,7 @@ class PluginSection { if (this.required) { return; } - + this.required = true; this.plugins.forEach((plugin) => { diff --git a/test/e2e/page_objects/admin.js b/test/e2e/page_objects/admin.js index aa3b58ab5..c76a45d9e 100644 --- a/test/e2e/page_objects/admin.js +++ b/test/e2e/page_objects/admin.js @@ -57,6 +57,10 @@ module.exports = { suspendUserDialog: '.talk-admin-suspend-user-dialog', suspendUserConfirmButton: '.talk-admin-suspend-user-dialog-confirm', supendUserSendButton: '.talk-admin-suspend-user-dialog-send', + usernameDialog: '.talk-reject-username-dialog', + usernameDialogButtons: '.talk-reject-username-dialog-buttons', + usernameDialogSuspend: '.talk-reject-username-dialog-button-k', + usernameDialogSuspensionMessage: '.talk-reject-username-dialog-suspension-message', toast: '.toastify', toastClose: '.toastify__close', }, @@ -97,10 +101,6 @@ module.exports = { flaggedUser:'.talk-admin-community-flagged-user', flaggedUserApproveButton: '.talk-admin-flagged-user-approve-button', flaggedUserRejectButton: '.talk-admin-flagged-user-reject-button', - usernameDialog: '.talk-reject-username-dialog', - usernameDialogButtons: '.talk-reject-username-dialog-buttons', - usernameDialogSuspend: '.talk-reject-username-dialog-button-k', - usernameDialogSuspensionMessage: '.talk-reject-username-dialog-suspension-message' }, sections: { people: { @@ -137,6 +137,10 @@ module.exports = { this.parent .click('@drawerOverlay') .waitForElementNotPresent('@drawerOverlay'); + + // Wait a bit to let animations terminate cleanly. + this.api.pause(200); + return this.parent; }, }], diff --git a/test/e2e/specs/04_userStatus.js b/test/e2e/specs/04_userStatus.js index 41540db65..011f59056 100644 --- a/test/e2e/specs/04_userStatus.js +++ b/test/e2e/specs/04_userStatus.js @@ -64,15 +64,18 @@ module.exports = { .click('@flaggedUserRejectButton'); }, 'admin suspends the user': (client) => { + const adminPage = client.page.admin(); const community = client.page.admin().section.community; - community + adminPage .waitForElementVisible('@usernameDialog') .waitForElementVisible('@usernameDialogButtons') .waitForElementVisible('@usernameDialogSuspend') .click('@usernameDialogSuspend') .waitForElementVisible('@usernameDialogSuspensionMessage') - .click('@usernameDialogSuspend') + .click('@usernameDialogSuspend'); + + community .waitForElementNotPresent('@flaggedUser'); }, 'admin logs out': (client) => { diff --git a/yarn.lock b/yarn.lock index 282e501e1..c380bcd3a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7473,6 +7473,12 @@ react-paginate@^5.0.0: prop-types "^15.6.0" react-addons-create-fragment "^15.0.0" +react-portal@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/react-portal/-/react-portal-4.1.2.tgz#7f28f3c8c2ed5c541907c0ed0f24e8996acf627f" + dependencies: + prop-types "^15.5.8" + react-recaptcha@^2.2.6: version "2.3.5" resolved "https://registry.yarnpkg.com/react-recaptcha/-/react-recaptcha-2.3.5.tgz#a5db337125bb00fb13c2fa2e4ebfbe8b0cd06bb7"