From 64276cd157ef7e2ccbc4307833592b2af6529c12 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Thu, 30 Nov 2017 23:20:01 +0000 Subject: [PATCH 1/4] Remove Dashboard --- locales/da.yml | 11 ----------- locales/en.yml | 11 ----------- locales/es.yml | 11 ----------- locales/fr.yml | 11 ----------- locales/pt_BR.yml | 11 ----------- 5 files changed, 55 deletions(-) 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" From 9c0e6ccb3683cc6110a1fd429d8b75762f9de6be Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Thu, 30 Nov 2017 23:21:01 +0000 Subject: [PATCH 2/4] Remove Dashboard --- client/coral-admin/src/AppRouter.js | 2 - client/coral-admin/src/components/Drawer.js | 10 +- .../coral-admin/src/components/ui/Header.js | 11 +- client/coral-admin/src/reducers/dashboard.js | 15 --- client/coral-admin/src/reducers/index.js | 2 - .../Dashboard/components/ActivityWidget.js | 47 -------- .../Dashboard/components/CountdownTimer.js | 93 --------------- .../routes/Dashboard/components/Dashboard.css | 40 ------- .../routes/Dashboard/components/Dashboard.js | 15 --- .../routes/Dashboard/components/FlagWidget.js | 54 --------- .../routes/Dashboard/components/LikeWidget.js | 49 -------- .../components/MostLikedCommentsWidget.js | 38 ------ .../routes/Dashboard/components/Widget.css | 110 ------------------ .../routes/Dashboard/containers/Dashboard.js | 65 ----------- .../coral-admin/src/routes/Dashboard/index.js | 1 - 15 files changed, 4 insertions(+), 548 deletions(-) delete mode 100644 client/coral-admin/src/reducers/dashboard.js delete mode 100644 client/coral-admin/src/routes/Dashboard/components/ActivityWidget.js delete mode 100644 client/coral-admin/src/routes/Dashboard/components/CountdownTimer.js delete mode 100644 client/coral-admin/src/routes/Dashboard/components/Dashboard.css delete mode 100644 client/coral-admin/src/routes/Dashboard/components/Dashboard.js delete mode 100644 client/coral-admin/src/routes/Dashboard/components/FlagWidget.js delete mode 100644 client/coral-admin/src/routes/Dashboard/components/LikeWidget.js delete mode 100644 client/coral-admin/src/routes/Dashboard/components/MostLikedCommentsWidget.js delete mode 100644 client/coral-admin/src/routes/Dashboard/components/Widget.css delete mode 100644 client/coral-admin/src/routes/Dashboard/containers/Dashboard.js delete mode 100644 client/coral-admin/src/routes/Dashboard/index.js 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'; From ef200a85cbec4f4a8a04619409bbd1aae727f281 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 30 Nov 2017 17:16:32 -0700 Subject: [PATCH 3/4] removed backend for metrics --- graph/loaders/assets.js | 6 - graph/loaders/index.js | 2 - graph/loaders/metrics.js | 257 --------------------------- graph/resolvers/root_query.js | 22 --- graph/resolvers/user.js | 3 +- graph/typeDefs.graphql | 21 --- perms/constants.js | 1 - perms/queryReducer.js | 2 - test/server/graph/loaders/metrics.js | 133 -------------- 9 files changed, 1 insertion(+), 446 deletions(-) delete mode 100644 graph/loaders/metrics.js delete mode 100644 test/server/graph/loaders/metrics.js 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/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); - }); - }); - - }); - - }); - - }); - }); -}); From dff89ec6cd327799e2552be41df4ce2035fe269e Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Fri, 1 Dec 2017 12:53:17 +0000 Subject: [PATCH 4/4] Remove Dashboard docs --- docs/_docs/03-03-product-guide-moderator-features.md | 7 ------- 1 file changed, 7 deletions(-) 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