diff --git a/client/coral-admin/src/AppRouter.js b/client/coral-admin/src/AppRouter.js
index d8e1b5377..362143d38 100644
--- a/client/coral-admin/src/AppRouter.js
+++ b/client/coral-admin/src/AppRouter.js
@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
import {Router, Route, IndexRedirect, IndexRoute} from 'react-router';
import Configure from 'routes/Configure';
-import Dashboard from 'routes/Dashboard';
import Install from 'routes/Install';
import Stories from 'routes/Stories';
import Community from 'routes/Community/containers/Community';
@@ -18,7 +17,6 @@ const routes = (
-
{/* Community Routes */}
diff --git a/client/coral-admin/src/components/Drawer.js b/client/coral-admin/src/components/Drawer.js
index 70da8ad5e..29ee8d17e 100644
--- a/client/coral-admin/src/components/Drawer.js
+++ b/client/coral-admin/src/components/Drawer.js
@@ -12,20 +12,14 @@ const CoralDrawer = ({handleLogout, auth = {}}) => (
{ auth && auth.user && can(auth.user, 'ACCESS_ADMIN') ?
-
- {t('configure.dashboard')}
-
{
can(auth.user, 'MODERATE_COMMENTS') && (
-
{t('configure.moderate')}
-
+
)
}
-
- {t('configure.dashboard')}
-
{
can(auth.user, 'MODERATE_COMMENTS') && (
-
{t('configure.moderate')}
{(root.premodCount !== 0 || root.reportedCount !== 0) && }
-
+
)
}
{
- return (
-
-
{t('dashboard.most_conversations')}
-
-
{t('streams.article')}
-
{t('dashboard.comment_count')}
-
-
- {
- assets.length
- ? assets.map((asset) => {
- return (
-
-
Moderate
-
{asset.commentCount}
-
- {asset.title}
-
-
{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}
-
- );
- })
- :
{t('dashboard.no_activity')}
- }
-
-
- );
-};
-
-ActivityWidget.propTypes = {
- assets: PropTypes.arrayOf(PropTypes.shape({
- id: PropTypes.string,
- url: PropTypes.string,
- commentCount: PropTypes.number,
- author: PropTypes.string,
- created_at: PropTypes.string
- })).isRequired
-};
-
-export default ActivityWidget;
diff --git a/client/coral-admin/src/routes/Dashboard/components/CountdownTimer.js b/client/coral-admin/src/routes/Dashboard/components/CountdownTimer.js
deleted file mode 100644
index a2e6ea9f1..000000000
--- a/client/coral-admin/src/routes/Dashboard/components/CountdownTimer.js
+++ /dev/null
@@ -1,93 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import styles from './Dashboard.css';
-import {Icon} from 'coral-ui';
-
-import t from 'coral-framework/services/i18n';
-const refreshIntervalSeconds = 60 * 5;
-
-// TODO: refactor out storage code into redux.
-
-class CountdownTimer extends React.Component {
-
- static contextTypes = {
- storage: PropTypes.object,
- };
-
- static propTypes = {
- handleTimeout: PropTypes.func.isRequired
- }
-
- constructor (props, context) {
- super(props, context);
- const {storage} = context;
- try {
- if (storage && storage.getItem('coral:dashboardNote') === null) {
- storage.setItem('coral:dashboardNote', 'show');
- }
- } catch (e) {
-
- // above will fail in Private Mode in some browsers.
- }
-
- this.state = {
- secondsUntilRefresh: refreshIntervalSeconds,
- dashboardNote: (storage && storage.getItem('coral:dashboardNote')) || 'show'
- };
- }
-
- componentWillMount () {
- this.interval = setInterval(() => { // the countdown timer
- let nextCount = this.state.secondsUntilRefresh - 1;
- if (nextCount < 0) {
- nextCount = refreshIntervalSeconds;
- return this.props.handleTimeout();
- }
- this.setState({secondsUntilRefresh: nextCount});
- }, 1000);
- }
-
- componentWillUnmount () {
- window.clearInterval(this.interval);
- }
-
- formatTime = () => {
- const minutes = Math.floor(this.state.secondsUntilRefresh / 60);
- let seconds = (this.state.secondsUntilRefresh % 60).toString();
- if (seconds.length < 2) {
- seconds = `0${seconds}`;
- }
-
- return `${minutes}:${seconds}`;
- }
-
- dismissNote = () => {
- const {storage} = this.context;
- try {
- if (storage) {
- storage.setItem('coral:dashboardNote', 'hide');
- }
- } catch (e) {
-
- // when setItem fails in Safari Private mode
- this.setState({dashboardNote: 'hide'});
- }
- }
-
- render () {
- const {storage} = this.context;
- const hideReloadNote = (storage && storage.getItem('coral:dashboardNote') === 'hide') ||
- this.state.dashboardNote === 'hide'; // for Safari Incognito
- return (
-
- ×
- {t('dashboard.next_update', this.formatTime())} {t('dashboard.auto_update')}
-
- );
- }
-}
-
-export default CountdownTimer;
diff --git a/client/coral-admin/src/routes/Dashboard/components/Dashboard.css b/client/coral-admin/src/routes/Dashboard/components/Dashboard.css
deleted file mode 100644
index aee7e702b..000000000
--- a/client/coral-admin/src/routes/Dashboard/components/Dashboard.css
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * @TODO: deprecated as this file contains styles from multiple components. Please refactor.
- */
-
-.Dashboard {
- display: flex;
- max-width: 1280px;
- margin: 0 auto;
-}
-
-.heading {
- margin: 0;
- font-size: 1.5rem;
- font-weight: bold;
-}
-
-.autoUpdate {
- background-color: #d5d5d5;
- padding: 3px 10px 10px 10px;
- margin-bottom: 0;
-
- i {
- position: relative;
- top: 7px;
- }
-
- b {
- float: right;
- border-radius: 20px;
- cursor: pointer;
- background-color: #c0c0c0;
- width: 30px;
- height: 30px;
- text-align: center;
- top: 4px;
- position: relative;
- line-height: 1.7em;
- font-size: 1.3em;
- }
-}
diff --git a/client/coral-admin/src/routes/Dashboard/components/Dashboard.js b/client/coral-admin/src/routes/Dashboard/components/Dashboard.js
deleted file mode 100644
index ba224a3b9..000000000
--- a/client/coral-admin/src/routes/Dashboard/components/Dashboard.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import React from 'react';
-import FlagWidget from './FlagWidget';
-import ActivityWidget from './ActivityWidget';
-import CountdownTimer from './CountdownTimer';
-import styles from './Dashboard.css';
-
-export default ({root: {assetsByActivity, assetsByFlag}, reloadData}) => (
-
-);
diff --git a/client/coral-admin/src/routes/Dashboard/components/FlagWidget.js b/client/coral-admin/src/routes/Dashboard/components/FlagWidget.js
deleted file mode 100644
index 2f3548cf1..000000000
--- a/client/coral-admin/src/routes/Dashboard/components/FlagWidget.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import {Link} from 'react-router';
-import styles from './Widget.css';
-
-import t from 'coral-framework/services/i18n';
-
-const FlagWidget = ({assets}) => {
-
- return (
-
-
{t('dashboard.most_flags')}
-
-
{t('streams.article')}
-
{t('dashboard.flags')}
-
-
- {
- assets.length
- ? assets.map((asset) => {
- let flagSummary = null;
- if (asset.action_summaries) {
- flagSummary = asset.action_summaries.find((s) => s.__typename === 'FlagAssetActionSummary');
- }
-
- return (
-
-
Moderate
-
{flagSummary ? flagSummary.actionCount : 0}
-
- {asset.title}
-
-
{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}
-
- );
- })
- :
{t('dashboard.no_flags')}
- }
-
-
- );
-};
-
-FlagWidget.propTypes = {
- assets: PropTypes.arrayOf(PropTypes.shape({
- id: PropTypes.string,
- url: PropTypes.string,
- action_summaries: PropTypes.array,
- author: PropTypes.string,
- created_at: PropTypes.string
- })).isRequired
-};
-
-export default FlagWidget;
diff --git a/client/coral-admin/src/routes/Dashboard/components/LikeWidget.js b/client/coral-admin/src/routes/Dashboard/components/LikeWidget.js
deleted file mode 100644
index 7525f7223..000000000
--- a/client/coral-admin/src/routes/Dashboard/components/LikeWidget.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import {Link} from 'react-router';
-import styles from './Widget.css';
-import t from 'coral-framework/services/i18n';
-
-const LikeWidget = ({assets}) => {
-
- return (
-
-
Articles with the most likes
-
-
{t('streams.article')}
-
{t('modqueue.likes')}
-
-
- {
- assets.length
- ? assets.map((asset) => {
- const likeSummary = asset.action_summaries.find((s) => s.type === 'LikeAssetActionSummary');
- return (
-
-
Moderate
-
{likeSummary ? likeSummary.actionCount : 0}
-
- {asset.title}
-
-
{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}
-
- );
- })
- :
{t('dashboard.no_likes')}
- }
-
-
- );
-};
-
-LikeWidget.propTypes = {
- assets: PropTypes.arrayOf(PropTypes.shape({
- id: PropTypes.string,
- url: PropTypes.string,
- action_summaries: PropTypes.array,
- author: PropTypes.string,
- created_at: PropTypes.string
- })).isRequired
-};
-
-export default LikeWidget;
diff --git a/client/coral-admin/src/routes/Dashboard/components/MostLikedCommentsWidget.js b/client/coral-admin/src/routes/Dashboard/components/MostLikedCommentsWidget.js
deleted file mode 100644
index 6bbb67c50..000000000
--- a/client/coral-admin/src/routes/Dashboard/components/MostLikedCommentsWidget.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import React from 'react';
-
-import ModerationQueue from 'coral-admin/src/containers/ModerationQueue/ModerationQueue';
-import styles from './Widget.css';
-import BanUserDialog from 'coral-admin/src/components/BanUserDialog';
-import t from 'coral-framework/services/i18n';
-
-const MostLikedCommentsWidget = (props) => {
- const {
- comments,
- moderation,
- settings,
- handleBanUser,
- showBanUserDialog,
- hideBanUserDialog,
- acceptComment,
- rejectComment
- } = props;
-
- return (
-
-
{t('most_liked_comments')}
-
-
-
- );
-};
-
-export default MostLikedCommentsWidget;
diff --git a/client/coral-admin/src/routes/Dashboard/components/Widget.css b/client/coral-admin/src/routes/Dashboard/components/Widget.css
deleted file mode 100644
index e94d2313f..000000000
--- a/client/coral-admin/src/routes/Dashboard/components/Widget.css
+++ /dev/null
@@ -1,110 +0,0 @@
-/**
- * @TODO: deprecated as this file contains styles from multiple components. Please refactor.
- */
-
-:root {
- --row-height: 60px;
-}
-
-.widget {
- margin: 10px 5px 5px 5px;
- box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.2);
- flex: 1;
- background-color: white;
- box-sizing: border-box;
-}
-
-.widget * {
- box-sizing: border-box;
-}
-
-.heading {
- margin: 0;
- padding-left: 10px;
- font-size: 1.3rem;
- font-weight: 600;
- color: #2c2c2c;
-}
-
-.widgetTable {
- height: calc(var(--row-height) * 10);
-}
-
-.widgetTable + div:after {
- content: '';
- clear: both;
- display: block;
-}
-
-.widgetHead p {
- color: #2c2c2c;
- font-weight: 500;
- padding: 10px;
- text-align: left;
- text-transform: capitalize;
- display: inline-block;
- box-sizing: border-box;
- margin-bottom: 0;
-}
-
-.widgetHead p:last-child {
- float: right;
- margin-right: 100px;
-}
-
-.rowLinkify {
- border-bottom: 1px solid lightgrey;
- color: #555;
- height: var(--row-height);
- padding: 10px;
- transition: background-color 200ms;
-}
-
-.rowLinkify:last-child {
- border-bottom: none;
-}
-
-.rowLinkify:hover {
- background-color: #f8f8f8;
- pointer: default;
-}
-
-.linkToAsset {
- display: inline-block;
- text-decoration: none;
-}
-
-.linkToModerate {
- background-color: #BDBDBD;
- padding: 10px 14px;
- text-decoration: none;
- color: black;
- float: right;
- margin-left: 15px;
- transition: background-color 200ms;
-}
-
-.linkToModerate:hover {
- background-color: #9E9E9E;
-}
-
-.lede {
- font-size: 0.9em;
- color: #aaa;
-}
-
-.assetTitle {
- color: #555;
- text-decoration: none;
- font-size: 1.2em;
- font-weight: 500;
- margin: 0;
-}
-
-.widgetCount {
- color: #555;
- font-size: 1.3em;
- font-weight: 400;
- float: right;
- margin-top: 7px;
-}
diff --git a/client/coral-admin/src/routes/Dashboard/containers/Dashboard.js b/client/coral-admin/src/routes/Dashboard/containers/Dashboard.js
deleted file mode 100644
index e2e9ef28c..000000000
--- a/client/coral-admin/src/routes/Dashboard/containers/Dashboard.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import React from 'react';
-import {connect} from 'react-redux';
-import Dashboard from '../components/Dashboard';
-import {compose, gql} from 'react-apollo';
-import withQuery from 'coral-framework/hocs/withQuery';
-
-import {Spinner} from 'coral-ui';
-
-class DashboardContainer extends React.Component {
-
- reloadData = () => {
- this.props.data.refetch();
- }
-
- render () {
- if (this.props.data.loading) {
- return ;
- }
- return ;
- }
-}
-
-export const witDashboardQuery = withQuery(gql`
- query CoralAdmin_Dashboard($from: Date!, $to: Date!) {
- assetsByFlag: assetMetrics(from: $from, to: $to, sortBy: FLAG) {
- ...CoralAdmin_Metrics
- }
- assetsByActivity: assetMetrics(from: $from, to: $to, sortBy: ACTIVITY) {
- ...CoralAdmin_Metrics
- }
- }
- fragment CoralAdmin_Metrics on Asset {
- id
- title
- url
- author
- created_at
- commentCount
- action_summaries {
- actionCount
- actionableItemCount
- }
- }
-`, {
- options: ({windowStart, windowEnd}) => {
- return {
- variables: {
- from: windowStart,
- to: windowEnd,
- }
- };
- }
- });
-
-const mapStateToProps = (state) => {
- return {
- windowStart: state.dashboard.windowStart,
- windowEnd: state.dashboard.windowEnd,
- };
-};
-
-export default compose(
- connect(mapStateToProps),
- witDashboardQuery,
-)(DashboardContainer);
diff --git a/client/coral-admin/src/routes/Dashboard/index.js b/client/coral-admin/src/routes/Dashboard/index.js
deleted file mode 100644
index 5bbb476e2..000000000
--- a/client/coral-admin/src/routes/Dashboard/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export {default} from './containers/Dashboard.js';
diff --git a/docs/_docs/03-03-product-guide-moderator-features.md b/docs/_docs/03-03-product-guide-moderator-features.md
index 45516b1a5..064257623 100644
--- a/docs/_docs/03-03-product-guide-moderator-features.md
+++ b/docs/_docs/03-03-product-guide-moderator-features.md
@@ -8,13 +8,6 @@ permalink: /moderator-features/
The Admin is your moderators will moderate your comments, and your Admins will
configure and manage the different parts of Talk.
-### Dashboard
-
-The Dashboard provides real-time information to moderators so they know at a
-glance where they should direct their attention. It shows what articles are
-receiving the most reports and where the most active conversations are
-happening.
-
### Moderate
This is the tab where Moderators will spend the majority of their time. They can
diff --git a/graph/loaders/assets.js b/graph/loaders/assets.js
index b2e315ec5..832b7b473 100644
--- a/graph/loaders/assets.js
+++ b/graph/loaders/assets.js
@@ -79,11 +79,6 @@ const findOrCreateAssetByURL = async (context, asset_url) => {
return asset;
};
-const getAssetsForMetrics = async ({loaders: {Comments}}) => {
- return Comments.getByQuery({action_type: 'FLAG'})
- .then((connection) => connection.nodes);
-};
-
const findByUrl = async (context, asset_url) => {
// Verify that the asset_url is parsable.
@@ -111,7 +106,6 @@ module.exports = (context) => ({
findByUrl: (url) => findByUrl(context, url),
getByQuery: (query) => getAssetsByQuery(context, query),
getByID: new DataLoader((ids) => genAssetsByID(context, ids)),
- getForMetrics: () => getAssetsForMetrics(context),
getAll: new util.SingletonResolver(() => AssetModel.find({}))
}
});
diff --git a/graph/loaders/index.js b/graph/loaders/index.js
index 8bc170c6d..0ef09940f 100644
--- a/graph/loaders/index.js
+++ b/graph/loaders/index.js
@@ -4,7 +4,6 @@ const debug = require('debug')('talk:graph:loaders');
const Actions = require('./actions');
const Assets = require('./assets');
const Comments = require('./comments');
-const Metrics = require('./metrics');
const Settings = require('./settings');
const Tags = require('./tags');
const Users = require('./users');
@@ -17,7 +16,6 @@ let loaders = [
Actions,
Assets,
Comments,
- Metrics,
Settings,
Tags,
Users,
diff --git a/graph/loaders/metrics.js b/graph/loaders/metrics.js
deleted file mode 100644
index 28efe2963..000000000
--- a/graph/loaders/metrics.js
+++ /dev/null
@@ -1,257 +0,0 @@
-const _ = require('lodash');
-const DataLoader = require('dataloader');
-const {objectCacheKeyFn} = require('./util');
-
-const ActionModel = require('../../models/action');
-const CommentModel = require('../../models/comment');
-
-/**
- * Returns the assets which have had comments made within the last time period.
- */
-const getAssetActivityMetrics = ({loaders: {Assets}}, {from, to, limit}) => {
- let assetMetrics = [];
-
- return CommentModel.aggregate([
- {$match: {
- parent_id: null,
- created_at: {
- $gt: from,
- $lt: to
- }
- }},
- {$group: {
- _id: '$asset_id',
- commentCount: {
- $sum: 1
- }
- }},
- {$project: {
- _id: false,
- asset_id: '$_id',
- commentCount: '$commentCount'
- }},
- {$sort: {
- commentCount: -1
- }},
- {$limit: limit}
- ])
- .then((results) => {
- assetMetrics = results;
-
- return Assets.getByID.loadMany(results.map((result) => result.asset_id));
- })
- .then((assets) => assets.map((asset, i) => {
-
- // We're leveraging the fact that the comments returned by the aggregation
- // query are in the request order that we just made, it's what the
- // Assets.getByID loader does.
- asset.commentCount = assetMetrics[i].commentCount;
-
- return asset;
- }));
-};
-
-/**
- * Returns a list of assets with action metadata included on the models.
- */
-const getAssetMetrics = async ({loaders: {Metrics, Assets, Comments}}, {from, to, sortBy, limit}) => {
-
- // Get the recent actions.
- let actionSummaries = await Metrics.getRecentActions.load({from, to});
-
- let commentMetrics = actionSummaries.reduce((acc, {item_id, action_type, count}) => {
- if (action_type !== sortBy) {
- return acc;
- }
-
- if (!(item_id in acc)) {
- acc[item_id] = [];
- }
-
- acc[item_id].push({action_type, count});
-
- return acc;
- }, {});
-
- // Collect just the comment id's.
- let commentIDs = Object.keys(commentMetrics);
-
- // Find those comments.
- let comments = await Comments.get.loadMany(commentIDs);
-
- let commentResults = _.groupBy(comments, 'asset_id');
-
- let assetMetrics = Object.keys(commentResults)
- .map((asset_id) => {
- let ids = commentResults[asset_id].map((comment) => comment.id);
- let summaries = _.groupBy(_.flatten(ids.map((id) => commentMetrics[id])), 'action_type');
-
- let action_summaries = Object.keys(summaries).map((action_type) => ({
- action_type,
- actionCount: summaries[action_type].reduce((acc, {count}) => acc + count, 0),
- actionableItemCount: summaries[action_type].length
- }));
-
- return {action_summaries, id: asset_id};
- })
-
- .filter((asset) => {
- let contextActionSummary = asset.action_summaries.find((({action_type}) => action_type === sortBy));
- if (contextActionSummary === null || contextActionSummary.actionCount === 0) {
- return false;
- }
-
- return true;
- })
-
- // Sort these metrics by the predefined sort order. This will ensure that
- // if the action summary does not exist on the object, that it is less
- // prefered over the one that does have it.
- .sort((a, b) => {
- let aActionSummary = a.action_summaries.find((({action_type}) => action_type === sortBy));
- let bActionSummary = b.action_summaries.find((({action_type}) => action_type === sortBy));
-
- // Both of them had an actionCount, hence we can determine that we could
- // compare the actual values directly.
- return bActionSummary.actionCount - aActionSummary.actionCount;
- });
-
- // Only keep the top `limit`.
- assetMetrics = assetMetrics.slice(0, limit);
-
- // Determine the assets that we need to return.
- let assets = await Assets.getByID.loadMany(assetMetrics.map((asset) => asset.id));
-
- // Join up the assets that are returned by their id.
- let groupedAssets = _.groupBy(assets, 'id');
-
- // Return from the sorted asset metrics and return their assetes.
- return assetMetrics.map(({id, action_summaries}) => {
- if (id in groupedAssets) {
- let asset = groupedAssets[id][0];
-
- // Add the action summaries to the asset.
- asset.action_summaries = action_summaries;
-
- return asset;
- }
-
- return null;
- }).filter((asset) => asset != null);
-};
-
-/**
- * Returns a list of comments that are retrieved based on most activity within
- * the indicated time range.
- */
-const getCommentMetrics = async ({loaders: {Metrics, Comments}}, {from, to, sortBy, limit}) => {
-
- let commentActionSummaries = {};
-
- let actionSummaries = await Metrics.getRecentActions.load({from, to});
-
- actionSummaries.sort((a, b) => {
- let aActionSummary = a.action_type === sortBy ? a : null;
- let bActionSummary = b.action_type === sortBy ? b : null;
-
- // If either a or b don't have this action type, then one of them will
- // automatically win.
- if (aActionSummary == null || bActionSummary == null) {
- if (bActionSummary != null) {
- return 1;
- }
-
- if (aActionSummary != null) {
- return -1;
- }
-
- return 0;
- }
-
- // Both of them had an actionCount, hence we can determine that we could
- // compare the actual values directly.
- return bActionSummary.count - aActionSummary.count;
- });
-
- commentActionSummaries = _.groupBy(actionSummaries, 'item_id');
-
- // Grab the comment id's for comment where they have at least one of the
- // actions being sorted by.
- let commentIDs = Object.keys(commentActionSummaries).filter((item_id) => {
- let contextActionSummary = commentActionSummaries[item_id].find(({action_type}) => action_type === sortBy);
- if (contextActionSummary == null) {
- return false;
- }
-
- return true;
- });
-
- // Only keep the top `limit`.
- commentIDs = commentIDs.slice(0, limit);
-
- // If there are no comment's to get, then just continue with an empty
- // array.
- if (commentIDs.length === 0) {
- return [];
- }
-
- // Find those comments, this is the final stage, so let's get all the
- // fields.
- let comments = await Comments.get.loadMany(commentIDs);
-
- return comments.map((comment) => {
-
- // Add in the action summaries genrerated.
- comment.action_summaries = commentActionSummaries[comment.id];
-
- return comment;
- });
-};
-
-const getRecentActions = (context, {from, to}) => {
- return ActionModel.aggregate([
-
- // Find all actions that were created in the time range.
- {$match: {
- item_type: 'COMMENTS',
- created_at: {
- $gt: from,
- $lt: to
- }
- }},
-
- // Count all those items.
- {$group: {
- _id: {
- item_id: '$item_id',
- action_type: '$action_type'
- },
- count: {
- $sum: 1
- }
- }},
-
- // Project the count to a better field.
- {$project: {
- item_id: '$_id.item_id',
- action_type: '$_id.action_type',
- count: '$count'
- }}
- ]);
-};
-
-module.exports = (context) => ({
- Metrics: {
- getRecentActions: new DataLoader(([{from, to}]) => getRecentActions(context, {from, to}).then((as) => [as]), {
- batch: false,
- cacheKeyFn: objectCacheKeyFn('from', 'to')
- }),
- Assets: {
- get: ({from, to, sortBy, limit}) => getAssetMetrics(context, {from, to, sortBy, limit}),
- getActivity: ({from, to, limit}) => getAssetActivityMetrics(context, {from, to, limit}),
- },
- Comments: {
- get: ({from, to, sortBy, limit}) => getCommentMetrics(context, {from, to, sortBy, limit}),
- }
- }
-});
diff --git a/graph/resolvers/root_query.js b/graph/resolvers/root_query.js
index e4701c901..aa8b90f20 100644
--- a/graph/resolvers/root_query.js
+++ b/graph/resolvers/root_query.js
@@ -1,7 +1,6 @@
const {
SEARCH_ASSETS,
SEARCH_OTHERS_COMMENTS,
- SEARCH_COMMENT_METRICS,
SEARCH_OTHER_USERS
} = require('../../perms/constants');
@@ -58,27 +57,6 @@ const RootQuery = {
return Users.getCountByQuery(query);
},
- assetMetrics(_, query, {user, loaders: {Metrics: {Assets}}}) {
- if (user == null || !user.can(SEARCH_ASSETS)) {
- return null;
- }
-
- const {sortBy} = query;
- if (sortBy === 'ACTIVITY') {
- return Assets.getActivity(query);
- }
-
- return Assets.get(query);
- },
-
- commentMetrics(_, query, {user, loaders: {Metrics: {Comments}}}) {
- if (user == null || !user.can(SEARCH_COMMENT_METRICS)) {
- return null;
- }
-
- return Comments.get(query);
- },
-
// This returns the current user, ensure that if we aren't logged in, we
// return null.
me(_, args, {user}) {
diff --git a/graph/resolvers/user.js b/graph/resolvers/user.js
index e5c5746ca..d95ae53f6 100644
--- a/graph/resolvers/user.js
+++ b/graph/resolvers/user.js
@@ -5,7 +5,6 @@ const {
SEARCH_OTHER_USERS,
SEARCH_OTHERS_COMMENTS,
UPDATE_USER_ROLES,
- SEARCH_COMMENT_METRICS,
VIEW_SUSPENSION_INFO,
LIST_OWN_TOKENS
} = require('../../perms/constants');
@@ -79,7 +78,7 @@ const User = {
// Extract the reliability from the user metadata if they have permission.
reliable(user, _, {user: requestingUser}) {
- if (requestingUser && requestingUser.can(SEARCH_COMMENT_METRICS)) {
+ if (requestingUser && requestingUser.can(SEARCH_ACTIONS)) {
return KarmaService.model(user);
}
},
diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql
index 44b906e24..182504abb 100644
--- a/graph/typeDefs.graphql
+++ b/graph/typeDefs.graphql
@@ -818,19 +818,6 @@ enum USER_STATUS {
APPROVED
}
-# Metrics for the assets.
-enum ASSET_METRICS_SORT {
-
- # Represents a FlagAction.
- FLAG
-
- # Represents a don't agree action.
- DONTAGREE
-
- # Represents activity.
- ACTIVITY
-}
-
type RootQuery {
# Site wide settings and defaults.
@@ -865,14 +852,6 @@ type RootQuery {
# a single User by id
user(id: ID!): User
-
- # Asset metrics related to user actions are saturated into the assets
- # returned. Parameters `from` and `to` are related to the action created_at field.
- assetMetrics(from: Date!, to: Date!, sortBy: ASSET_METRICS_SORT!, limit: Int = 10): [Asset!]
-
- # Comment metrics related to user actions are saturated into the comments
- # returned. Parameters `from` and `to` are related to the action created_at field.
- commentMetrics(from: Date!, to: Date!, sortBy: ACTION_TYPE!, limit: Int = 10): [Comment!]
}
################################################################################
diff --git a/locales/da.yml b/locales/da.yml
index 48b5ab362..0a5152cd9 100644
--- a/locales/da.yml
+++ b/locales/da.yml
@@ -98,7 +98,6 @@ da:
copy_and_paste: "Kopier og indsæt kode nedenfor i dit CMS system for at indlejre din kommentarboks i dine artiker."
custom_css_url: "Brugerdefineret CSS URL"
custom_css_url_desc: "URL til et CSS stylesheet der tilsidesætter den alimdelige strøms stilart. Kan være internt eller externt"
- dashboard: "Instrumentbræt"
days: "Dage"
description: "Som administrator kan du tilpasse indstillingerne til kommentarstrømmen for denne historie:"
domain_list_text: "Indtast de domæner du gerne vil tillade til Talk. f.eks. Dit lokale udviklings miljø (fx. localhost:3000 staging.domain.com domain.com)."
@@ -153,16 +152,6 @@ da:
username: "Brugernavn"
write_your_username: "Rediger dit brugernavn"
your_username: "Dit brugernavn vises på hver kommentar du sender."
- dashboard:
- auto_update: "Data opdateres automatisk hvert femte minut, eller når du genindlæser."
- comment_count: "kommentarer"
- flags: "Flags"
- most_flags: "Historier med de fleste flags"
- most_conversations: "Historier med de fleste samtaler"
- next_update: "{0} minutter indtil næste opdatering."
- no_activity: "Der har ikke været nogen kommentarer i løbet af de sidste fem minutter."
- no_flags: "Der har ikke været nogle flag i de sidste 5 minutter! Hurra!"
- no_likes: "Der har ikke været nogen der syntes godt om noget i de sidste 5 minutter. Helt stille"
done: "Færdig"
edit_comment:
body_input_label: "Rediger denne kommentar"
diff --git a/locales/en.yml b/locales/en.yml
index 02bdea30b..f1d566475 100644
--- a/locales/en.yml
+++ b/locales/en.yml
@@ -100,7 +100,6 @@ en:
copy_and_paste: "Copy and paste code below into your CMS to embed your comment box in your articles"
custom_css_url: "Custom CSS URL"
custom_css_url_desc: "URL of a CSS stylesheet that will override default Embed Stream styles. Can be internal or external."
- dashboard: Dashboard
days: Days
description: "As an admin, you can customize the settings for the comment stream for this story:"
domain_list_text: "Enter the domains you would like to permit for Talk e.g. your local staging and production environments (ex. localhost:3000 staging.domain.com domain.com)."
@@ -155,16 +154,6 @@ en:
username: Username
write_your_username: "Edit your username"
your_username: "Your username appears on every comment you post."
- dashboard:
- auto_update: "Data automatically updates every five minutes or when you Reload."
- comment_count: comments
- flags: Flags
- most_flags: "Stories with the most flags"
- most_conversations: "Stories with the most conversations"
- next_update: "{0} minutes until next update."
- no_activity: "There haven't been any comments anywhere in the last five minutes."
- no_flags: "There have been no flags in the last 5 minutes! Hooray!"
- no_likes: "There have been no likes in the last 5 minutes. All quiet."
done: Done
edit_comment:
body_input_label: "Edit this comment"
diff --git a/locales/es.yml b/locales/es.yml
index 0282b6431..9441253c7 100644
--- a/locales/es.yml
+++ b/locales/es.yml
@@ -98,7 +98,6 @@ es:
copy_and_paste: "Copiar y pegar el código de más abajo en tu CMS para colocar la caja de comentarios en tus artículos"
custom_css_url: "URL CSS a medida"
custom_css_url_desc: "URL de una hoja de estilo que va a sobrescribir los estilos por defecto del hilo de comentarios. Puede ser interna o externa."
- dashboard: Panel
days: Días
description: "Como Administrador/a puedes modificar la configuración de los comentarios en este artículo"
domain_list_text: "Agrega dominios permitidos a Talk, por ejemplo tu localhost, staging y ambientes de producción (ej. localhost:3000, staging.domain.com, domain.com)."
@@ -153,16 +152,6 @@ es:
username: "Nombre"
write_your_username: "Edita tu nombre"
your_username: "Tu nombre aparece en cada comentario que publiques."
- dashboard:
- auto_update: "Los datos se actualizan automáticamente cada cinco minutos o cuando refresca el navegador."
- comment_count: "comentarios"
- flags: "Reportes"
- most_flags: "Artículos con la mayor cantidad de reportes"
- most_conversations: "Artículos con las mayores conversaciones"
- next_update: "{0} minutos hasta la próxima actualización."
- no_activity: "¡No han habido comentarios en ningún lado en los últimos 5 minutos."
- no_flags: "¡No ha habido ningún reporte en los últimos 5 minutos! Bravo!"
- no_likes: "¡No ha habido ningún 'me gusta' en los últimos 5 minutos. Todo tranquilo."
done: "Hecho"
edit_comment:
body_input_label: "Editar este comentario"
diff --git a/locales/fr.yml b/locales/fr.yml
index 5c5a45aab..31ad3ca91 100644
--- a/locales/fr.yml
+++ b/locales/fr.yml
@@ -79,7 +79,6 @@ fr:
copy_and_paste: "Copiez et collez le code ci-dessous dans votre CMS pour intégrer votre boîte de commentaires dans vos articles."
custom_css_url: "URL CSS personnalisée"
custom_css_url_desc: "URL d'une feuille de style CSS qui remplacera les styles par défaut d'intégration des commentaires. Peut être interne ou externe."
- dashboard: Tableau de bord
days: Journées
description: "En tant qu'administrateur, vous pouvez personnaliser les paramètres du fil de commentaires pour cet élément."
domain_list_text: "Entrez les domaines que vous souhaitez autoriser pour Talk, par exemple vos environnements locaux de production et de production (ex : localhost: 3000 staging.domain.com domain.com)."
@@ -121,16 +120,6 @@ fr:
weeks: Semaines
wordlist: "Mots interdits"
continue: Continuer
- dashboard:
- auto_update: "Les données sont automatiquement mises à jour toutes les cinq minutes ou lorsque vous rechargez."
- comment_count: commentaires
- flags: signalements
- most_flags: "Articles avec le plus de signalements"
- most_conversations: "Articles avec le plus de conversations"
- next_update: "{0} minutes jusqu'à la prochaine mise à jour."
- no_activity: "Il n'y a eu aucun commentaire nulle part dans les cinq dernières minutes."
- no_flags: "Il n'y a pas eu aucun signalement au cours des 5 dernières minutes! Hooray !"
- no_likes: "Il n'y a pas eu de \"J'aime\" au cours des 5 dernières minutes. Tout est calme."
done: Terminé
edit_comment:
body_input_label: "Modifier ce commentaire"
diff --git a/locales/pt_BR.yml b/locales/pt_BR.yml
index ae8e1310c..bdfe72d5a 100644
--- a/locales/pt_BR.yml
+++ b/locales/pt_BR.yml
@@ -98,7 +98,6 @@ pt_BR:
copy_and_paste: "Copie e cole o código abaixo em seu CMS para incorporar sua caixa de comentários em seus artigos."
custom_css_url: "URL para CSS customizado"
custom_css_url_desc: "URL de uma folha de estilo CSS para substituir os estilos padrão dos comentários embutidos. Pode ser interno ou externo."
- dashboard: Painel de controle
days: Dias
description: "Como administrador, você pode personalizar as configurações da lista de comentários para esta história:"
domain_list_text: "Insira os domínios que você gostaria de permitir para o Talk, como seus ambientes de desenvolvimento, teste ou produção (ej. localhost:3000 staging.domain.com domain.com)."
@@ -153,16 +152,6 @@ pt_BR:
username: Nome de usuário
write_your_username: "Edite seu nome de usuário"
your_username: "Nosso nome de usuário aparece em todos os comentários que você publica."
- dashboard:
- auto_update: "Atualizado autimaticamente a cada minutos ou após recarregamento."
- comment_count: comentários
- flags: Marcações
- most_flags: "Conversas com mais comentários marcados"
- most_conversations: "Conversas com mais comentários"
- next_update: "{0} minutos até a próxima atualização."
- no_activity: "Não houve nenhum comentário nos últimos cinco minutos."
- no_flags: "Não houve nenhum comentário marcado em nenhum lugar nos últimos 5 minutos. Hip Hip Uha!"
- no_likes: "Não houve reações nos últimos 5 minutos."
done: Feito
edit_comment:
body_input_label: "Edite este comentário"
diff --git a/perms/constants.js b/perms/constants.js
index 9cda7442b..7841e9711 100644
--- a/perms/constants.js
+++ b/perms/constants.js
@@ -26,7 +26,6 @@ module.exports = {
SEARCH_ACTIONS: 'SEARCH_ACTIONS',
SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS: 'SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS',
SEARCH_OTHERS_COMMENTS: 'SEARCH_OTHERS_COMMENTS',
- SEARCH_COMMENT_METRICS: 'SEARCH_COMMENT_METRICS',
LIST_OWN_TOKENS: 'LIST_OWN_TOKENS',
SEARCH_COMMENT_STATUS_HISTORY: 'SEARCH_COMMENT_STATUS_HISTORY',
VIEW_SUSPENSION_INFO: 'VIEW_SUSPENSION_INFO',
diff --git a/perms/queryReducer.js b/perms/queryReducer.js
index b71c3d964..2cc5b95eb 100644
--- a/perms/queryReducer.js
+++ b/perms/queryReducer.js
@@ -13,8 +13,6 @@ module.exports = (user, perm) => {
return check(user, ['ADMIN', 'MODERATOR']);
case types.SEARCH_OTHERS_COMMENTS:
return check(user, ['ADMIN', 'MODERATOR']);
- case types.SEARCH_COMMENT_METRICS:
- return check(user, ['ADMIN', 'MODERATOR']);
case types.LIST_OWN_TOKENS:
return check(user, ['ADMIN']);
case types.SEARCH_COMMENT_STATUS_HISTORY:
diff --git a/test/server/graph/loaders/metrics.js b/test/server/graph/loaders/metrics.js
deleted file mode 100644
index 4414428bd..000000000
--- a/test/server/graph/loaders/metrics.js
+++ /dev/null
@@ -1,133 +0,0 @@
-const {graphql} = require('graphql');
-
-const schema = require('../../../../graph/schema');
-const Context = require('../../../../graph/context');
-const UserModel = require('../../../../models/user');
-const AssetModel = require('../../../../models/asset');
-const SettingsService = require('../../../../services/settings');
-const ActionModel = require('../../../../models/action');
-const CommentModel = require('../../../../models/comment');
-
-const {expect} = require('chai');
-
-describe('graph.loaders.Metrics', () => {
- beforeEach(() => SettingsService.init());
-
- describe('#Comments', () => {
- const query = `
- query CommentMetrics($from: Date!, $to: Date!) {
- flagged: commentMetrics(from: $from, to: $to, sortBy: FLAG) {
- id
- }
- }
- `;
-
- describe('different comment states', () => {
-
- beforeEach(() => CommentModel.create([
- {id: '1', body: 'a new comment!'},
- {id: '2', body: 'a new comment!'},
- {id: '3', body: 'a new comment!'}
- ]));
-
- [
- {flagged: 0, actions: []},
- {flagged: 1, actions: [{action_type: 'FLAG', item_id: '1', item_type: 'COMMENTS'}]},
- {flagged: 1, actions: [
- {action_type: 'FLAG', item_id: '1', item_type: 'COMMENTS'},
- ]},
- {flagged: 1, actions: [
- {action_type: 'FLAG', item_id: '3', item_type: 'COMMENTS'}
- ]}
- ].forEach(({flagged, actions}) => {
-
- describe(`with actions=${actions.length}`, () => {
-
- beforeEach(() => ActionModel.create(actions));
-
- it(`returns the correct amount of metrics flagged=${flagged}`, () => {
- const context = new Context({user: new UserModel({roles: ['ADMIN']})});
-
- return graphql(schema, query, {}, context, {
- from: (new Date()).setMinutes((new Date()).getMinutes() - 5),
- to: (new Date()).setMinutes((new Date()).getMinutes() + 5)
- })
- .then(({data, errors}) => {
- expect(errors).to.be.undefined;
- expect(data.flagged).to.have.length(flagged);
- });
- });
-
- });
-
- });
-
- });
- });
-
- describe('#Assets', () => {
- const query = `
- fragment metrics on Asset {
- id
- action_summaries {
- actionCount
- actionableItemCount
- }
- }
-
- query Metrics($from: Date!, $to: Date!) {
- assetsByFlag: assetMetrics(from: $from, to: $to, sortBy: FLAG) {
- ...metrics
- }
- }
- `;
-
- describe('different comment states', () => {
-
- beforeEach(() => Promise.all([
- AssetModel.create([
- {id: 'a1', url: 'http://localhost:3030/article/1'},
- {id: 'a2', url: 'http://localhost:3030/article/2'}
- ]),
- CommentModel.create([
- {id: 'c1', asset_id: 'a1', body: 'a new comment!'},
- {id: 'c2', asset_id: 'a1', body: 'a new comment!'},
- {id: 'c3', asset_id: 'a1', body: 'a new comment!'}
- ])
- ]));
-
- [
- {flagged: 0, actions: []},
- {flagged: 1, actions: [{action_type: 'FLAG', item_id: 'c1', item_type: 'COMMENTS'}]},
- {flagged: 1, actions: [
- {action_type: 'FLAG', item_id: 'c1', item_type: 'COMMENTS'},
- ]},
- {flagged: 1, actions: [
- {action_type: 'FLAG', item_id: 'c3', item_type: 'COMMENTS'}
- ]}
- ].forEach(({flagged, actions}) => {
-
- describe(`with actions=${actions.length}`, () => {
-
- beforeEach(() => ActionModel.create(actions));
-
- it(`returns the correct amount of metrics flagged=${flagged}`, () => {
- const context = new Context({user: new UserModel({roles: ['ADMIN']})});
-
- return graphql(schema, query, {}, context, {
- from: (new Date()).setMinutes((new Date()).getMinutes() - 5),
- to: (new Date()).setMinutes((new Date()).getMinutes() + 5)
- })
- .then(({data, errors}) => {
- expect(errors).to.be.undefined;
- expect(data.assetsByFlag).to.have.length(flagged);
- });
- });
-
- });
-
- });
-
- });
- });
-});