From 6444e8c2ab3a374434941c406ad7dfea8ebbe2be Mon Sep 17 00:00:00 2001 From: okbel Date: Thu, 17 May 2018 13:48:30 -0300 Subject: [PATCH 01/20] Karma --- .../src/components/KarmaTooltip.css | 76 +++++++++++++++++++ .../src/components/KarmaTooltip.js | 52 +++++++++++++ .../coral-admin/src/components/UserDetail.css | 17 ++--- .../coral-admin/src/components/UserDetail.js | 39 +++++++--- client/coral-framework/utils/user.js | 15 ++++ locales/en.yml | 4 + 6 files changed, 185 insertions(+), 18 deletions(-) create mode 100644 client/coral-admin/src/components/KarmaTooltip.css create mode 100644 client/coral-admin/src/components/KarmaTooltip.js diff --git a/client/coral-admin/src/components/KarmaTooltip.css b/client/coral-admin/src/components/KarmaTooltip.css new file mode 100644 index 000000000..1f7e1b732 --- /dev/null +++ b/client/coral-admin/src/components/KarmaTooltip.css @@ -0,0 +1,76 @@ +.karmaTooltip { + position: relative; + display: inline-block; + margin: 0 4px; +} + +.icon { + font-size: 16px; + color: #0D5B8F; + -ms-user-select:none; + -moz-user-select: none; + -webkit-user-select: none; + -webkit-touch-callout:none; + user-select: none; + -webkit-tap-highlight-color:rgba(0,0,0,0); +} + +.icon:hover { + cursor: pointer; +} + +.menu { + background-color: white; + border: solid 1px #999; + border-radius: 3px; + padding: 10px; + position: absolute; + -webkit-box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2); + box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2); + z-index: 10; + top: 32px; + left: -100px; + width: 150px; + text-align: left; + color: #616161; +} + +.menu::before{ + content: ''; + border: 10px solid transparent; + border-top-color: #999; + position: absolute; + left: 96px; + top: -20px; + transform: rotate(180deg); +} + +.menu::after{ + content: ''; + border: 10px solid transparent; + border-top-color: white; + position: absolute; + left: 96px; + top: -19px; + transform: rotate(180deg); +} + +.descriptionList { + padding: 0; + margin: 0; + list-style: none; +} + +.strongItem { + margin-right: 3px; +} + +.descriptionItem { + font-size: 0.9em; +} + +.link { + color: #2B7EB5; + text-decoration: underline; + display: block; +} \ No newline at end of file diff --git a/client/coral-admin/src/components/KarmaTooltip.js b/client/coral-admin/src/components/KarmaTooltip.js new file mode 100644 index 000000000..370856f1f --- /dev/null +++ b/client/coral-admin/src/components/KarmaTooltip.js @@ -0,0 +1,52 @@ +import React from 'react'; +import cn from 'classnames'; +import { Icon } from 'coral-ui'; +import styles from './KarmaTooltip.css'; +import ClickOutside from 'coral-framework/components/ClickOutside'; +import t from 'coral-framework/services/i18n'; + +const initialState = { menuVisible: false }; + +class KarmaTooltip extends React.Component { + state = initialState; + + toogleMenu = () => { + this.setState({ menuVisible: !this.state.menuVisible }); + }; + + hideMenu = () => { + this.setState({ menuVisible: false }); + }; + + render() { + const { menuVisible } = this.state; + + return ( + +
+ + + + + {menuVisible && ( +
+ {t('user_detail.user_karma_score')} + + {t('user_detail.learn_more')} + +
+ )} +
+
+ ); + } +} + +export default KarmaTooltip; diff --git a/client/coral-admin/src/components/UserDetail.css b/client/coral-admin/src/components/UserDetail.css index 58faee5c4..0f034d419 100644 --- a/client/coral-admin/src/components/UserDetail.css +++ b/client/coral-admin/src/components/UserDetail.css @@ -39,17 +39,16 @@ margin-right: 0px; } -.statItem, -.statReportResult { +.statItem, .statReportResult, .statKarmaResult { padding: 3px 5px; background-color: #D8D8D8; border-radius: 3px; font-weight: 500; - display: block; font-size: 0.9em; line-height: normal; letter-spacing: 0.4px; - min-width: 60px; + min-width: 35px; + display: inline-block; } .statResult { @@ -58,21 +57,21 @@ display: inline-block; } -.statReportResult { +.statReportResult, .statKarmaResult { color: white; margin: 5px 0; font-weight: 400; } -.statReportResult.reliable { - background-color: #749C48; +.statReportResult.reliable, .statKarmaResult.good { + background-color: #03AB61; } -.statReportResult.neutral { +.statReportResult.neutral, .statKarmaResult.neutral { background-color: #616161; } -.statReportResult.unreliable { +.statReportResult.unreliable, .statKarmaResult.bad { background-color: #F44336; } diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index 5dabc5b6f..723a71eef 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -10,6 +10,7 @@ import { getReliability, isSuspended, isBanned, + getKarma, } from 'coral-framework/utils/user'; import ButtonCopyToClipboard from './ButtonCopyToClipboard'; import ClickOutside from 'coral-framework/components/ClickOutside'; @@ -25,6 +26,7 @@ import { import ActionsMenu from 'coral-admin/src/components/ActionsMenu'; import ActionsMenuItem from 'coral-admin/src/components/ActionsMenuItem'; import UserInfoTooltip from './UserInfoTooltip'; +import KarmaTooltip from './KarmaTooltip'; import t from 'coral-framework/services/i18n'; class UserDetail extends React.Component { @@ -203,23 +205,29 @@ class UserDetail extends React.Component { diff --git a/client/coral-framework/utils/user.js b/client/coral-framework/utils/user.js index 610557542..693652994 100644 --- a/client/coral-framework/utils/user.js +++ b/client/coral-framework/utils/user.js @@ -49,3 +49,18 @@ export const canUsernameBeUpdated = status => { moment(created_at).isAfter(oldestEditTime) ); }; + +/** + * getKarma + * retrieves karma value as string + */ + +export const getKarma = score => { + if (score === 0) { + return 'neutral'; + } else if (score) { + return 'good'; + } else { + return 'bad'; + } +}; diff --git a/locales/en.yml b/locales/en.yml index f60219472..ab7be8e9d 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -470,6 +470,10 @@ en: all: "All" rejected: "Rejected" user_history: "User History" + karma: "Karma" + learn_more: "Learn More" + user_karma_score: "User Karma Score" + karma_docs_link: "https://docs.coralproject.net/talk/trust/#user-karma-score" user_history: user_banned: "User banned" ban_removed: "Ban removed" From 887f7e17f49304a0e6445c07abaa55a53c1ed7c8 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 17 May 2018 11:38:00 -0600 Subject: [PATCH 02/20] server side --- client/coral-admin/src/components/UserDetail.js | 7 +++++-- client/coral-admin/src/containers/UserDetail.js | 2 ++ client/coral-framework/utils/user.js | 6 +++--- graph/typeDefs.graphql | 8 ++++++++ services/karma.js | 9 +++++++++ 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index 723a71eef..070f22ba4 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -245,9 +245,12 @@ class UserDetail extends React.Component { - {100} + {user.reliable.commenterScore} diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js index 360819dd6..810a336c8 100644 --- a/client/coral-admin/src/containers/UserDetail.js +++ b/client/coral-admin/src/containers/UserDetail.js @@ -185,6 +185,8 @@ export const withUserDetailQuery = withQuery( } reliable { flagger + commenter + commenterScore } state { status { diff --git a/client/coral-framework/utils/user.js b/client/coral-framework/utils/user.js index 693652994..e591fa558 100644 --- a/client/coral-framework/utils/user.js +++ b/client/coral-framework/utils/user.js @@ -55,10 +55,10 @@ export const canUsernameBeUpdated = status => { * retrieves karma value as string */ -export const getKarma = score => { - if (score === 0) { +export const getKarma = reliability => { + if (reliability === null) { return 'neutral'; - } else if (score) { + } else if (reliability) { return 'good'; } else { return 'bad'; diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 07947af6e..1c323e6f4 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -20,9 +20,17 @@ type Reliability { # `null` if the reliability cannot be determined. flagger: Boolean + # flaggerScore will contains the number of agreed flags vs disagred flag + # count. + flaggerScore: Int! + # Commenter will be `true` when the commenter is reliable, `false` if not, or # `null` if the reliability cannot be determined. commenter: Boolean + + # commenterScore the number of approved comments (not untouched) subtracted by + # the number of rejected comments. + commenterScore: Int! } ################################################################################ diff --git a/services/karma.js b/services/karma.js index 58d9da3a0..c39c93a32 100644 --- a/services/karma.js +++ b/services/karma.js @@ -1,6 +1,7 @@ const debug = require('debug')('talk:services:karma'); const UserModel = require('../models/user'); const { TRUST_THRESHOLDS } = require('../config'); +const { get } = require('lodash'); /** * This will create an object with the property name of the action type as the @@ -83,9 +84,17 @@ class KarmaModel { return KarmaService.isReliable('flag', this.model); } + get flaggerScore() { + return get(this.model, 'flag.karma', 0); + } + get commenter() { return KarmaService.isReliable('comment', this.model); } + + get commenterScore() { + return get(this.model, 'comment.karma', 0); + } } /** From 0e8065cdf97452e583e79896eb7bf26462506c90 Mon Sep 17 00:00:00 2001 From: okbel Date: Fri, 18 May 2018 13:31:50 -0300 Subject: [PATCH 03/20] Styling --- .../src/components/KarmaTooltip.css | 6 ++- .../coral-admin/src/components/UserDetail.css | 10 ++++- .../coral-admin/src/components/UserDetail.js | 44 ++++++++----------- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/client/coral-admin/src/components/KarmaTooltip.css b/client/coral-admin/src/components/KarmaTooltip.css index 1f7e1b732..dc766c5f0 100644 --- a/client/coral-admin/src/components/KarmaTooltip.css +++ b/client/coral-admin/src/components/KarmaTooltip.css @@ -1,7 +1,7 @@ .karmaTooltip { position: relative; display: inline-block; - margin: 0 4px; + margin: 2px 4px 0; } .icon { @@ -13,6 +13,10 @@ -webkit-touch-callout:none; user-select: none; -webkit-tap-highlight-color:rgba(0,0,0,0); + + > i { + vertical-align: baseline; + } } .icon:hover { diff --git a/client/coral-admin/src/components/UserDetail.css b/client/coral-admin/src/components/UserDetail.css index 0f034d419..ffd076cdb 100644 --- a/client/coral-admin/src/components/UserDetail.css +++ b/client/coral-admin/src/components/UserDetail.css @@ -35,6 +35,10 @@ margin-right: 20px; } +.karmaStat { + display: flex; +} + .stat:last-child { margin-right: 0px; } @@ -47,20 +51,22 @@ font-size: 0.9em; line-height: normal; letter-spacing: 0.4px; - min-width: 35px; - display: inline-block; + min-width: 25px; + display: block; } .statResult { font-size: 1.5em; padding: 5px 0; display: inline-block; + text-align: center; } .statReportResult, .statKarmaResult { color: white; margin: 5px 0; font-weight: 400; + text-align: center; } .statReportResult.reliable, .statKarmaResult.good { diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index 070f22ba4..5573d2166 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -205,29 +205,23 @@ class UserDetail extends React.Component {
  • -
    - - {t('user_detail.total_comments')} - -
    + + {t('user_detail.total_comments')} + {totalComments}
  • -
    - - {t('user_detail.reject_rate')} - -
    + + {t('user_detail.reject_rate')} + {rejectedPercent.toFixed(1)}%
  • -
    - - {t('user_detail.reports')} - -
    + + {t('user_detail.reports')} +
  • -
  • +
  • {t('user_detail.karma')} - + + {user.reliable.commenterScore} +
    - - {user.reliable.commenterScore} - +
From b816053191e93c83effe3135e1d6d65e51fc1a87 Mon Sep 17 00:00:00 2001 From: okbel Date: Fri, 18 May 2018 14:07:10 -0300 Subject: [PATCH 04/20] Updating the cache --- client/coral-admin/src/graphql/index.js | 38 +++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/client/coral-admin/src/graphql/index.js b/client/coral-admin/src/graphql/index.js index 3874e6c94..2b5b5d8c0 100644 --- a/client/coral-admin/src/graphql/index.js +++ b/client/coral-admin/src/graphql/index.js @@ -24,6 +24,16 @@ const userRoleFragment = gql` } `; +const toBoolean = value => { + if (value === 0) { + return null; + } else if (value > 0) { + return true; + } else { + return false; + } +}; + export default { mutations: { SetUserRole: ({ variables: { id, role } }) => ({ @@ -156,7 +166,9 @@ export default { } const updated = update(prev, { users: { - nodes: { $apply: nodes => nodes.filter(node => node.id !== id) }, + nodes: { + $apply: nodes => nodes.filter(node => node.id !== id), + }, }, }); return updated; @@ -185,7 +197,9 @@ export default { const updated = update(prev, { ...decrement, flaggedUsers: { - nodes: { $apply: nodes => nodes.filter(node => node.id !== id) }, + nodes: { + $apply: nodes => nodes.filter(node => node.id !== id), + }, }, }); return updated; @@ -295,12 +309,32 @@ export default { updateQueries: { CoralAdmin_UserDetail: prev => { const increment = { + user: { + reliable: { + commenter: { + $set: toBoolean(prev.user.reliable.commenterScore - 1), + }, + commenterScore: { + $apply: count => count - 1, + }, + }, + }, rejectedComments: { $apply: count => (count < prev.totalComments ? count + 1 : count), }, }; const decrement = { + user: { + reliable: { + commenter: { + $set: toBoolean(prev.user.reliable.commenterScore + 1), + }, + commenterScore: { + $apply: count => count + 1, + }, + }, + }, rejectedComments: { $apply: count => (count > 0 ? count - 1 : 0), }, From 6667bbe01f271fb3c32dc7f5ff5b939374e47e21 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Tue, 22 May 2018 13:18:28 -0400 Subject: [PATCH 05/20] Docs typo --- docs/source/03-07-product-guide-trust.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/03-07-product-guide-trust.md b/docs/source/03-07-product-guide-trust.md index 72248e7d1..aa0a89259 100644 --- a/docs/source/03-07-product-guide-trust.md +++ b/docs/source/03-07-product-guide-trust.md @@ -25,7 +25,7 @@ If their next comment is also rejected, their user karma score is now `-2`, and We strongly recommend not telling your community how this system works, or where the threshholds lie. Firstly, they might try to game the system to meet approval, and secondly, it makes it harder for you to change the threshhold in the future. We suggest using language such as "We hold back comments for approval for a variety of reasons, including content, account history, and more." -If you see that a high proportion of first-time commenters on your site are abusive, you might want to change the threshhold to `0`, at least temporarily. You can configure your own Trust thresholds by using [TRUST_THRESHOLD](/talk/advanced-configuration/#trust-thresholds) in your site configuration. +If you see that a high proportion of first-time commenters on your site are abusive, you might want to change the threshhold to `0`, at least temporarily. You can configure your own Trust thresholds by using [TRUST_THRESHOLDS](/talk/advanced-configuration/#trust-thresholds) in your site configuration. ## Reliable and Unreliable Flaggers @@ -49,7 +49,7 @@ Here are the default thresholds: 0 to +1: Neutral +2 and higher: Reliable ``` -You can configure your own Trust thresholds by using [TRUST_THRESHOLD](/talk/advanced-configuration/#trust-thresholds) in your +You can configure your own Trust thresholds by using [TRUST_THRESHOLDS](/talk/advanced-configuration/#trust-thresholds) in your configuration. Note: Report Karma doesn't include reports of "I don't agree with this comment". From 8e24d4f5c6898be79c628b4031f387e0ba07abbc Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Tue, 22 May 2018 20:12:17 -0400 Subject: [PATCH 06/20] Make it so low threshold is included in karma calc --- services/karma.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/karma.js b/services/karma.js index c39c93a32..72533d328 100644 --- a/services/karma.js +++ b/services/karma.js @@ -121,7 +121,7 @@ class KarmaService { if (trust && trust[name]) { if (trust[name].karma > THRESHOLDS[name].RELIABLE) { return true; - } else if (trust[name].karma < THRESHOLDS[name].UNRELIABLE) { + } else if (trust[name].karma <= THRESHOLDS[name].UNRELIABLE) { return false; } } else if (THRESHOLDS[name].RELIABLE < 0) { From 17f76e85b0091e913d2b00789e91a0a53b33a01e Mon Sep 17 00:00:00 2001 From: okbel Date: Wed, 23 May 2018 13:07:34 -0300 Subject: [PATCH 07/20] changes --- client/coral-admin/src/components/KarmaTooltip.css | 5 ----- 1 file changed, 5 deletions(-) diff --git a/client/coral-admin/src/components/KarmaTooltip.css b/client/coral-admin/src/components/KarmaTooltip.css index dc766c5f0..0742f5f71 100644 --- a/client/coral-admin/src/components/KarmaTooltip.css +++ b/client/coral-admin/src/components/KarmaTooltip.css @@ -7,10 +7,6 @@ .icon { font-size: 16px; color: #0D5B8F; - -ms-user-select:none; - -moz-user-select: none; - -webkit-user-select: none; - -webkit-touch-callout:none; user-select: none; -webkit-tap-highlight-color:rgba(0,0,0,0); @@ -29,7 +25,6 @@ border-radius: 3px; padding: 10px; position: absolute; - -webkit-box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2); box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2); z-index: 10; top: 32px; From 975448ec69fb8756dbc2f55f383242d1de5a9340 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 23 May 2018 15:10:44 -0600 Subject: [PATCH 08/20] patches to reliability computation based on settings --- .../coral-admin/src/containers/UserDetail.js | 8 +++ client/coral-admin/src/graphql/index.js | 29 +++++++--- graph/resolvers/index.js | 2 + graph/resolvers/karma_threshold.js | 6 +++ graph/resolvers/settings.js | 10 +++- graph/typeDefs.graphql | 26 +++++++++ services/karma.js | 15 +++--- test/server/services/karma.js | 53 +++++++++++++++++++ 8 files changed, 131 insertions(+), 18 deletions(-) create mode 100644 graph/resolvers/karma_threshold.js create mode 100644 test/server/services/karma.js diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js index 4a6a93e84..666e6c8f2 100644 --- a/client/coral-admin/src/containers/UserDetail.js +++ b/client/coral-admin/src/containers/UserDetail.js @@ -228,6 +228,14 @@ export const withUserDetailQuery = withQuery( } ${getSlotFragmentSpreads(slots, 'user')} } + settings { + karma { + comment { + reliable + unreliable + } + } + } me { id } diff --git a/client/coral-admin/src/graphql/index.js b/client/coral-admin/src/graphql/index.js index 2b5b5d8c0..bb4b35b74 100644 --- a/client/coral-admin/src/graphql/index.js +++ b/client/coral-admin/src/graphql/index.js @@ -24,14 +24,23 @@ const userRoleFragment = gql` } `; -const toBoolean = value => { - if (value === 0) { - return null; - } else if (value > 0) { +/** + * calculateReliability will determine the reliability of a karma score based on + * the settings for the karma type. + * + * @param {Number} karma - the current karma value/score for the given user + * @param {Object} thresholds - the karma thresholds to base the karma computation on + */ +const calculateReliability = (karma, { reliable, unreliable }) => { + if (karma >= reliable) { return true; - } else { + } + + if (karma <= unreliable) { return false; } + + return null; }; export default { @@ -312,7 +321,10 @@ export default { user: { reliable: { commenter: { - $set: toBoolean(prev.user.reliable.commenterScore - 1), + $set: calculateReliability( + prev.user.reliable.commenterScore - 1, + prev.settings.karma.comment + ), }, commenterScore: { $apply: count => count - 1, @@ -328,7 +340,10 @@ export default { user: { reliable: { commenter: { - $set: toBoolean(prev.user.reliable.commenterScore + 1), + $set: calculateReliability( + prev.user.reliable.commenterScore + 1, + prev.settings.karma.comment + ), }, commenterScore: { $apply: count => count + 1, diff --git a/graph/resolvers/index.js b/graph/resolvers/index.js index a8765f743..4bc9d9624 100644 --- a/graph/resolvers/index.js +++ b/graph/resolvers/index.js @@ -15,6 +15,7 @@ const DontAgreeActionSummary = require('./dont_agree_action_summary'); const FlagAction = require('./flag_action'); const FlagActionSummary = require('./flag_action_summary'); const GenericUserError = require('./generic_user_error'); +const KarmaThreshold = require('./karma_threshold'); const LocalUserProfile = require('./local_user_profile'); const RootMutation = require('./root_mutation'); const RootQuery = require('./root_query'); @@ -48,6 +49,7 @@ let resolvers = { FlagAction, FlagActionSummary, GenericUserError, + KarmaThreshold, LocalUserProfile, RootMutation, RootQuery, diff --git a/graph/resolvers/karma_threshold.js b/graph/resolvers/karma_threshold.js new file mode 100644 index 000000000..693b1042c --- /dev/null +++ b/graph/resolvers/karma_threshold.js @@ -0,0 +1,6 @@ +const { property } = require('lodash'); + +module.exports = { + reliable: property('RELIABLE'), + unreliable: property('UNRELIABLE'), +}; diff --git a/graph/resolvers/settings.js b/graph/resolvers/settings.js index 6ad1aa455..11e21068b 100644 --- a/graph/resolvers/settings.js +++ b/graph/resolvers/settings.js @@ -1,8 +1,13 @@ const { VIEW_PROTECTED_SETTINGS } = require('../../perms/constants'); - const { decorateWithPermissionCheck } = require('./util'); -const Settings = {}; +const Settings = { + karma: ( + settings, + args, + { connectors: { services: { Karma: { THRESHOLDS } } } } + ) => THRESHOLDS, +}; // PROTECTED_SETTINGS are the settings keys that must be protected for only some // eyes. @@ -11,6 +16,7 @@ const PROTECTED_SETTINGS = { autoCloseStream: [VIEW_PROTECTED_SETTINGS], wordlist: [VIEW_PROTECTED_SETTINGS], domains: [VIEW_PROTECTED_SETTINGS], + karma: [VIEW_PROTECTED_SETTINGS], }; // decorate the fields on the settings resolver with a permission check. diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 1c323e6f4..1f3016a18 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -801,6 +801,29 @@ type Domains { whitelist: [String!]! } +# KarmaThreshold defines the bounds for which a User will become unreliable or +# reliable based on their karma score. If the score is equal or less than the +# unreliable value, they are unreliable. If the score is equal or more than the +# reliable value, they are reliable. If they are neither reliable or unreliable +# then they are neutral. +type KarmaThreshold { + reliable: Int! + unreliable: Int! +} + +# KarmaThresholds contains the currently set thresholds for triggering Trust +# beheviour. +type KarmaThresholds { + + # flag represents karma settings in relation to how well a User's flagging + # ability aligns with the moderation decicions made by moderators. + flag: KarmaThreshold! + + # comment represents the karma setting in relation to how well a User's + # comments are moderated. + comment: KarmaThreshold! +} + # Settings stores the global settings for a given installation. type Settings { @@ -873,6 +896,9 @@ type Settings { # domains will return a given list of domains. domains: Domains + + # karma contains the currently set thresholds for triggering Trust beheviour. + karma: KarmaThresholds } ################################################################################ diff --git a/services/karma.js b/services/karma.js index 72533d328..e255e3b14 100644 --- a/services/karma.js +++ b/services/karma.js @@ -115,18 +115,14 @@ class KarmaService { /** * Inspects the reliability of a property and returns it if known. * @param {String} name - name of the property - * @param {Object} trust - object possibly containing the propertys + * @param {Object} trust - object possibly containing the properties */ static isReliable(name, trust) { - if (trust && trust[name]) { - if (trust[name].karma > THRESHOLDS[name].RELIABLE) { - return true; - } else if (trust[name].karma <= THRESHOLDS[name].UNRELIABLE) { - return false; - } - } else if (THRESHOLDS[name].RELIABLE < 0) { + const karma = get(trust, [name, 'karma'], 0); + + if (karma >= THRESHOLDS[name].RELIABLE) { return true; - } else if (THRESHOLDS[name].UNRELIABLE > 0) { + } else if (karma <= THRESHOLDS[name].UNRELIABLE) { return false; } @@ -171,3 +167,4 @@ class KarmaService { } module.exports = KarmaService; +module.exports.THRESHOLDS = THRESHOLDS; diff --git a/test/server/services/karma.js b/test/server/services/karma.js new file mode 100644 index 000000000..d725d8308 --- /dev/null +++ b/test/server/services/karma.js @@ -0,0 +1,53 @@ +const chai = require('chai'); +const { expect } = chai; +const { merge } = require('lodash'); +const Karma = require('../../../services/karma'); + +const thresholdsBackup = {}; +const thresholdsOverride = { + comment: { + RELIABLE: 1, + UNRELIABLE: -1, + }, + flag: { + RELIABLE: 1, + UNRELIABLE: -1, + }, +}; + +describe('services.Karma', () => { + before(() => { + // Backup the existing thresholds. + merge(thresholdsBackup, Karma.THRESHOLDS); + + // Configure the thresholds to a known value. + merge(Karma.THRESHOLDS, thresholdsOverride); + + expect(Karma.THRESHOLDS).to.deep.equal(thresholdsOverride); + }); + + after(() => { + // Restore the thresholds. + merge(Karma.THRESHOLDS, thresholdsBackup); + + expect(Karma.THRESHOLDS).to.deep.equal(thresholdsBackup); + }); + + describe('#isReliable', () => { + it('neutral', () => { + expect(Karma.isReliable('comment', {})).to.be.null; + expect(Karma.isReliable('comment', { comment: {} })).to.be.null; + expect(Karma.isReliable('comment', { comment: { karma: 0 } })).to.be.null; + }); + it('unreliable', () => { + expect(Karma.isReliable('comment', { comment: { karma: -1 } })).to.be + .false; + expect(Karma.isReliable('comment', { comment: { karma: -2 } })).to.be + .false; + }); + it('reliable', () => { + expect(Karma.isReliable('comment', { comment: { karma: 1 } })).to.be.true; + expect(Karma.isReliable('comment', { comment: { karma: 2 } })).to.be.true; + }); + }); +}); From f36b6e2cb64c792469712dea13a08c5428f5b6fa Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 23 May 2018 15:14:41 -0600 Subject: [PATCH 09/20] field renamed --- client/coral-admin/src/components/UserDetail.js | 2 +- client/coral-admin/src/containers/UserDetail.js | 2 +- client/coral-admin/src/graphql/index.js | 8 ++++---- graph/typeDefs.graphql | 8 ++++---- services/karma.js | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index d227b2dd6..f81274934 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -255,7 +255,7 @@ class UserDetail extends React.Component { styles[getKarma(user.reliable.commenter)] )} > - {user.reliable.commenterScore} + {user.reliable.commenterKarma} diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js index 666e6c8f2..1664e7cae 100644 --- a/client/coral-admin/src/containers/UserDetail.js +++ b/client/coral-admin/src/containers/UserDetail.js @@ -187,7 +187,7 @@ export const withUserDetailQuery = withQuery( reliable { flagger commenter - commenterScore + commenterKarma } state { status { diff --git a/client/coral-admin/src/graphql/index.js b/client/coral-admin/src/graphql/index.js index bb4b35b74..d1bbf501b 100644 --- a/client/coral-admin/src/graphql/index.js +++ b/client/coral-admin/src/graphql/index.js @@ -322,11 +322,11 @@ export default { reliable: { commenter: { $set: calculateReliability( - prev.user.reliable.commenterScore - 1, + prev.user.reliable.commenterKarma - 1, prev.settings.karma.comment ), }, - commenterScore: { + commenterKarma: { $apply: count => count - 1, }, }, @@ -341,11 +341,11 @@ export default { reliable: { commenter: { $set: calculateReliability( - prev.user.reliable.commenterScore + 1, + prev.user.reliable.commenterKarma + 1, prev.settings.karma.comment ), }, - commenterScore: { + commenterKarma: { $apply: count => count + 1, }, }, diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 1f3016a18..5bc102736 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -20,17 +20,17 @@ type Reliability { # `null` if the reliability cannot be determined. flagger: Boolean - # flaggerScore will contains the number of agreed flags vs disagred flag + # flaggerKarma will contains the number of agreed flags vs disagred flag # count. - flaggerScore: Int! + flaggerKarma: Int! # Commenter will be `true` when the commenter is reliable, `false` if not, or # `null` if the reliability cannot be determined. commenter: Boolean - # commenterScore the number of approved comments (not untouched) subtracted by + # commenterKarma the number of approved comments (not untouched) subtracted by # the number of rejected comments. - commenterScore: Int! + commenterKarma: Int! } ################################################################################ diff --git a/services/karma.js b/services/karma.js index e255e3b14..0437ad98a 100644 --- a/services/karma.js +++ b/services/karma.js @@ -84,7 +84,7 @@ class KarmaModel { return KarmaService.isReliable('flag', this.model); } - get flaggerScore() { + get flaggerKarma() { return get(this.model, 'flag.karma', 0); } @@ -92,7 +92,7 @@ class KarmaModel { return KarmaService.isReliable('comment', this.model); } - get commenterScore() { + get commenterKarma() { return get(this.model, 'comment.karma', 0); } } From 97416d421b3355ded1d782d275d807b33f0a1e44 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 23 May 2018 16:17:38 -0600 Subject: [PATCH 10/20] adjusted karma tooltip --- .../src/components/KarmaTooltip.css | 30 ++++++++++++++++++- .../src/components/KarmaTooltip.js | 29 ++++++++++++++++++ .../coral-admin/src/components/UserDetail.js | 24 ++------------- .../coral-admin/src/containers/UserDetail.js | 1 - locales/en.yml | 2 +- 5 files changed, 62 insertions(+), 24 deletions(-) diff --git a/client/coral-admin/src/components/KarmaTooltip.css b/client/coral-admin/src/components/KarmaTooltip.css index dc766c5f0..ff6f8b742 100644 --- a/client/coral-admin/src/components/KarmaTooltip.css +++ b/client/coral-admin/src/components/KarmaTooltip.css @@ -59,6 +59,34 @@ transform: rotate(180deg); } +.menu ul { + list-style: none; + padding: 0; + + li { + display: flex; + justify-content: space-between; + margin: 5px 0; + } +} + +.label { + padding: 4px 5px; + border-radius: 3px; + color: #fff; + font-weight: 400; + text-align: center; + font-size: .9em; + line-height: normal; + letter-spacing: .4px; + min-width: 25px; + display: block; + + /* &.reliable { background-color: #03AB61; } */ + /* &.neutral { background-color: #616161; } */ + &.unreliable { background-color: #F44336; } +} + .descriptionList { padding: 0; margin: 0; @@ -77,4 +105,4 @@ color: #2B7EB5; text-decoration: underline; display: block; -} \ No newline at end of file +} diff --git a/client/coral-admin/src/components/KarmaTooltip.js b/client/coral-admin/src/components/KarmaTooltip.js index 370856f1f..772b119e9 100644 --- a/client/coral-admin/src/components/KarmaTooltip.js +++ b/client/coral-admin/src/components/KarmaTooltip.js @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import cn from 'classnames'; import { Icon } from 'coral-ui'; import styles from './KarmaTooltip.css'; @@ -8,6 +9,13 @@ import t from 'coral-framework/services/i18n'; const initialState = { menuVisible: false }; class KarmaTooltip extends React.Component { + static propTypes = { + settings: PropTypes.shape({ + reliable: PropTypes.number.isRequired, + unreliable: PropTypes.number.isRequired, + }).isRequired, + }; + state = initialState; toogleMenu = () => { @@ -19,6 +27,7 @@ class KarmaTooltip extends React.Component { }; render() { + const { settings: { unreliable } } = this.props; const { menuVisible } = this.state; return ( @@ -34,6 +43,26 @@ class KarmaTooltip extends React.Component { {menuVisible && (
{t('user_detail.user_karma_score')} +
    + {/*
  • + Reliable{' '} + + ≥ {reliable} + +
  • +
  • + Neutral{' '} + + < {reliable}, > {unreliable} + +
  • */} +
  • + {t('user_detail.unreliable')}{' '} + + ≤ {unreliable} + +
  • +
-
  • - - {t('user_detail.reports')} - - - {capitalize(getReliability(user.reliable.flagger))} - -
  • @@ -258,7 +240,7 @@ class UserDetail extends React.Component { {user.reliable.commenterKarma}
    - +
  • diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js index 1664e7cae..f12b26b30 100644 --- a/client/coral-admin/src/containers/UserDetail.js +++ b/client/coral-admin/src/containers/UserDetail.js @@ -185,7 +185,6 @@ export const withUserDetailQuery = withQuery( provider } reliable { - flagger commenter commenterKarma } diff --git a/locales/en.yml b/locales/en.yml index 273a6d423..9b38c833f 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -466,10 +466,10 @@ en: email: "Email" total_comments: "Total Comments" reject_rate: "Reject Rate" - reports: "Reports" all: "All" rejected: "Rejected" user_history: "User History" + unreliable: "Unreliable" karma: "Karma" learn_more: "Learn More" user_karma_score: "User Karma Score" From 9a73c6db59ebde11429bc96a2e62013d115bfb44 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 23 May 2018 16:18:10 -0600 Subject: [PATCH 11/20] "Trust" flag to "Karma" and updated translations --- locales/ar.yml | 149 +--------------------------------------------- locales/da.yml | 3 - locales/de.yml | 1 - locales/en.yml | 2 +- locales/es.yml | 1 - locales/fi_FI.yml | 1 - locales/fr.yml | 1 - locales/nl_NL.yml | 1 - locales/pt_BR.yml | 49 --------------- locales/zh_CN.yml | 68 +-------------------- locales/zh_TW.yml | 70 ---------------------- 11 files changed, 3 insertions(+), 343 deletions(-) diff --git a/locales/ar.yml b/locales/ar.yml index 852b9ac1b..2b320a621 100644 --- a/locales/ar.yml +++ b/locales/ar.yml @@ -292,55 +292,7 @@ ar: suspect_word: "كلمة مشتبهة" banned_word: "كلمة محظورة" body_count: "يتجاوز النص الحد الأقصى للطول المسموح" - trust: "ثقة" links: "رابط" - modqueue: - account: "account flags" - actions: Actions - all: all - all_streams: "All Streams" - notify_edited: '{0} edited comment "{1}"' - notify_accepted: '{0} accepted comment "{1}"' - notify_rejected: '{0} rejected comment "{1}"' - notify_flagged: '{0} flagged comment "{1}"' - notify_reset: '{0} reset status of comment "{1}"' - approve: "Approve" - approved: "Approved" - ban_user: "Ban" - billion: B - close: Close - empty_queue: "No more comments to moderate! You're all caught up. Go have some ☕️" - flagged: flagged - reported: reported - less_detail: "Less detail" - likes: likes - million: M - mod_faster: "Moderate faster with keyboard shortcuts" - moderate: "Moderate →" - more_detail: "More detail" - new: New - newest_first: "Newest First" - navigation: Navigation - next_comment: "Go to the next comment" - toggle_search: "Open search" - next_queue: "Switch queues" - oldest_first: "Oldest First" - premod: pre-mod - prev_comment: "Go to the previous comment" - reject: "Reject" - rejected: "Rejected" - reply: "Reply" - select_stream: "Select Stream" - shift_key: "⇧" - shortcuts: "Shortcuts" - sort: "Sort" - show_shortcuts: "Show Shortcuts" - singleview: "Zen mode" - thismenu: "Open this menu" - jump_to_queue: "Jump to specific queue" - thousand: k - try_these: "Try these" - view_more_shortcuts: "View more shortcuts" my_comment_history: "سجل التعليقات" name: اسم no_agree_comment: "لا أوافق على هذا التعليق" @@ -358,9 +310,6 @@ ar: report_notif: "شكرا على الإبلاغ عن هذا التعليق. تم إبلاغ فريق الإشراف لدينا وسيراجعه قريبًا." report_notif_remove: "لقد تمت إزالة بلاغك." reported: بلغ عنه - comment_history_blank: - title: You have not written any comments - info: A history of your comments will appear here settings: from_settings_page: "من صفحة الملف الشخصي يمكنك مشاهدة سجل التعليقات." my_comment_history: "سجل التعليقات" @@ -378,104 +327,8 @@ ar: step_1_header: "بلغ عن مشكلة" step_2_header: "ساعدنا على الفهم" step_3_header: "شكرا لك على المساهمة الخاصة بك" - streams: - all: All - article: Story - closed: Closed - empty_result: "No assets match this search. Maybe try widening your search?" - filter_streams: "Filter Streams" - newest: Newest - oldest: Oldest - open: Open - pubdate: "Publication Date" - search: Search - sort_by: "Sort By" - status: "Stream Status" - stream_status: "Stream Status" - suspenduser: - title_suspend: "Suspend User" - description_suspend: "You are suspending {0}. This comment will go to the Rejected queue, and {0} will not be allowed to like, report, reply or post until the suspension time is complete." - select_duration: "Select suspension duration" - one_hour: "1 hour" - hours: "{0} hours" - days: "{0} days" - hour: "{0} hours" - day: "{0} days" - cancel: "Cancel" - suspend_user: "Suspend User" - email_message_suspend: "Dear {0},\n\nIn accordance with {1}’s community guidelines, your account has been temporarily suspended. During the suspension, you will be unable to comment, flag or engage with fellow commenters. Please rejoin the conversation {2}." - title_notify: "Notify the user of their temporary suspension" - notify_suspend_until: "User {0} has been temporarily suspended. This suspension will automatically end {1}." - description_notify: "Suspending this user will temporarily disable their account." - write_message: "Write a message" - send: Send - reject_username: - username: username - no_cancel: "No cancel" - description_reject: "Would you like to temporarily ban this user because of their {0}? Doing so will temporarily suspend this user until they rewrite their {0}." - title_notify: "Notify the user of their temporary suspension" - description_notify: "Suspending this user will temporarily disable their account." - title_reject: "We noticed you rejected a username" - suspend_user: "Suspend User" - yes_suspend: "Yes suspend" - email_message_reject: "Another member of the community recently flagged your username for review. Because of its content your user was rejected. This means you can no longer comment, like, or flag content until you rewrite your username. Please e-mail us if you have any questions or concerns." - write_message: "Write a message" - send: Send thank_you: "نحن نقدر سلامتك وردود الفعل. سيراجع المشرف التقرير الخاص بك" - user: - bio_flags: "flags for this bio" - user_bio: "User Bio" - username_flags: "flags for this username" - user_detail: - remove_suspension: "Remove Suspension" - suspend: "Suspend User" - remove_ban: "Remove Ban" - ban: "Ban User" - member_since: "Member Since" - email: "Email" - total_comments: "Total Comments" - reject_rate: "Reject Rate" - reports: "Reports" - all: "All" - rejected: "Rejected" - user_history: "User History" - user_history: - user_banned: "User banned" - ban_removed: "Ban removed" - username_status: "Username {0}" - suspended: "Suspended, {0}" - suspension_removed: "Suspension removed" - system: "System" - date: "Date" - action: "Action" - taken_by: "Taken By" user_impersonating: "هذا المستخدم ينتحل شخصية" user_no_comment: "لم تترك تعليقا مطلقا. إنضم إلى المحادثة!" username_offensive: "اسم المستخدم هذا مسيء" - view_conversation: "عرض المحادثة" - install: - initial: - description: "Let's set up your Talk community in just a few short steps." - submit: "Get Started" - add_organization: - description: "Please tell us the name of your organization. This will appear in emails when inviting new team members." - label: "Organization Name" - save: "Save" - create: - email: "Email address" - username: "Username" - password: "Password" - confirm_password: "Confirm Password" - organization_contact_email: "Organization Contact Email" - save: "Save" - permitted_domains: - title: "Permitted domains" - description: "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)." - submit: "Finish install" - final: - description: "Thanks for installing Talk! We sent an email to verify your email address. While you finish setting up the account, you can start engaging with your readers now." - launch: "Launch Talk" - close: "Close this Installer" - admin_sidebar: - view_options: "View Options" - sort_comments: "Sort Comments" + view_conversation: "عرض المحادثة" \ No newline at end of file diff --git a/locales/da.yml b/locales/da.yml index 4bcb3bb1d..d21a1aee6 100644 --- a/locales/da.yml +++ b/locales/da.yml @@ -211,7 +211,6 @@ da: NO_SPECIAL_CHARACTERS: "Brugernavne kan kun indeholder bogstaver og _" PASSWORD_LENGTH: "Adgangskoden er for kort" PROFANITY_ERROR: "Brugernavne må ikke inholde stødende indhold. Kontakt venligst administratoren, hvis du mener at dette er en fejl." - RATE_LIMIT_EXCEEDED: "Rate limit exceeded" USERNAME_IN_USE: "Brugernavnet er allerede i brug" USERNAME_REQUIRED: "Du skal indtaste et brugernavn" EMAIL_NOT_VERIFIED: "E-mail address not verified" @@ -287,10 +286,8 @@ da: comment_spam: "Spam" comment_noagree: "Uenig" comment_other: "Andre" - suspect_word: "Suspect Word" banned_word: "Forbudt ord" body_count: "Body overstiger max længde" - trust: "Stol" links: "Link" modqueue: account: "konto flag" diff --git a/locales/de.yml b/locales/de.yml index bec73fa5d..2bf137991 100644 --- a/locales/de.yml +++ b/locales/de.yml @@ -322,7 +322,6 @@ de: suspect_word: "Verdächtiges Wort" banned_word: "Unzulässiges Wort" body_count: "Text überschreitet Zeichenlimit" - trust: "Vertrauen" links: "Link" modqueue: account: "Konto-Markierungen" diff --git a/locales/en.yml b/locales/en.yml index 9b38c833f..32399fd9d 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -322,7 +322,7 @@ en: suspect_word: "Suspect Word" banned_word: "Banned Word" body_count: "Body exceeds max length" - trust: "Trust" + trust: "Karma" links: "Link" modqueue: account: "account flags" diff --git a/locales/es.yml b/locales/es.yml index ff2777c41..fc9b93b36 100644 --- a/locales/es.yml +++ b/locales/es.yml @@ -310,7 +310,6 @@ es: suspect_word: "Palabra sospechosa" banned_word: "Palabra prohibida" body_count: "El texto exede el límite permitido" - trust: "Trust" links: "Link" modqueue: account: "reportes de cuentas" diff --git a/locales/fi_FI.yml b/locales/fi_FI.yml index 494a48cdb..4758a2920 100644 --- a/locales/fi_FI.yml +++ b/locales/fi_FI.yml @@ -292,7 +292,6 @@ fi_FI: suspect_word: "Epäilyttävä sana" banned_word: "Kielletty sana" body_count: "Liian pitkä viesti" - trust: "Luotettava" links: "Linkki" modqueue: account: "Liputuksia" diff --git a/locales/fr.yml b/locales/fr.yml index 1e9e0e496..4e32cf66c 100644 --- a/locales/fr.yml +++ b/locales/fr.yml @@ -300,7 +300,6 @@ fr: suspect_word: "Mot suspect" banned_word: "Mot banni" body_count: "Le texte dépasse la longueur maximale" - trust: "Trust" links: "Lien" modqueue: account: "Signalements du compte" diff --git a/locales/nl_NL.yml b/locales/nl_NL.yml index 9d4057065..297917c9e 100644 --- a/locales/nl_NL.yml +++ b/locales/nl_NL.yml @@ -290,7 +290,6 @@ nl_NL: suspect_word: "Verdacht woord" banned_word: "Geblokeerd woord" body_count: "Tekst is te lang" - trust: "Vertrouwen" links: "Link" modqueue: account: "account meldingen" diff --git a/locales/pt_BR.yml b/locales/pt_BR.yml index 4b7b5c3e3..c464480e0 100644 --- a/locales/pt_BR.yml +++ b/locales/pt_BR.yml @@ -61,11 +61,6 @@ pt_BR: reaction: 'Reação' reactions: 'Reações' story: 'Conversas' - flagged_usernames: - notify_approved: '{0} approved username {1}' - notify_rejected: '{0} rejected username {1}' - notify_flagged: '{0} reported username {1}' - notify_changed: 'user {0} changed their username to {1}' community: account_creation_date: "Data de criação da conta" active: Ativo @@ -273,24 +268,6 @@ pt_BR: loading_results: "Carregando resultados" marketing: "Isso parece um anúncio/marketing" moderate_this_stream: "Moderar comentários" - flags: - reasons: - user: - username_offensive: "Offensive" - username_nolike: "Dislike" - username_impersonating: "Impersonation" - username_spam: "Spam" - username_other: "Other" - comment: - comment_offensive: "Offensive" - comment_spam: "Spam" - comment_noagree: "Disagree" - comment_other: "Other" - suspect_word: "Suspect Word" - banned_word: "Banned Word" - body_count: "Body exceeds max length" - trust: "Trust" - links: "Link" modqueue: account: "contas marcadas" actions: Ações @@ -418,29 +395,6 @@ pt_BR: bio_flags: "Marcadas para este perfil" user_bio: "Perfil do usuário" username_flags: "Marcadas para este usuário" - user_detail: - remove_suspension: "Remove Suspension" - suspend: "Suspend User" - remove_ban: "Remove Ban" - ban: "Ban User" - member_since: "Member Since" - email: "Email" - total_comments: "Total Comments" - reject_rate: "Reject Rate" - reports: "Reports" - all: "All" - rejected: "Rejected" - user_history: "User History" - user_history: - user_banned: "User banned" - ban_removed: "Ban removed" - username_status: "Username {0}" - suspended: "Suspended, {0}" - suspension_removed: "Suspension removed" - system: "System" - date: "Date" - action: "Action" - taken_by: "Taken By" user_impersonating: "Este usuário está representando" user_no_comment: "Você nunca deixou um comentário. Participe da conversa!" username_offensive: "Esse nome de usuário é ofensivo" @@ -468,6 +422,3 @@ pt_BR: description: "Obrigado por instalar o Talk! Enviamos um e-mail para verificar seu endereço de e-mail. Enquanto você terminar de configurar a conta, você pode começar a se envolver com seus leitores agora." launch: "Iniciar Talk" close: "Feche este instalador" - admin_sidebar: - view_options: "View Options" - sort_comments: "Sort Comments" diff --git a/locales/zh_CN.yml b/locales/zh_CN.yml index b3307a886..c70f5b779 100644 --- a/locales/zh_CN.yml +++ b/locales/zh_CN.yml @@ -12,22 +12,11 @@ zh_CN: note_reject_comment: "封禁该用户将使这条评论被移入“被拒”队列。" note_ban_user: "封禁该用户将使其无法编辑或删除评论。" yes_ban_user: "是的,封禁该用户" - write_a_message: "Write a message" - send: "Send" - notify_ban_headline: "Notify the user of ban" - notify_ban_description: "This will notify the user by email that they have been banned from the community" - email_message_ban: "Dear {0},\n\nSomeone with access to your account has violated our community guidelines. As a result, your account has been banned. You will no longer be able to comment, like or report comments. if you think this has been done in error, please contact our community team." bio_offensive: "该简介含有冒犯言语" cancel: "取消" confirm_email: click_to_confirm: "Click below to confirm your email address" confirm: "Confirm" - password_reset: - set_new_password: "Change Your Password" - new_password: "New Password" - new_password_help: "Password must be at least 8 characters" - confirm_new_password: "Confirm New Password" - change_password: "Change Password" characters_remaining: "字符剩余可用" comment: anon: "匿名" @@ -61,11 +50,6 @@ zh_CN: reaction: '回应' reactions: '回应' story: '文章' - flagged_usernames: - notify_approved: '{0} approved username {1}' - notify_rejected: '{0} rejected username {1}' - notify_flagged: '{0} reported username {1}' - notify_changed: 'user {0} changed their username to {1}' community: account_creation_date: "账户创建日期" active: "活动中" @@ -203,9 +187,6 @@ zh_CN: embedlink: copy: "复制到粘贴板" error: - COMMENT_PARENT_NOT_VISIBLE: "The comment that you're replying to has been removed or doesn't exist." - EMAIL_VERIFICATION_TOKEN_INVALID: "Email verification token is invalid." - PASSWORD_RESET_TOKEN_INVALID: "Your password reset link is invalid." COMMENT_TOO_SHORT: "评论至少应有一个字符。请修改您的评论,再度尝试。" NOT_AUTHORIZED: "您没有权限进行该操作" NO_SPECIAL_CHARACTERS: "用户名只能包含字母、数字跟下划线" @@ -237,7 +218,6 @@ zh_CN: username: "用户名只能包含字母、数字跟下划线" unexpected: "发生了异常错误。对不起!" required_field: "该字段必填" - temporarily_suspended: "Your account is currently suspended. It will be reactivated {0}. Please contact us if you have any questions." flag_comment: "举报评论" flag_reason: "举报理由(可选)" flag_username: "举报用户名" @@ -256,8 +236,6 @@ zh_CN: error: "用户名只能包含字母、数字跟下划线" label: "新用户名" msg: "由于您的用户名不当,您的帐号目前被暂停使用。如要恢复您的帐户,请输入一个新的用户名。如有任何疑问,请与我们联系。" - changed_name: - msg: "Your username change is under review by our moderation team." my_comments: "我的评论" my_profile: "我的资料" new_count: "查看 {0} 更多 {1}" @@ -275,24 +253,6 @@ zh_CN: loading_results: "加载结果中" marketing: "这看起来像是广告" moderate_this_stream: "审查该流" - flags: - reasons: - user: - username_offensive: "Offensive" - username_nolike: "Dislike" - username_impersonating: "Impersonation" - username_spam: "Spam" - username_other: "Other" - comment: - comment_offensive: "Offensive" - comment_spam: "Spam" - comment_noagree: "Disagree" - comment_other: "Other" - suspect_word: "Suspect Word" - banned_word: "Banned Word" - body_count: "Body exceeds max length" - trust: "Trust" - links: "Link" modqueue: account: "帐户标记" actions: "操作" @@ -420,29 +380,6 @@ zh_CN: bio_flags: "对简介的举报" user_bio: "用户简介" username_flags: "对用户名的举报" - user_detail: - remove_suspension: "Remove Suspension" - suspend: "Suspend User" - remove_ban: "Remove Ban" - ban: "Ban User" - member_since: "Member Since" - email: "Email" - total_comments: "Total Comments" - reject_rate: "Reject Rate" - reports: "Reports" - all: "All" - rejected: "Rejected" - user_history: "User History" - user_history: - user_banned: "User banned" - ban_removed: "Ban removed" - username_status: "Username {0}" - suspended: "Suspended, {0}" - suspension_removed: "Suspension removed" - system: "System" - date: "Date" - action: "Action" - taken_by: "Taken By" user_impersonating: "冒名用户" user_no_comment: "您未曾发表评论。现在就来加入对话吧!" username_offensive: "用户名有冒犯性" @@ -468,7 +405,4 @@ zh_CN: final: description: "感谢您安装 Talk!我们已向您的邮箱发送一封验证邮件。当您进行帐号设置时,您可以开始跟您的读者开始互动。" launch: "启动 Talk" - close: "关闭安装程序" - admin_sidebar: - view_options: "View Options" - sort_comments: "Sort Comments" + close: "关闭安装程序" \ No newline at end of file diff --git a/locales/zh_TW.yml b/locales/zh_TW.yml index c186a667f..2b5db5d44 100644 --- a/locales/zh_TW.yml +++ b/locales/zh_TW.yml @@ -12,22 +12,8 @@ zh_TW: note_reject_comment: "封禁該用戶將使這條評論被移入“被拒”列表。" note_ban_user: "封禁該用戶將使其無法編輯或刪除評論。" yes_ban_user: "是的,封禁該用戶" - write_a_message: "Write a message" - send: "Send" - notify_ban_headline: "Notify the user of ban" - notify_ban_description: "This will notify the user by email that they have been banned from the community" - email_message_ban: "Dear {0},\n\nSomeone with access to your account has violated our community guidelines. As a result, your account has been banned. You will no longer be able to comment, like or report comments. if you think this has been done in error, please contact our community team." bio_offensive: "該介紹包含具有攻擊性的內容。" cancel: "取消" - confirm_email: - click_to_confirm: "Click below to confirm your email address" - confirm: "Confirm" - password_reset: - set_new_password: "Change Your Password" - new_password: "New Password" - new_password_help: "Password must be at least 8 characters" - confirm_new_password: "Confirm New Password" - change_password: "Change Password" characters_remaining: "剩餘字符數" comment: anon: "匿名用戶" @@ -61,11 +47,6 @@ zh_TW: reaction: '回應' reactions: '回應' story: '故事' - flagged_usernames: - notify_approved: '{0} approved username {1}' - notify_rejected: '{0} rejected username {1}' - notify_flagged: '{0} reported username {1}' - notify_changed: 'user {0} changed their username to {1}' community: account_creation_date: "賬戶創建日期" active: 激活 @@ -203,9 +184,6 @@ zh_TW: embedlink: copy: "覆制到剪貼板" error: - COMMENT_PARENT_NOT_VISIBLE: "The comment that you're replying to has been removed or doesn't exist." - EMAIL_VERIFICATION_TOKEN_INVALID: "Email verification token is invalid." - PASSWORD_RESET_TOKEN_INVALID: "Your password reset link is invalid." COMMENT_TOO_SHORT: "評論長度必須超過一個字符,請您修改評論後重試。" NOT_AUTHORIZED: "您無權執行該操作。" NO_SPECIAL_CHARACTERS: "用戶名只能包含字母、數字和下劃線" @@ -237,7 +215,6 @@ zh_TW: username: "用戶名只能包含字母、數字和下劃線。" unexpected: "發生了意外錯誤。抱歉!" required_field: "該字段必填" - temporarily_suspended: "Your account is currently suspended. It will be reactivated {0}. Please contact us if you have any questions." flag_comment: "舉報評論" flag_reason: "舉報原因(可選)" flag_username: "舉報用戶名" @@ -256,8 +233,6 @@ zh_TW: error: "用戶名只能包含字母、數字和下劃線。" label: "新用戶名" msg: "由於您的用戶名不當,您的帳號目前已被暫停使用。如要恢復您的帳戶,請輸入一個新的用戶名。如有任何疑問,請與我們聯繫。" - changed_name: - msg: "Your username change is under review by our moderation team." my_comments: "我的評論" my_profile: "我的概況" new_count: "查看{0}更多{1}" @@ -275,24 +250,6 @@ zh_TW: loading_results: "加載結果" marketing: "這看起來像是廣告/營銷" moderate_this_stream: "審核這個流" - flags: - reasons: - user: - username_offensive: "Offensive" - username_nolike: "Dislike" - username_impersonating: "Impersonation" - username_spam: "Spam" - username_other: "Other" - comment: - comment_offensive: "Offensive" - comment_spam: "Spam" - comment_noagree: "Disagree" - comment_other: "Other" - suspect_word: "Suspect Word" - banned_word: "Banned Word" - body_count: "Body exceeds max length" - trust: "Trust" - links: "Link" modqueue: account: "帳戶標記" actions: 操作 @@ -345,7 +302,6 @@ zh_TW: no_agree_comment: "我不同意這個評論" no_like_bio: "我不喜歡這個個人簡介" no_like_username: "我不喜歡這個用戶名" - already_flagged_username: "You have already flagged this username." other: 其他 permalink: 分享 personal_info: "該評論洩露了個人身份資訊" @@ -420,29 +376,6 @@ zh_TW: bio_flags: "該簡介的標記" user_bio: "用戶簡介" username_flags: "該用戶名的標記" - user_detail: - remove_suspension: "Remove Suspension" - suspend: "Suspend User" - remove_ban: "Remove Ban" - ban: "Ban User" - member_since: "Member Since" - email: "Email" - total_comments: "Total Comments" - reject_rate: "Reject Rate" - reports: "Reports" - all: "All" - rejected: "Rejected" - user_history: "User History" - user_history: - user_banned: "User banned" - ban_removed: "Ban removed" - username_status: "Username {0}" - suspended: "Suspended, {0}" - suspension_removed: "Suspension removed" - system: "System" - date: "Date" - action: "Action" - taken_by: "Taken By" user_impersonating: "此用戶正在冒充" user_no_comment: "您尚未評論過。加入對話吧!" username_offensive: "這個用戶名有冒犯性" @@ -469,6 +402,3 @@ zh_TW: description: "感謝安裝Talk!我們給您發送了一封郵件以驗證您的電子郵箱地址。在完成帳戶設置後,您即可開始與您的讀者互動。" launch: "啟動Talk" close: "關閉安裝程序" - admin_sidebar: - view_options: "View Options" - sort_comments: "Sort Comments" From 0835f3f32163cbdf0ee3766f2e93d38ec9cadbb6 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 23 May 2018 17:16:51 -0600 Subject: [PATCH 12/20] fixed translations --- client/coral-admin/src/components/CommentLabels.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/client/coral-admin/src/components/CommentLabels.js b/client/coral-admin/src/components/CommentLabels.js index 1f71d9771..5c7da08c7 100644 --- a/client/coral-admin/src/components/CommentLabels.js +++ b/client/coral-admin/src/components/CommentLabels.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import Label from 'coral-ui/components/Label'; import Slot from 'coral-framework/components/Slot'; +import { t } from 'coral-framework/services/i18n'; import FlagLabel from 'coral-ui/components/FlagLabel'; import cn from 'classnames'; import styles from './CommentLabels.css'; @@ -63,10 +64,14 @@ const CommentLabels = ({ {getUserFlaggedType(actions)} )} {hasSuspectedWords(actions) && ( - Suspect + + {t('flags.reasons.comment.suspect_word')} + )} {hasHistoryFlag(actions) && ( - History + + {t('flags.reasons.comment.trust')} + )} Date: Thu, 24 May 2018 10:09:44 -0600 Subject: [PATCH 13/20] fixed first time user --- services/moderation/phases/karma.js | 42 ++++++++++++++--------------- test/server/services/karma.js | 20 +++++++------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/services/moderation/phases/karma.js b/services/moderation/phases/karma.js index ad55b37a7..7a929824b 100644 --- a/services/moderation/phases/karma.js +++ b/services/moderation/phases/karma.js @@ -6,28 +6,26 @@ module.exports = ctx => { const { connectors: { services: { Karma } } } = ctx; const trust = get(ctx, 'user.metadata.trust', null); - if (trust !== null) { - // If the user is not a reliable commenter (passed the unreliability - // threshold by having too many rejected comments) then we can change the - // status of the comment to `SYSTEM_WITHHELD`, therefore pushing the user's - // comments away from the public eye until a moderator can manage them. This of - // course can only be applied if the comment's current status is `NONE`, - // we don't want to interfere if the comment was rejected. - if (Karma.isReliable('comment', trust) === false) { - // Add the flag related to Trust to the comment. - return { - status: 'SYSTEM_WITHHELD', - actions: [ - { - action_type: 'FLAG', - user_id: null, - group_id: 'TRUST', - metadata: { - trust, - }, + // If the user is not a reliable commenter (passed the unreliability + // threshold by having too many rejected comments) then we can change the + // status of the comment to `SYSTEM_WITHHELD`, therefore pushing the user's + // comments away from the public eye until a moderator can manage them. This of + // course can only be applied if the comment's current status is `NONE`, + // we don't want to interfere if the comment was rejected. + if (Karma.isReliable('comment', trust) === false) { + // Add the flag related to Trust to the comment. + return { + status: 'SYSTEM_WITHHELD', + actions: [ + { + action_type: 'FLAG', + user_id: null, + group_id: 'TRUST', + metadata: { + trust, }, - ], - }; - } + }, + ], + }; } }; diff --git a/test/server/services/karma.js b/test/server/services/karma.js index d725d8308..4a6753125 100644 --- a/test/server/services/karma.js +++ b/test/server/services/karma.js @@ -6,8 +6,8 @@ const Karma = require('../../../services/karma'); const thresholdsBackup = {}; const thresholdsOverride = { comment: { - RELIABLE: 1, - UNRELIABLE: -1, + RELIABLE: 2, + UNRELIABLE: 0, }, flag: { RELIABLE: 1, @@ -35,19 +35,21 @@ describe('services.Karma', () => { describe('#isReliable', () => { it('neutral', () => { - expect(Karma.isReliable('comment', {})).to.be.null; - expect(Karma.isReliable('comment', { comment: {} })).to.be.null; - expect(Karma.isReliable('comment', { comment: { karma: 0 } })).to.be.null; + expect(Karma.isReliable('comment', { comment: { karma: 1 } })).to.be.null; + expect(Karma.isReliable('comment', { comment: { karma: 0 } })).to.not.be + .null; + expect(Karma.isReliable('comment', { comment: { karma: -1 } })).to.not.be + .null; }); it('unreliable', () => { - expect(Karma.isReliable('comment', { comment: { karma: -1 } })).to.be - .false; - expect(Karma.isReliable('comment', { comment: { karma: -2 } })).to.be + expect(Karma.isReliable('comment', {})).to.be.false; + expect(Karma.isReliable('comment', { comment: {} })).to.be.false; + expect(Karma.isReliable('comment', { comment: { karma: 0 } })).to.be .false; }); it('reliable', () => { - expect(Karma.isReliable('comment', { comment: { karma: 1 } })).to.be.true; expect(Karma.isReliable('comment', { comment: { karma: 2 } })).to.be.true; + expect(Karma.isReliable('comment', { comment: { karma: 3 } })).to.be.true; }); }); }); From 0c35badd951a71f3c4f85bd19b3184c4403def5a Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 24 May 2018 12:58:55 -0600 Subject: [PATCH 14/20] name changes --- client/coral-admin/src/components/KarmaTooltip.js | 4 ++-- client/coral-admin/src/components/UserDetail.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/coral-admin/src/components/KarmaTooltip.js b/client/coral-admin/src/components/KarmaTooltip.js index 772b119e9..0b0866ef1 100644 --- a/client/coral-admin/src/components/KarmaTooltip.js +++ b/client/coral-admin/src/components/KarmaTooltip.js @@ -10,7 +10,7 @@ const initialState = { menuVisible: false }; class KarmaTooltip extends React.Component { static propTypes = { - settings: PropTypes.shape({ + thresholds: PropTypes.shape({ reliable: PropTypes.number.isRequired, unreliable: PropTypes.number.isRequired, }).isRequired, @@ -27,7 +27,7 @@ class KarmaTooltip extends React.Component { }; render() { - const { settings: { unreliable } } = this.props; + const { thresholds: { unreliable } } = this.props; const { menuVisible } = this.state; return ( diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index 1b3fe4333..a221e8b86 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -240,7 +240,7 @@ class UserDetail extends React.Component { {user.reliable.commenterKarma} - + From a046162f9c20d433f43cbee6dcadf39b17ae39d9 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 24 May 2018 13:57:15 -0600 Subject: [PATCH 15/20] `karma` -> `karmaThresholds` --- client/coral-admin/src/components/UserDetail.js | 10 ++++++++-- client/coral-admin/src/containers/UserDetail.js | 2 +- client/coral-admin/src/graphql/index.js | 4 ++-- graph/resolvers/settings.js | 4 ++-- graph/typeDefs.graphql | 5 +++-- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index a221e8b86..fd609373d 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -76,7 +76,13 @@ class UserDetail extends React.Component { renderLoaded() { const { root, - root: { me, user, totalComments, rejectedComments, settings: { karma } }, + root: { + me, + user, + totalComments, + rejectedComments, + settings: { karmaThresholds }, + }, activeTab, selectedCommentIds, toggleSelect, @@ -240,7 +246,7 @@ class UserDetail extends React.Component { {user.reliable.commenterKarma} - + diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js index f12b26b30..e34d4df6e 100644 --- a/client/coral-admin/src/containers/UserDetail.js +++ b/client/coral-admin/src/containers/UserDetail.js @@ -228,7 +228,7 @@ export const withUserDetailQuery = withQuery( ${getSlotFragmentSpreads(slots, 'user')} } settings { - karma { + karmaThresholds { comment { reliable unreliable diff --git a/client/coral-admin/src/graphql/index.js b/client/coral-admin/src/graphql/index.js index d1bbf501b..6a1b06a6e 100644 --- a/client/coral-admin/src/graphql/index.js +++ b/client/coral-admin/src/graphql/index.js @@ -323,7 +323,7 @@ export default { commenter: { $set: calculateReliability( prev.user.reliable.commenterKarma - 1, - prev.settings.karma.comment + prev.settings.karmaThresholds.comment ), }, commenterKarma: { @@ -342,7 +342,7 @@ export default { commenter: { $set: calculateReliability( prev.user.reliable.commenterKarma + 1, - prev.settings.karma.comment + prev.settings.karmaThresholds.comment ), }, commenterKarma: { diff --git a/graph/resolvers/settings.js b/graph/resolvers/settings.js index 11e21068b..a299530f2 100644 --- a/graph/resolvers/settings.js +++ b/graph/resolvers/settings.js @@ -2,7 +2,7 @@ const { VIEW_PROTECTED_SETTINGS } = require('../../perms/constants'); const { decorateWithPermissionCheck } = require('./util'); const Settings = { - karma: ( + karmaThresholds: ( settings, args, { connectors: { services: { Karma: { THRESHOLDS } } } } @@ -16,7 +16,7 @@ const PROTECTED_SETTINGS = { autoCloseStream: [VIEW_PROTECTED_SETTINGS], wordlist: [VIEW_PROTECTED_SETTINGS], domains: [VIEW_PROTECTED_SETTINGS], - karma: [VIEW_PROTECTED_SETTINGS], + karmaThresholds: [VIEW_PROTECTED_SETTINGS], }; // decorate the fields on the settings resolver with a permission check. diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 5bc102736..1ca003de6 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -897,8 +897,9 @@ type Settings { # domains will return a given list of domains. domains: Domains - # karma contains the currently set thresholds for triggering Trust beheviour. - karma: KarmaThresholds + # karmaThresholds contains the currently set thresholds for triggering Trust + # beheviour. + karmaThresholds: KarmaThresholds } ################################################################################ From e39bcb11ba7cd74aa7b70f6897ea1fdf32a0a5bb Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 24 May 2018 14:02:02 -0600 Subject: [PATCH 16/20] added comment --- client/coral-admin/src/components/KarmaTooltip.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/coral-admin/src/components/KarmaTooltip.js b/client/coral-admin/src/components/KarmaTooltip.js index 0b0866ef1..9c4184afd 100644 --- a/client/coral-admin/src/components/KarmaTooltip.js +++ b/client/coral-admin/src/components/KarmaTooltip.js @@ -44,6 +44,7 @@ class KarmaTooltip extends React.Component {
    {t('user_detail.user_karma_score')}
      + {/* NOTE: we may display this data in the future, keeping around for that eventuality */} {/*
    • Reliable{' '} From f8cc658c54e8291d49153b88d58af9b1ef085a1a Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 24 May 2018 14:34:13 -0600 Subject: [PATCH 17/20] csp fixes --- routes/api/v1/csp.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/routes/api/v1/csp.js b/routes/api/v1/csp.js index ad1101987..5d7005b4e 100644 --- a/routes/api/v1/csp.js +++ b/routes/api/v1/csp.js @@ -4,13 +4,18 @@ const { logger } = require('../../../services/logging'); const router = express.Router(); const schema = Joi.object().keys({ - 'csp-report': Joi.object().keys({ - 'document-uri': Joi.string(), - referrer: Joi.string(), - 'blocked-uri': Joi.string(), - 'violated-directive': Joi.string(), - 'original-policy': Joi.string(), - }), + 'csp-report': Joi.object() + .keys({ + 'document-uri': Joi.string(), + referrer: Joi.string().allow(''), + 'blocked-uri': Joi.string(), + 'violated-directive': Joi.string(), + 'original-policy': Joi.string(), + 'script-sample': Joi.string() + .allow('') + .optional(), + }) + .optionalKeys('referrer', 'script-sample'), }); const json = express.json({ type: 'application/csp-report' }); @@ -18,7 +23,7 @@ const json = express.json({ type: 'application/csp-report' }); router.post('/', json, async (req, res, next) => { const { value, error: err } = Joi.validate(req.body, schema, { stripUnknown: true, - presence: 'required', + presence: 'optional', }); if (err) { res.status(400).end(); From 64c16a18050861815f690075504da361cea8658b Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 24 May 2018 15:02:08 -0600 Subject: [PATCH 18/20] support the __webpack_nonce__ parameter --- .../helpers/{publicPath.js => webpackGlobals.js} | 14 ++++++++++++-- middleware/nonce.js | 9 ++++++++- middleware/staticTemplate.js | 2 +- webpack.config.js | 7 +++++-- 4 files changed, 26 insertions(+), 6 deletions(-) rename client/coral-framework/helpers/{publicPath.js => webpackGlobals.js} (57%) diff --git a/client/coral-framework/helpers/publicPath.js b/client/coral-framework/helpers/webpackGlobals.js similarity index 57% rename from client/coral-framework/helpers/publicPath.js rename to client/coral-framework/helpers/webpackGlobals.js index 57b6b7d15..99569529b 100644 --- a/client/coral-framework/helpers/publicPath.js +++ b/client/coral-framework/helpers/webpackGlobals.js @@ -1,9 +1,9 @@ -/* global __webpack_public_path__ */ // eslint-disable-line no-unused-vars +/* global __webpack_public_path__, __webpack_nonce__ */ // eslint-disable-line no-unused-vars import { getStaticConfiguration } from 'coral-framework/services/staticConfiguration'; // Load the static url from the static configuration. -const { STATIC_URL } = getStaticConfiguration(); +const { STATIC_URL, SCRIPT_NONCE } = getStaticConfiguration(); // Update the static url for the imported public path so dynamically imported // chunks will use the correct path as defined by the process.env.STATIC_URL @@ -14,3 +14,13 @@ const { STATIC_URL } = getStaticConfiguration(); // https://webpack.js.org/configuration/output/#output-publicpath // __webpack_public_path__ = STATIC_URL + 'static/'; + +// All dynamically included scripts that support nonce's will add this to their +// script tags. +// +// The __webpack_nonce__ can be referenced: https://webpack.js.org/guides/csp/ +// +// Pending issues: +// - https://github.com/webpack-contrib/style-loader/pull/319 +// +__webpack_nonce__ = SCRIPT_NONCE; diff --git a/middleware/nonce.js b/middleware/nonce.js index 1181fac85..7d072cf37 100644 --- a/middleware/nonce.js +++ b/middleware/nonce.js @@ -1,9 +1,16 @@ +const { get, merge } = require('lodash'); const uuid = require('uuid/v4'); // nonce is designed to create a random value that can be used in conjunction // with the csp middleware. module.exports = (req, res, next) => { - res.locals.nonce = uuid(); + const nonce = uuid(); + + // Attach the nonce to the locals. + res.locals.nonce = nonce; + res.locals.data = merge({}, get(res.locals, 'data', {}), { + SCRIPT_NONCE: nonce, + }); next(); }; diff --git a/middleware/staticTemplate.js b/middleware/staticTemplate.js index d8137693e..8d467fcda 100644 --- a/middleware/staticTemplate.js +++ b/middleware/staticTemplate.js @@ -48,7 +48,7 @@ const attachStaticLocals = locals => { for (const key in TEMPLATE_LOCALS) { const value = TEMPLATE_LOCALS[key]; - locals[key] = value; + merge(locals, { [key]: value }); } }; diff --git a/webpack.config.js b/webpack.config.js index 6914499bf..96c09dd9e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -74,7 +74,7 @@ const config = { target: 'web', output: { path: path.join(__dirname, 'dist'), - publicPath: '', + webpackGlobals: '', filename: '[name].[chunkhash].js', chunkFilename: '[name].[chunkhash].chunk.js', }, @@ -309,7 +309,10 @@ const applyConfig = (entries, root = {}) => entry: entries.reduce( (entry, { name, path: modulePath, disablePolyfill = false }) => { const entries = [ - path.join(__dirname, 'client/coral-framework/helpers/publicPath'), + path.join( + __dirname, + 'client/coral-framework/helpers/webpackGlobals' + ), ]; if (disablePolyfill) { entries.push(modulePath); From 16573b127e785c28cca77e4aeb2c7851a6684261 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 24 May 2018 16:19:52 -0600 Subject: [PATCH 19/20] text-replace error --- webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack.config.js b/webpack.config.js index 96c09dd9e..3d815e53b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -74,7 +74,7 @@ const config = { target: 'web', output: { path: path.join(__dirname, 'dist'), - webpackGlobals: '', + publicPath: '', filename: '[name].[chunkhash].js', chunkFilename: '[name].[chunkhash].chunk.js', }, From 90e4682cf68acbd3d534d1e8edbcd3ac94bc0771 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 25 May 2018 22:49:35 +0200 Subject: [PATCH 20/20] Take karma into account when doing e2e --- .../src/components/ApproveButton.js | 3 +- .../src/components/RejectButton.js | 3 +- .../Moderation/components/ModerationMenu.js | 1 + .../Moderation/components/ModerationQueue.js | 10 +++- test/e2e/globals.js | 3 -- test/e2e/page_objects/admin.js | 23 ++++++++- test/e2e/specs/03_embedStream.js | 10 ++-- test/e2e/specs/05_banUser.js | 49 +++++++++++++++++++ test/e2e/specs/06_suspendUser.js | 19 +++---- 9 files changed, 98 insertions(+), 23 deletions(-) diff --git a/client/coral-admin/src/components/ApproveButton.js b/client/coral-admin/src/components/ApproveButton.js index 884ac3da5..96dbdd375 100644 --- a/client/coral-admin/src/components/ApproveButton.js +++ b/client/coral-admin/src/components/ApproveButton.js @@ -14,7 +14,8 @@ const ApproveButton = ({ active, minimal, onClick, className, disabled }) => { className={cn( styles.root, { [styles.minimal]: minimal, [styles.active]: active }, - className + className, + 'talk-admin-approve-button' )} onClick={onClick} disabled={disabled || active} diff --git a/client/coral-admin/src/components/RejectButton.js b/client/coral-admin/src/components/RejectButton.js index 69e1047e6..2aabff007 100644 --- a/client/coral-admin/src/components/RejectButton.js +++ b/client/coral-admin/src/components/RejectButton.js @@ -14,7 +14,8 @@ const RejectButton = ({ active, minimal, onClick, className, disabled }) => { className={cn( styles.root, { [styles.minimal]: minimal, [styles.active]: active }, - className + className, + 'talk-admin-reject-button' )} onClick={onClick} disabled={disabled || active} diff --git a/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js b/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js index 27394c6c9..5300ce3eb 100644 --- a/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js +++ b/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js @@ -24,6 +24,7 @@ const ModerationMenu = ({ asset = {}, items, getModPath, activeTab }) => { > {items.map(queue => ( nodes.some(node => node.id === id); @@ -380,6 +381,11 @@ class ModerationQueue extends React.Component { ...props } = this.props; + const rootClassName = cn( + styles.root, + `talk-admin-moderate-queue-${this.props.activeTab}` + ); + if (comments.length === 0) { return (
      @@ -401,7 +407,7 @@ class ModerationQueue extends React.Component { const comment = comments[index]; return ( -
      +
      +
      this.viewNewComments()} count={comments.length - view.length} diff --git a/test/e2e/globals.js b/test/e2e/globals.js index aeecd204e..6a7eb07fb 100644 --- a/test/e2e/globals.js +++ b/test/e2e/globals.js @@ -23,9 +23,6 @@ module.exports = { username: 'user', password: 'testtest', }, - comment: { - body: 'This is a test comment', - }, organizationName: 'Coral', organizationContactEmail: 'coral@coralproject.net', }, diff --git a/test/e2e/page_objects/admin.js b/test/e2e/page_objects/admin.js index 8e319e499..64d22c97b 100644 --- a/test/e2e/page_objects/admin.js +++ b/test/e2e/page_objects/admin.js @@ -70,9 +70,28 @@ module.exports = { }, moderate: { selector: '.talk-admin-moderation-container', + commands: [ + { + url: function() { + return `${this.api.launchUrl}/admin/moderate`; + }, + ready() { + return this.waitForElementVisible('body'); + }, + goToQueue(queue) { + this.click(`#talk-admin-moderate-tab-${queue}`).expect.section( + `.talk-admin-moderate-queue-${queue}` + ).to.be.visible; + return this; + }, + }, + ], elements: { - comment: '.talk-admin-moderate-comment', - commentUsername: '.talk-admin-moderate-comment-username', + firstComment: '.talk-admin-moderate-comment', + firstCommentUsername: '.talk-admin-moderate-comment-username', + firstCommentContent: '.talk-admin-comment', + firstCommentApprove: '.talk-admin-approve-button', + firstCommentReject: '.talk-admin-reject-button', }, }, stories: { diff --git a/test/e2e/specs/03_embedStream.js b/test/e2e/specs/03_embedStream.js index 4b6831271..9eca7a736 100644 --- a/test/e2e/specs/03_embedStream.js +++ b/test/e2e/specs/03_embedStream.js @@ -1,3 +1,5 @@ +const commentBody = 'Embed Stream Test'; + module.exports = { '@tags': ['embedStream', 'login'], @@ -40,28 +42,26 @@ module.exports = { }, 'user posts a comment': client => { const comments = client.page.embedStream().section.comments; - const { testData: { comment } } = client.globals; comments .waitForElementVisible('@commentBoxTextarea') - .setValue('@commentBoxTextarea', comment.body) + .setValue('@commentBoxTextarea', commentBody) .waitForElementVisible('@commentBoxPostButton') .click('@commentBoxPostButton') .waitForElementVisible('@firstCommentContent') .getText('@firstCommentContent', result => { - comments.assert.equal(result.value, comment.body); + comments.assert.equal(result.value, commentBody); }); }, 'signed in user sees comment history': client => { const profile = client.page.embedStream().goToProfileSection(); - const { testData: { comment } } = client.globals; profile .waitForElementVisible('@myCommentHistory') .waitForElementVisible('@myCommentHistoryComment') .getText('@myCommentHistoryComment', result => { - profile.assert.equal(result.value, comment.body); + profile.assert.equal(result.value, commentBody); }); }, 'user sees replies and reactions to comments': client => { diff --git a/test/e2e/specs/05_banUser.js b/test/e2e/specs/05_banUser.js index 45ab0c347..51055a180 100644 --- a/test/e2e/specs/05_banUser.js +++ b/test/e2e/specs/05_banUser.js @@ -1,3 +1,5 @@ +const commentBody = 'Ban User Test'; + module.exports = { before: client => { client.setWindowPosition(0, 0); @@ -109,4 +111,51 @@ module.exports = { .waitForElementVisible('@commentBoxTextarea') .waitForElementVisible('@commentBoxPostButton'); }, + 'user posts comment, karma should stop it from happening': client => { + const comments = client.page.embedStream().section.comments; + + comments + .waitForElementVisible('@commentBoxTextarea') + .setValue('@commentBoxTextarea', commentBody) + .waitForElementVisible('@commentBoxPostButton') + .click('@commentBoxPostButton'); + + client.pause(2000); + + comments.waitForElementNotPresent('@firstCommentContent'); + }, + 'user logs out 3': client => { + const embedStream = client.page.embedStream(); + const comments = embedStream.section.comments; + + comments.logout(); + }, + 'admin logs in (3)': client => { + const adminPage = client.page.admin(); + const { testData: { admin } } = client.globals; + + adminPage.navigateAndLogin(admin); + }, + 'admin goes to moderation queue reported': client => { + const adminPage = client.page.admin(); + + adminPage.goToModerate().goToQueue('reported'); + }, + 'comment should be in reported queue': client => { + const moderate = client.page.admin().section.moderate; + + moderate + .waitForElementVisible('@firstComment') + .getText('@firstCommentContent', result => { + moderate.assert.equal(result.value, commentBody); + }); + }, + 'approve comment to restore karma': client => { + const moderate = client.page.admin().section.moderate; + + moderate.click('@firstCommentApprove'); + + // TODO: check why this fails. + // .waitForElementNotPresent('@firstComment'); + }, }; diff --git a/test/e2e/specs/06_suspendUser.js b/test/e2e/specs/06_suspendUser.js index 81fde3781..e02f21db9 100644 --- a/test/e2e/specs/06_suspendUser.js +++ b/test/e2e/specs/06_suspendUser.js @@ -1,3 +1,5 @@ +const commentBody = 'Suspend User Test'; + module.exports = { before: client => { client.setWindowPosition(0, 0); @@ -25,16 +27,15 @@ module.exports = { }, 'user posts comment': client => { const comments = client.page.embedStream().section.comments; - const { testData: { comment } } = client.globals; comments .waitForElementVisible('@commentBoxTextarea') - .setValue('@commentBoxTextarea', comment.body) + .setValue('@commentBoxTextarea', commentBody) .waitForElementVisible('@commentBoxPostButton') .click('@commentBoxPostButton') .waitForElementVisible('@firstCommentContent') .getText('@firstCommentContent', result => { - comments.assert.equal(result.value, comment.body); + comments.assert.equal(result.value, commentBody); }); }, 'user logs out': client => { @@ -84,9 +85,9 @@ module.exports = { .goToModerate(); moderate - .waitForElementVisible('@comment') - .waitForElementVisible('@commentUsername') - .click('@commentUsername'); + .waitForElementVisible('@firstComment') + .waitForElementVisible('@firstCommentUsername') + .click('@firstCommentUsername'); userDetailDrawer .waitForElementVisible('@actionsMenu') @@ -112,9 +113,9 @@ module.exports = { const { moderate, userDetailDrawer } = adminPage.section; moderate - .waitForElementVisible('@comment') - .waitForElementVisible('@commentUsername') - .click('@commentUsername'); + .waitForElementVisible('@firstComment') + .waitForElementVisible('@firstCommentUsername') + .click('@firstCommentUsername'); userDetailDrawer .waitForElementVisible('@tabBar')