diff --git a/client/coral-admin/src/containers/Dashboard/Dashboard.js b/client/coral-admin/src/containers/Dashboard/Dashboard.js
new file mode 100644
index 000000000..c287d5f2c
--- /dev/null
+++ b/client/coral-admin/src/containers/Dashboard/Dashboard.js
@@ -0,0 +1,15 @@
+import React from 'react';
+import {compose} from 'react-apollo';
+import {mostFlags} from 'coral-admin/src/graphql/queries';
+
+class Dashboard extends React.Component {
+ render () {
+ return (
+
Dashboard
+ );
+ }
+}
+
+export default compose(
+ mostFlags
+)(Dashboard);
diff --git a/client/coral-admin/src/graphql/queries/index.js b/client/coral-admin/src/graphql/queries/index.js
new file mode 100644
index 000000000..30550e48a
--- /dev/null
+++ b/client/coral-admin/src/graphql/queries/index.js
@@ -0,0 +1,4 @@
+import {graphql} from 'react-apollo';
+import MOST_FLAGS from './mostFlags.graphql';
+
+export const mostFlags = graphql(MOST_FLAGS, {});
diff --git a/client/coral-admin/src/graphql/queries/mostFlags.graphql b/client/coral-admin/src/graphql/queries/mostFlags.graphql
new file mode 100644
index 000000000..b84e21d08
--- /dev/null
+++ b/client/coral-admin/src/graphql/queries/mostFlags.graphql
@@ -0,0 +1,3 @@
+query mostFlags {
+ metric
+}
diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json
index 0eb1ab98d..68658d4a0 100644
--- a/client/coral-admin/src/translations.json
+++ b/client/coral-admin/src/translations.json
@@ -48,6 +48,7 @@
"copy": "Copy to Clipboard"
},
"configure": {
+ "dashboard": "Dashboard",
"enable-pre-moderation": "Enable pre-moderation",
"enable-pre-moderation-text": "Moderators must approve any comment before it is published.",
"require-email-verification": "Require Email Verification",
@@ -157,6 +158,7 @@
"username_flags": ""
},
"configure": {
+ "dashboard": "Panel",
"enable-pre-moderation": "Habilitar pre-moderación",
"enable-pre-moderation-text": "Los moderadores deben aprobar cada comentario antes de que sea publicado.",
"require-email-verification": "Necesita confirmación de correo",
diff --git a/graph/loaders/assets.js b/graph/loaders/assets.js
index a07a18a3f..ba8fb3751 100644
--- a/graph/loaders/assets.js
+++ b/graph/loaders/assets.js
@@ -46,6 +46,15 @@ const findOrCreateAssetByURL = (context, asset_url) => {
});
};
+const getAssetsForMetrics = ({loaders: {Actions, Comments}}) => {
+ return Actions.getByTypes({action_type: 'FLAG', item_type: 'COMMENT'})
+ .then((actions) => { // ALL ACTIONS :O
+ const ids = actions.map(({item_id}) => item_id);
+
+ return Comments.getByQuery({ids});
+ });
+};
+
/**
* Creates a set of loaders based on a GraphQL context.
* @param {Object} context the context of the GraphQL request
@@ -59,6 +68,7 @@ module.exports = (context) => ({
getByURL: (url) => findOrCreateAssetByURL(context, url),
getByID: new DataLoader((ids) => genAssetsByID(context, ids)),
+ getForMetrics: () => getAssetsForMetrics(context),
getAll: new util.SingletonResolver(() => AssetModel.find({}))
}
});
diff --git a/graph/resolvers/root_query.js b/graph/resolvers/root_query.js
index 8aa14d5c6..72d57ed14 100644
--- a/graph/resolvers/root_query.js
+++ b/graph/resolvers/root_query.js
@@ -34,7 +34,7 @@ const RootQuery = {
// Map the actions from the items referenced byt this query. The actions
// returned by this query are explicitly going to be distinct by their
// `item_id`'s.
- let ids = actions.map((action) => action.item_id);
+ let ids = actions.map(({item_id}) => item_id);
// Perform the query using the available resolver.
return Comments.getByQuery({ids, statuses, asset_id, parent_id, limit, cursor, sort});
@@ -44,6 +44,14 @@ const RootQuery = {
return Comments.getByQuery(query);
},
+ metric(_, args, {user, loaders: {Assets}}) {
+ if (user == null || !user.hasRoles('ADMIN')) {
+ return null;
+ }
+
+ return Assets.getForMetrics();
+ },
+
// This returns the current user, ensure that if we aren't logged in, we
// return null.
me(_, args, {user}) {
diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql
index e231bc0c3..7cbb6bd47 100644
--- a/graph/typeDefs.graphql
+++ b/graph/typeDefs.graphql
@@ -167,6 +167,36 @@ interface ActionSummary {
current_user: Action
}
+# A summary of actions for a specific action type on an Asset.
+interface AssetActionSummary {
+
+ # Number of actions associated with actionable types on this this Asset.
+ actionCount: Int
+
+ # Number of unique actionable types that are referenced by the actions.
+ actionableItemCount: Int
+}
+
+# A summary of counts related to all the Likes on an Asset.
+type LikeAssetActionSummary implements AssetActionSummary {
+
+ # Number of actions associated with actionable types on this this Asset.
+ actionCount: Int
+
+ # Number of unique actionable types that are referenced by the actions.
+ actionableItemCount: Int
+}
+
+# A summary of counts related to all the Flags on an Asset.
+type FlagAssetActionSummary implements AssetActionSummary {
+
+ # Number of actions associated with actionable types on this this Asset.
+ actionCount: Int
+
+ # Number of unique actionable types that are referenced by the actions.
+ actionableItemCount: Int
+}
+
# LikeAction is used by users who "like" a specific entity.
type LikeAction implements Action {
@@ -294,6 +324,13 @@ type Asset {
# The date that the asset was created.
created_at: Date
+
+ # Summary of all Actions against all entities associated with the Asset.
+ # (likes, flags, etc.)
+ action_summaries: [AssetActionSummary]
+
+ # Unique users that have commented on this Asset.
+ authorCount: Int
}
################################################################################
@@ -358,6 +395,9 @@ type RootQuery {
# The currently logged in user based on the request.
me: User
+
+ # metrics
+ metric: [Asset]
}
################################################################################