mirror of
https://github.com/wassname/talk.git
synced 2026-09-09 11:38:08 +08:00
Merge branch 'master' into sory-139595043-qbox
This commit is contained in:
@@ -9,6 +9,7 @@ import CommunityContainer from 'containers/Community/CommunityContainer';
|
||||
|
||||
import ModerationLayout from 'containers/ModerationQueue/ModerationLayout';
|
||||
import ModerationContainer from 'containers/ModerationQueue/ModerationContainer';
|
||||
import Dashboard from 'containers/Dashboard/Dashboard';
|
||||
|
||||
const routes = (
|
||||
<div>
|
||||
@@ -18,6 +19,7 @@ const routes = (
|
||||
<Route path='community' component={CommunityContainer} />
|
||||
<Route path='configure' component={Configure} />
|
||||
<Route path='streams' component={Streams} />
|
||||
<Route path='dashboard' component={Dashboard} />
|
||||
|
||||
{/* Moderation Routes */}
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
.heading {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.widgetTable {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.widgetTable thead th {
|
||||
border-bottom: 1px solid #f47e6b;
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.widgetTable tbody tr {
|
||||
border-bottom: 1px solid lightgrey;
|
||||
}
|
||||
|
||||
.widgetTable tbody tr:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.widgetTable tbody td {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.lede {
|
||||
font-size: 0.9em;
|
||||
color: grey;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import React, {PropTypes} from 'react';
|
||||
import {Link} from 'react-router';
|
||||
import styles from './FlagWidget.css';
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from 'coral-admin/src/translations';
|
||||
|
||||
const lang = new I18n(translations);
|
||||
|
||||
const FlagWidget = ({assets}) => {
|
||||
|
||||
return (
|
||||
<table className={styles.widgetTable}>
|
||||
<thead className={styles.widgetHead}>
|
||||
<tr>
|
||||
<th></th>{/* empty on purpose */}
|
||||
<th>{lang.t('streams.article')}</th>
|
||||
<th>{lang.t('modqueue.flagged')}</th>
|
||||
<th>{lang.t('modqueue.likes')}</th>
|
||||
<th>{lang.t('dashboard.comment_count')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
assets.length
|
||||
? assets.map((asset, index) => {
|
||||
const flagCount = asset.action_summaries.find(s => s.__typename === 'FlagAssetActionSummary').actionCount;
|
||||
const likeCount = asset.action_summaries.find(s => s.__typename === 'LikeAssetActionSummary').actionCount;
|
||||
return (
|
||||
<tr key={asset.id}>
|
||||
<td>{index + 1}.</td>
|
||||
<td>
|
||||
<Link to={`/admin/moderate/flagged/${asset.id}`}>{asset.title}</Link>
|
||||
<p className={styles.lede}>{asset.author} - Published: {new Date(asset.created_at).toLocaleDateString()}</p>
|
||||
</td>
|
||||
<td>{likeCount}</td>
|
||||
<td>{flagCount}</td>
|
||||
<td>{asset.commentCount}</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
: <tr><td colSpan="3">{lang.t('dashboard.no_flags')}</td></tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
FlagWidget.propTypes = {
|
||||
assets: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
url: PropTypes.string,
|
||||
commentCount: PropTypes.number,
|
||||
action_summaries: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
__typename: PropTypes.string.isRequired,
|
||||
actionCount: PropTypes.number.isRequired,
|
||||
actionableItemCount: PropTypes.number.isRequired
|
||||
})
|
||||
)
|
||||
})
|
||||
).isRequired
|
||||
};
|
||||
|
||||
export default FlagWidget;
|
||||
@@ -13,21 +13,33 @@ export default ({handleLogout, restricted = false}) => (
|
||||
!restricted ?
|
||||
<div>
|
||||
<Navigation className={styles.nav}>
|
||||
<IndexLink className={styles.navLink} to="/admin/moderate"
|
||||
activeClassName={styles.active}>
|
||||
{lang.t('configure.moderate')}
|
||||
</IndexLink>
|
||||
<Link className={styles.navLink} to="/admin/streams"
|
||||
activeClassName={styles.active}>
|
||||
<IndexLink
|
||||
className={styles.navLink}
|
||||
to="/admin/moderate"
|
||||
activeClassName={styles.active}>
|
||||
{lang.t('configure.moderate')}
|
||||
</IndexLink>
|
||||
<Link className={styles.navLink}
|
||||
to="/admin/streams"
|
||||
activeClassName={styles.active}>
|
||||
{lang.t('configure.streams')}
|
||||
</Link>
|
||||
<Link className={styles.navLink} to="/admin/community"
|
||||
activeClassName={styles.active}>
|
||||
{lang.t('configure.community')}
|
||||
<Link className={styles.navLink}
|
||||
to="/admin/community"
|
||||
activeClassName={styles.active}>
|
||||
{lang.t('configure.community')}
|
||||
</Link>
|
||||
<Link className={styles.navLink} to="/admin/configure"
|
||||
activeClassName={styles.active}>
|
||||
{lang.t('configure.configure')}
|
||||
<Link
|
||||
className={styles.navLink}
|
||||
to="/admin/configure"
|
||||
activeClassName={styles.active}>
|
||||
{lang.t('configure.configure')}
|
||||
</Link>
|
||||
<Link
|
||||
className={styles.navLink}
|
||||
to="/admin/dashboard"
|
||||
activeClassName={styles.active}>
|
||||
{lang.t('configure.dashboard')}
|
||||
</Link>
|
||||
</Navigation>
|
||||
<div className={styles.rightPanel}>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
.Dashboard {
|
||||
display: flex;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.widget {
|
||||
margin-top: 10px;
|
||||
flex: 1;
|
||||
box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.2);
|
||||
margin-right: 10px;
|
||||
padding: 15px;
|
||||
|
||||
}
|
||||
|
||||
.widget:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.heading {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import {compose} from 'react-apollo';
|
||||
import {mostFlags} from 'coral-admin/src/graphql/queries';
|
||||
import {Spinner} from 'coral-ui';
|
||||
import styles from './Dashboard.css';
|
||||
import FlagWidget from '../../components/FlagWidget';
|
||||
|
||||
class Dashboard extends React.Component {
|
||||
render () {
|
||||
|
||||
const {data} = this.props;
|
||||
const {metrics: assets} = data;
|
||||
|
||||
if (data.loading) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
if (data.error) {
|
||||
return <code><pre>{data.error}</pre></code>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.Dashboard}>
|
||||
<div className={styles.widget}>
|
||||
<h2 className={styles.heading}>Top Ten Articles with the most flagged comments</h2>
|
||||
<FlagWidget assets={assets} />
|
||||
</div>
|
||||
<div className={styles.widget}>
|
||||
<h2 className={styles.heading}>Top ten comments with the most likes</h2>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
mostFlags
|
||||
)(Dashboard);
|
||||
@@ -1,6 +1,24 @@
|
||||
import {graphql} from 'react-apollo';
|
||||
|
||||
import MOST_FLAGS from './mostFlags.graphql';
|
||||
import MOD_QUEUE_QUERY from './modQueueQuery.graphql';
|
||||
|
||||
export const mostFlags = graphql(MOST_FLAGS, {
|
||||
options: () => {
|
||||
|
||||
// currently hard-coded per Greg's advice
|
||||
const fiveMinutesAgo = new Date();
|
||||
fiveMinutesAgo.setMinutes(fiveMinutesAgo.getMinutes() - 305);
|
||||
return {
|
||||
variables: {
|
||||
sort: 'FLAG',
|
||||
from: fiveMinutesAgo.toISOString(),
|
||||
to: new Date().toISOString()
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
export const modQueueQuery = graphql(MOD_QUEUE_QUERY, {
|
||||
options: ({params: {id = ''}}) => {
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
query Metrics ($from: Date!, $to: Date!, $sort: ACTION_TYPE!) {
|
||||
metrics(from: $from, to: $to, sort: $sort) {
|
||||
id
|
||||
title
|
||||
url
|
||||
commentCount
|
||||
author
|
||||
created_at
|
||||
action_summaries {
|
||||
actionCount
|
||||
actionableItemCount
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
"loading": "Loading results"
|
||||
},
|
||||
"modqueue": {
|
||||
"likes": "likes",
|
||||
"all": "all",
|
||||
"premod": "pre-mod",
|
||||
"rejected": "rejected",
|
||||
@@ -50,6 +51,7 @@
|
||||
"configure": {
|
||||
"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",
|
||||
"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",
|
||||
@@ -106,6 +108,10 @@
|
||||
"email": "Another member of the community recently flagged your {0} for review. Because of its content your {0} was rejected. This means you can no longer comment, like, or flag content until you rewrite your {0}. Please e-mail moderator@newsorg.com if you have any questions or concerns.",
|
||||
"write_message": "Write a message"
|
||||
},
|
||||
"dashboard": {
|
||||
"no_flags": "There have been no flags in the last 5 minutes! Hooray!",
|
||||
"comment_count": "Comments"
|
||||
},
|
||||
"streams": {
|
||||
"search": "Search",
|
||||
"filter-streams": "Filter Streams",
|
||||
@@ -140,6 +146,7 @@
|
||||
"loading": "Cargando resultados"
|
||||
},
|
||||
"modqueue": {
|
||||
"likes": "gustos",
|
||||
"premod": "pre-mod",
|
||||
"rejected": "rechazado",
|
||||
"flagged": "marcado",
|
||||
@@ -161,6 +168,7 @@
|
||||
"configure": {
|
||||
"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 de Embed Stream. Puede ser interna o externa.",
|
||||
"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",
|
||||
@@ -207,6 +215,10 @@
|
||||
"cancel": "Cancelar",
|
||||
"yes_ban_user": "Si, Suspendan el usuario"
|
||||
},
|
||||
"dashbord": {
|
||||
"no_flags": "¡Nadie ha marcado nada en los últimos 5 minutos! ¡Bravo!",
|
||||
"comment_count": "Comentarios"
|
||||
},
|
||||
"streams": {
|
||||
"search": "",
|
||||
"filter-streams": "",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default function fetcher(query) {
|
||||
return fetch(`${window.location.host}/api/v1/graph/ql`, {
|
||||
return fetch(`${window.location.origin}/api/v1/graph/ql`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
|
||||
Reference in New Issue
Block a user