mirror of
https://github.com/wassname/talk.git
synced 2026-07-15 11:26:58 +08:00
Merge branch 'master' into staff-notifications
This commit is contained in:
+21
-6
@@ -8,6 +8,7 @@ job_defaults: &job_defaults
|
||||
integration_environment: &integration_environment
|
||||
NODE_ENV: test
|
||||
CIRCLE_TEST_REPORTS: /tmp/circleci-test-results
|
||||
E2E_MAX_RETRIES: 3
|
||||
|
||||
# integration_job runs the integration tests and saves the test results.
|
||||
integration_job: &integration_job
|
||||
@@ -29,6 +30,7 @@ integration_job: &integration_job
|
||||
name: Run the integration tests
|
||||
command: bash .circleci/e2e.sh
|
||||
- store_test_results:
|
||||
when: always
|
||||
path: /tmp/circleci-test-results
|
||||
|
||||
version: 2
|
||||
@@ -104,13 +106,19 @@ jobs:
|
||||
name: Setup the test results directory
|
||||
command: mkdir -p /tmp/circleci-test-results
|
||||
- run:
|
||||
name: Run the unit tests
|
||||
command: yarn test
|
||||
name: Run the client unit tests
|
||||
command: yarn test:client --ci
|
||||
environment:
|
||||
MOCHA_FILE: /tmp/circleci-test-results/junit/test-results.xml
|
||||
JEST_JUNIT_OUTPUT: /tmp/circleci-test-results/jest/test-results.xml
|
||||
JEST_REPORTER: jest-junit
|
||||
- run:
|
||||
name: Run the server unit tests
|
||||
command: yarn test:server
|
||||
environment:
|
||||
MOCHA_FILE: /tmp/circleci-test-results/mocha/test-results.xml
|
||||
MOCHA_REPORTER: mocha-junit-reporter
|
||||
NODE_ENV: test
|
||||
- store_test_results:
|
||||
when: always
|
||||
path: /tmp/circleci-test-results
|
||||
|
||||
# test_integration_chrome_local will run the integration tests locally with
|
||||
@@ -164,6 +172,8 @@ jobs:
|
||||
<<: *integration_environment
|
||||
BROWSERSTACK: true
|
||||
E2E_BROWSERS: ie
|
||||
# TODO: remove when more reliable
|
||||
E2E_MAX_RETRIES: 1
|
||||
|
||||
# test_integration_safari will run the integration tests with safari in
|
||||
# browserstack.
|
||||
@@ -173,6 +183,8 @@ jobs:
|
||||
<<: *integration_environment
|
||||
BROWSERSTACK: true
|
||||
E2E_BROWSERS: safari
|
||||
# TODO: remove when more reliable
|
||||
E2E_MAX_RETRIES: 1
|
||||
|
||||
# deploy will deploy the application as a docker image.
|
||||
deploy:
|
||||
@@ -182,7 +194,7 @@ jobs:
|
||||
- setup_remote_docker
|
||||
- run:
|
||||
name: Deploy the code
|
||||
command: bash ./scripts/docker.sh
|
||||
command: bash ./scripts/docker.sh deploy
|
||||
|
||||
# filter_deploy will add the filters for a deploy job in a workflow to make it
|
||||
# only execute on a deploy related job.
|
||||
@@ -274,4 +286,7 @@ workflows:
|
||||
- test_unit
|
||||
- test_integration_chrome
|
||||
- test_integration_firefox
|
||||
- test_integration_edge
|
||||
- test_integration_edge
|
||||
# TODO: uncomment when more reliable
|
||||
# - test_integration_ie
|
||||
# - test_integration_safari
|
||||
@@ -40,6 +40,7 @@ plugins/*
|
||||
!plugins/talk-plugin-mod
|
||||
!plugins/talk-plugin-moderation-actions
|
||||
!plugins/talk-plugin-notifications
|
||||
!plugins/talk-plugin-notifications-category-featured
|
||||
!plugins/talk-plugin-notifications-category-reply
|
||||
!plugins/talk-plugin-notifications-category-staff
|
||||
!plugins/talk-plugin-offtopic
|
||||
|
||||
+2
-1
@@ -143,7 +143,7 @@ async function reconcileRemotePlugins({ dryRun, upgradeRemote }) {
|
||||
|
||||
if (fetchable.length > 0) {
|
||||
console.log(
|
||||
`$ yarn add --ignore-scripts ${fetchable.map(
|
||||
`$ yarn add --ignore-scripts --ignore-workspace-root-check ${fetchable.map(
|
||||
({ name, version }) => `${name}@${version}`.cyan
|
||||
)}`
|
||||
);
|
||||
@@ -152,6 +152,7 @@ async function reconcileRemotePlugins({ dryRun, upgradeRemote }) {
|
||||
let args = [
|
||||
'add',
|
||||
'--ignore-scripts',
|
||||
'--ignore-workspace-root-check',
|
||||
...fetchable.map(({ name, version }) => `${name}@${version}`),
|
||||
];
|
||||
|
||||
|
||||
@@ -149,3 +149,11 @@ Source: [plugins/talk-plugin-notifications-category-reply](https://github.com/co
|
||||
|
||||
Replies made to each user will trigger an email to be sent with the notification
|
||||
details if enabled.
|
||||
|
||||
### talk-plugin-notifications-category-featured
|
||||
{:.param}
|
||||
|
||||
Source: [plugins/talk-plugin-notifications-category-featured](https://github.com/coralproject/talk/tree/master/plugins/talk-plugin-notifications-category-featured){:target="_blank"}
|
||||
|
||||
When a comment is featured (via the `talk-plugin-featured-comments` plugin), the
|
||||
user will receive a notification email.
|
||||
|
||||
@@ -27,6 +27,8 @@ module.exports = {
|
||||
'\\.ya?ml$': '<rootDir>/test/client/yamlTransformer.js',
|
||||
},
|
||||
|
||||
testResultsProcessor: process.env.JEST_REPORTER,
|
||||
|
||||
moduleNameMapper: {
|
||||
'^plugin-api\\/(.*)$': '<rootDir>/plugin-api/$1',
|
||||
'^plugins\\/(.*)$': '<rootDir>/plugins/$1',
|
||||
|
||||
@@ -216,6 +216,7 @@
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"ip": "^1.1.5",
|
||||
"jest": "^21.2.1",
|
||||
"jest-junit": "^3.6.0",
|
||||
"lint-staged": "^7.0.0",
|
||||
"mocha": "^3.1.2",
|
||||
"mocha-junit-reporter": "^1.12.1",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "@coralproject/eslint-config-talk/client"
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { compose, gql } from 'react-apollo';
|
||||
import Toggle from 'talk-plugin-notifications/client/components/Toggle';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import { withFragments } from 'plugin-api/beta/client/hocs';
|
||||
|
||||
class ToggleContainer extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
props.setTurnOffInputFragment({ onFeatured: false });
|
||||
|
||||
if (this.getOnFeaturedSetting()) {
|
||||
props.indicateOn();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const prevSetting = this.getOnFeaturedSetting(this.props);
|
||||
const nextSetting = this.getOnFeaturedSetting(nextProps);
|
||||
if (prevSetting && !nextSetting) {
|
||||
nextProps.indicateOff();
|
||||
} else if (!prevSetting && nextSetting) {
|
||||
nextProps.indicateOn();
|
||||
}
|
||||
}
|
||||
|
||||
getOnFeaturedSetting = (props = this.props) =>
|
||||
props.root.me.notificationSettings.onFeatured;
|
||||
|
||||
toggle = () => {
|
||||
this.props.updateNotificationSettings({
|
||||
onFeatured: !this.getOnFeaturedSetting(),
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Toggle checked={this.getOnFeaturedSetting()} onChange={this.toggle}>
|
||||
{t('talk-plugin-notifications-category-featured.toggle_description')}
|
||||
</Toggle>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ToggleContainer.propTypes = {
|
||||
data: PropTypes.object,
|
||||
root: PropTypes.object,
|
||||
indicateOn: PropTypes.func.isRequired,
|
||||
indicateOff: PropTypes.func.isRequired,
|
||||
setTurnOffInputFragment: PropTypes.func.isRequired,
|
||||
updateNotificationSettings: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const enhance = compose(
|
||||
withFragments({
|
||||
root: gql`
|
||||
fragment TalkNotificationsCategoryFeatured_Toggle_root on RootQuery {
|
||||
me {
|
||||
notificationSettings {
|
||||
onFeatured
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
);
|
||||
|
||||
export default enhance(ToggleContainer);
|
||||
@@ -0,0 +1,33 @@
|
||||
import { gql } from 'react-apollo';
|
||||
|
||||
export default {
|
||||
mutations: {
|
||||
UpdateNotificationSettings: ({
|
||||
variables: { input },
|
||||
state: { auth: { user: { id } } },
|
||||
}) => ({
|
||||
update: proxy => {
|
||||
if (input.onFeatured === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fragment = gql`
|
||||
fragment TalkNotificationsCategoryFeatured_User_Fragment on User {
|
||||
notificationSettings {
|
||||
onFeatured
|
||||
}
|
||||
}
|
||||
`;
|
||||
const fragmentId = `User_${id}`;
|
||||
const data = {
|
||||
__typename: 'User',
|
||||
notificationSettings: {
|
||||
__typename: 'NotificationSettings',
|
||||
onFeatured: input.onFeatured,
|
||||
},
|
||||
};
|
||||
proxy.writeFragment({ fragment, id: fragmentId, data });
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import Toggle from './containers/Toggle';
|
||||
import translations from './translations.yml';
|
||||
import graphql from './graphql';
|
||||
|
||||
export default {
|
||||
slots: {
|
||||
notificationSettings: [Toggle],
|
||||
},
|
||||
translations,
|
||||
...graphql,
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
en:
|
||||
talk-plugin-notifications-category-featured:
|
||||
toggle_description: My comment is featured
|
||||
@@ -0,0 +1,118 @@
|
||||
const { graphql } = require('graphql');
|
||||
const { get } = require('lodash');
|
||||
const path = require('path');
|
||||
|
||||
const handle = async (ctx, { comment }) => {
|
||||
const { connectors: { graph: { schema } } } = ctx;
|
||||
|
||||
// Check to see if this is a reply to an existing comment.
|
||||
const commentID = get(comment, 'id', null);
|
||||
if (commentID === null) {
|
||||
ctx.log.debug('could not get comment id');
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute the graph request.
|
||||
const reply = await graphql(
|
||||
schema,
|
||||
`
|
||||
query GetAuthorUserMetadata($comment_id: ID!) {
|
||||
comment(id: $comment_id) {
|
||||
id
|
||||
user {
|
||||
id
|
||||
notificationSettings {
|
||||
onFeatured
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{},
|
||||
ctx,
|
||||
{ comment_id: commentID }
|
||||
);
|
||||
if (reply.errors) {
|
||||
ctx.log.error({ err: reply.errors }, 'could not query for author metadata');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the user has notifications enabled.
|
||||
const enabled = get(
|
||||
reply,
|
||||
'data.comment.user.notificationSettings.onFeatured',
|
||||
false
|
||||
);
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const userID = get(reply, 'data.comment.user.id', null);
|
||||
if (!userID) {
|
||||
ctx.log.debug('could not get comment user id');
|
||||
return;
|
||||
}
|
||||
|
||||
// The user does have notifications for featured comments enabled, queue the
|
||||
// notification to be sent.
|
||||
return { userID, date: comment.created_at, context: comment.id };
|
||||
};
|
||||
|
||||
const hydrate = async (ctx, category, context) => {
|
||||
const { connectors: { graph: { schema } } } = ctx;
|
||||
|
||||
const reply = await graphql(
|
||||
schema,
|
||||
`
|
||||
query GetNotificationData($context: ID!) {
|
||||
comment(id: $context) {
|
||||
id
|
||||
asset {
|
||||
title
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{},
|
||||
ctx,
|
||||
{ context }
|
||||
);
|
||||
if (reply.errors) {
|
||||
throw reply.errors;
|
||||
}
|
||||
|
||||
const comment = get(reply, 'data.comment');
|
||||
const headline = get(comment, 'asset.title', null);
|
||||
const assetURL = get(comment, 'asset.url', null);
|
||||
const permalink = `${assetURL}?commentId=${comment.id}`;
|
||||
|
||||
return [headline, permalink];
|
||||
};
|
||||
|
||||
const handler = {
|
||||
handle,
|
||||
category: 'featured',
|
||||
event: 'commentFeatured',
|
||||
hydrate,
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
typeDefs: `
|
||||
type NotificationSettings {
|
||||
onFeatured: Boolean!
|
||||
}
|
||||
|
||||
input NotificationSettingsInput {
|
||||
onFeatured: Boolean
|
||||
}
|
||||
`,
|
||||
resolvers: {
|
||||
NotificationSettings: {
|
||||
// onFeatured returns false by default if not specified.
|
||||
onFeatured: settings => get(settings, 'onFeatured', false),
|
||||
},
|
||||
},
|
||||
translations: path.join(__dirname, 'translations.yml'),
|
||||
notifications: [handler],
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
en:
|
||||
talk-plugin-notifications:
|
||||
categories:
|
||||
featured:
|
||||
subject: "One of your comments was featured on [{0}]"
|
||||
body: "{0}\nA member of our team has selected this comment to be featured for other readers: {1}"
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "@coralproject/eslint-config-talk"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "@coralproject/eslint-config-talk"
|
||||
}
|
||||
@@ -23,9 +23,9 @@ class SettingsContainer extends React.Component {
|
||||
hasNotifications: this.state.hasNotifications.filter(i => i !== plugin),
|
||||
});
|
||||
setTurnOffInputFragment = fragment =>
|
||||
this.setState({
|
||||
turnOffInput: { ...this.state.turnOffInput, ...fragment },
|
||||
});
|
||||
this.setState(state => ({
|
||||
turnOffInput: { ...state.turnOffInput, ...fragment },
|
||||
}));
|
||||
|
||||
turnOffAll = () => {
|
||||
this.props.updateNotificationSettings(this.state.turnOffInput);
|
||||
|
||||
@@ -5399,6 +5399,14 @@ jest-jasmine2@^22.4.2:
|
||||
jest-util "^22.4.1"
|
||||
source-map-support "^0.5.0"
|
||||
|
||||
jest-junit@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-3.6.0.tgz#f4c4358e5286364a4324dc14abddd526aadfbd38"
|
||||
dependencies:
|
||||
mkdirp "^0.5.1"
|
||||
strip-ansi "^4.0.0"
|
||||
xml "^1.0.1"
|
||||
|
||||
jest-matcher-utils@^21.2.1:
|
||||
version "21.2.1"
|
||||
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-21.2.1.tgz#72c826eaba41a093ac2b4565f865eb8475de0f64"
|
||||
@@ -10987,7 +10995,7 @@ xml-name-validator@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
|
||||
|
||||
xml@^1.0.0:
|
||||
xml@^1.0.0, xml@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user