mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-18 16:36:15 +00:00
support collaborate notis interval (#4841)
* support collaborate notis interval * fix unit-test * fix unit-test * fix unit-test * fix unit-test * fix unit-test * update text * fix error msg
This commit is contained in:
@@ -5,7 +5,8 @@ import { Utils } from '../../utils/utils';
|
||||
import toaster from '../toast';
|
||||
|
||||
const {
|
||||
initialEmailNotificationInterval
|
||||
fileUpdatesEmailInterval,
|
||||
collaborateEmailInterval
|
||||
} = window.app.pageOptions;
|
||||
|
||||
class EmailNotice extends React.Component {
|
||||
@@ -14,7 +15,7 @@ class EmailNotice extends React.Component {
|
||||
super(props);
|
||||
|
||||
// interval: in seconds
|
||||
this.intervalOptions = [
|
||||
this.fileUpdatesOptions = [
|
||||
{interval: 0, text: gettext('Don\'t send emails')},
|
||||
{interval: 3600, text: gettext('Per hour')},
|
||||
{interval: 14400, text: gettext('Per 4 hours')},
|
||||
@@ -22,22 +23,37 @@ class EmailNotice extends React.Component {
|
||||
{interval: 604800, text: gettext('Per week')}
|
||||
];
|
||||
|
||||
this.collaborateOptions = [
|
||||
{interval: 0, text: gettext('Don\'t send emails')},
|
||||
{interval: 3600, text: gettext('Per hour') + ' (' + gettext('If notifications have not be read within one hour, they will be sent to your mailbox') + ')'}
|
||||
];
|
||||
|
||||
this.state = {
|
||||
currentInterval: initialEmailNotificationInterval
|
||||
fileUpdatesEmailInterval: fileUpdatesEmailInterval,
|
||||
collaborateEmailInterval: collaborateEmailInterval
|
||||
};
|
||||
}
|
||||
|
||||
inputChange = (e) => {
|
||||
inputFileUpdatesEmailIntervalChange = (e) => {
|
||||
if (e.target.checked) {
|
||||
this.setState({
|
||||
currentInterval: e.target.value
|
||||
fileUpdatesEmailInterval: parseInt(e.target.value)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
inputCollaborateEmailIntervalChange = (e) => {
|
||||
if (e.target.checked) {
|
||||
this.setState({
|
||||
collaborateEmailInterval: parseInt(e.target.value)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
formSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
seafileAPI.updateEmailNotificationInterval(this.state.currentInterval).then((res) => {
|
||||
let { fileUpdatesEmailInterval, collaborateEmailInterval } = this.state;
|
||||
seafileAPI.updateEmailNotificationInterval(fileUpdatesEmailInterval, collaborateEmailInterval).then((res) => {
|
||||
toaster.success(gettext('Success'));
|
||||
}).catch((error) => {
|
||||
let errorMsg = Utils.getErrorMsg(error);
|
||||
@@ -46,23 +62,38 @@ class EmailNotice extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { currentInterval } = this.state;
|
||||
const { fileUpdatesEmailInterval, collaborateEmailInterval } = this.state;
|
||||
return (
|
||||
<div className="setting-item" id="email-notice">
|
||||
<h3 className="setting-item-heading">{gettext('Email Notification of File Changes')}</h3>
|
||||
<h3 className="setting-item-heading">{gettext('Email Notification')}</h3>
|
||||
<h6 className="">{gettext('Notifications of file changes')}</h6>
|
||||
<p className="mb-1">{gettext('The list of added, deleted and modified files will be sent to your mailbox.')}</p>
|
||||
<form method="post" action="" id="set-email-notice-interval-form" onSubmit={this.formSubmit}>
|
||||
{this.intervalOptions.map((item, index) => {
|
||||
{this.fileUpdatesOptions.map((item, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
<input type="radio" name="interval" value={item.interval} className="align-middle" id={`interval-option${index + 1}`} checked={currentInterval == item.interval} onChange={this.inputChange} />
|
||||
<React.Fragment key={`file-updates-${index}`}>
|
||||
<input type="radio" name="interval" value={item.interval} className="align-middle" id={`file-updates-interval-option${index + 1}`} checked={fileUpdatesEmailInterval == item.interval} onChange={this.inputFileUpdatesEmailIntervalChange} />
|
||||
<label className="align-middle m-0 ml-2" htmlFor={`interval-option${index + 1}`}>{item.text}</label>
|
||||
<br />
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
<button type="submit" className="btn btn-outline-primary mt-2">{gettext('Submit')}</button>
|
||||
</form>
|
||||
|
||||
<h6 className="mt-4">{gettext('Notifications of collaboration')}</h6>
|
||||
<p className="mb-1">{gettext('Whether the notifications of collaboration such as sharing library or joining group should be sent to your mailbox.')}</p>
|
||||
<form method="post" action="" id="set-email-notice-interval-form" onSubmit={this.formSubmit}>
|
||||
{this.collaborateOptions.map((item, index) => {
|
||||
return (
|
||||
<React.Fragment key={`collaborate-${index}`}>
|
||||
<input type="radio" name="interval" value={item.interval} className="align-middle" id={`collaborate-interval-option${index + 1}`} checked={collaborateEmailInterval == item.interval} onChange={this.inputCollaborateEmailIntervalChange} />
|
||||
<label className="align-middle m-0 ml-2" htmlFor={`interval-option${index + 1}`}>{item.text}</label>
|
||||
<br />
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</form>
|
||||
<button type="submit" className="btn btn-outline-primary mt-2" onClick={this.formSubmit}>{gettext('Submit')}</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -336,8 +336,10 @@ class AccountInfo(APIView):
|
||||
except InstitutionAdmin.DoesNotExist:
|
||||
info['is_inst_admin'] = False
|
||||
|
||||
interval = UserOptions.objects.get_file_updates_email_interval(email)
|
||||
info['email_notification_interval'] = 0 if interval is None else interval
|
||||
file_updates_email_interval = UserOptions.objects.get_file_updates_email_interval(email)
|
||||
info['file_updates_email_interval'] = 0 if file_updates_email_interval is None else file_updates_email_interval
|
||||
collaborate_email_interval = UserOptions.objects.get_collaborate_email_interval(email)
|
||||
info['collaborate_email_interval'] = 0 if collaborate_email_interval is None else collaborate_email_interval
|
||||
return info
|
||||
|
||||
def get(self, request, format=None):
|
||||
@@ -358,13 +360,20 @@ class AccountInfo(APIView):
|
||||
return api_error(status.HTTP_400_BAD_REQUEST,
|
||||
_("Name should not include '/'."))
|
||||
|
||||
email_interval = request.data.get("email_notification_interval", None)
|
||||
if email_interval is not None:
|
||||
file_updates_email_interval = request.data.get("file_updates_email_interval", None)
|
||||
if file_updates_email_interval is not None:
|
||||
try:
|
||||
email_interval = int(email_interval)
|
||||
file_updates_email_interval = int(file_updates_email_interval)
|
||||
except ValueError:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST,
|
||||
'email_interval invalid')
|
||||
'file_updates_email_interval invalid')
|
||||
collaborate_email_interval = request.data.get("collaborate_email_interval", None)
|
||||
if collaborate_email_interval is not None:
|
||||
try:
|
||||
collaborate_email_interval = int(collaborate_email_interval)
|
||||
except ValueError:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST,
|
||||
'collaborate_email_interval invalid')
|
||||
|
||||
# update user info
|
||||
|
||||
@@ -375,12 +384,19 @@ class AccountInfo(APIView):
|
||||
profile.nickname = name
|
||||
profile.save()
|
||||
|
||||
if email_interval is not None:
|
||||
if email_interval <= 0:
|
||||
if file_updates_email_interval is not None:
|
||||
if file_updates_email_interval <= 0:
|
||||
UserOptions.objects.unset_file_updates_email_interval(username)
|
||||
else:
|
||||
UserOptions.objects.set_file_updates_email_interval(
|
||||
username, email_interval)
|
||||
username, file_updates_email_interval)
|
||||
|
||||
if collaborate_email_interval is not None:
|
||||
if collaborate_email_interval <= 0:
|
||||
UserOptions.objects.unset_collaborate_email_interval(username)
|
||||
else:
|
||||
UserOptions.objects.set_collaborate_email_interval(
|
||||
username, collaborate_email_interval)
|
||||
|
||||
return Response(self._get_account_info(request))
|
||||
|
||||
|
@@ -23,6 +23,7 @@ from seahub.invitations.models import Invitation
|
||||
from seahub.profile.models import Profile
|
||||
from seahub.constants import HASH_URLS
|
||||
from seahub.utils import get_site_name
|
||||
from seahub.options.models import UserOptions, KEY_COLLABORATE_EMAIL_INTERVAL, KEY_COLLABORATE_LAST_EMAILED_TIME
|
||||
|
||||
# Get an instance of a logger
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -208,64 +209,73 @@ class Command(BaseCommand):
|
||||
return Profile.objects.get_user_language(username)
|
||||
|
||||
def do_action(self):
|
||||
now = datetime.datetime.now()
|
||||
emails = []
|
||||
user_file_updates_email_intervals = []
|
||||
for ele in UserOptions.objects.filter(
|
||||
option_key=KEY_COLLABORATE_EMAIL_INTERVAL):
|
||||
try:
|
||||
user_file_updates_email_intervals.append(
|
||||
(ele.email, int(ele.option_val))
|
||||
)
|
||||
emails.append(ele.email)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
self.stderr.write('[%s]: %s' % (str(datetime.datetime.now()), e))
|
||||
continue
|
||||
|
||||
try:
|
||||
cmd_last_check = CommandsLastCheck.objects.get(command_type=self.label)
|
||||
logger.debug('Last check time is %s' % cmd_last_check.last_check)
|
||||
user_last_emailed_time_dict = {}
|
||||
for ele in UserOptions.objects.filter(option_key=KEY_COLLABORATE_LAST_EMAILED_TIME).filter(email__in=emails):
|
||||
try:
|
||||
user_last_emailed_time_dict[ele.email] = datetime.datetime.strptime(
|
||||
ele.option_val, "%Y-%m-%d %H:%M:%S")
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
self.stderr.write('[%s]: %s' % (str(datetime.datetime.now()), e))
|
||||
continue
|
||||
|
||||
unseen_notices = UserNotification.objects.get_all_notifications(
|
||||
seen=False, time_since=cmd_last_check.last_check)
|
||||
|
||||
logger.debug('Update last check time to %s' % now)
|
||||
cmd_last_check.last_check = now
|
||||
cmd_last_check.save()
|
||||
except CommandsLastCheck.DoesNotExist:
|
||||
logger.debug('No last check time found, get all unread notices.')
|
||||
unseen_notices = UserNotification.objects.get_all_notifications(
|
||||
seen=False)
|
||||
|
||||
logger.debug('Create new last check time: %s' % now)
|
||||
CommandsLastCheck(command_type=self.label, last_check=now).save()
|
||||
|
||||
email_ctx = {}
|
||||
for notice in unseen_notices:
|
||||
if notice.to_user in email_ctx:
|
||||
email_ctx[notice.to_user] += 1
|
||||
# save current language
|
||||
cur_language = translation.get_language()
|
||||
for (to_user, interval_val) in user_file_updates_email_intervals:
|
||||
# get last_emailed_time if any, defaults to today 00:00:00.0
|
||||
last_emailed_time = user_last_emailed_time_dict.get(to_user, None)
|
||||
now = datetime.datetime.now().replace(microsecond=0)
|
||||
if not last_emailed_time:
|
||||
last_emailed_time = datetime.datetime.now().replace(hour=0).replace(
|
||||
minute=0).replace(second=0).replace(microsecond=0)
|
||||
else:
|
||||
email_ctx[notice.to_user] = 1
|
||||
if (now - last_emailed_time).total_seconds() < interval_val:
|
||||
continue
|
||||
|
||||
for to_user, count in list(email_ctx.items()):
|
||||
# save current language
|
||||
cur_language = translation.get_language()
|
||||
# get notices
|
||||
user_notices_qs = UserNotification.objects.get_all_notifications(seen=False, time_since=last_emailed_time)
|
||||
user_notices, count = list(user_notices_qs), user_notices_qs.count()
|
||||
if not count:
|
||||
continue
|
||||
|
||||
# get and active user language
|
||||
user_language = self.get_user_language(to_user)
|
||||
translation.activate(user_language)
|
||||
logger.debug('Set language code to %s for user: %s' % (user_language, to_user))
|
||||
self.stdout.write('[%s] Set language code to %s' % (
|
||||
str(datetime.datetime.now()), user_language))
|
||||
logger.debug('Set language code to %s for user: %s' % (
|
||||
user_language, to_user))
|
||||
self.stdout.write('[%s] Set language code to %s for user: %s' % (
|
||||
str(datetime.datetime.now()), user_language, to_user))
|
||||
|
||||
# format mail content and send
|
||||
notices = []
|
||||
for notice in unseen_notices:
|
||||
logger.info('Processing unseen notice: [%s]' % (notice))
|
||||
|
||||
for notice in user_notices:
|
||||
d = json.loads(notice.detail)
|
||||
|
||||
repo_id = d.get('repo_id', None)
|
||||
group_id = d.get('group_id', None)
|
||||
repo_id = d.get('repo_id')
|
||||
group_id = d.get('group_id')
|
||||
try:
|
||||
if repo_id and not seafile_api.get_repo(repo_id):
|
||||
notice.delete()
|
||||
continue
|
||||
|
||||
if group_id and not ccnet_api.get_group(group_id):
|
||||
notice.delete()
|
||||
continue
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
continue
|
||||
|
||||
if notice.to_user != to_user:
|
||||
continue
|
||||
|
||||
@@ -297,7 +307,6 @@ class Command(BaseCommand):
|
||||
|
||||
if not notices:
|
||||
continue
|
||||
|
||||
user_name = email2nickname(to_user)
|
||||
contact_email = Profile.objects.get_contact_email_by_user(to_user)
|
||||
to_user = contact_email # use contact email if any
|
||||
@@ -312,7 +321,9 @@ class Command(BaseCommand):
|
||||
send_html_email(_('New notice on %s') % get_site_name(),
|
||||
'notifications/notice_email.html', c,
|
||||
None, [to_user])
|
||||
|
||||
# set new last_emailed_time
|
||||
UserOptions.objects.set_collaborate_last_emailed_time(
|
||||
to_user, now)
|
||||
logger.info('Successfully sent email to %s' % to_user)
|
||||
self.stdout.write('[%s] Successfully sent email to %s' % (str(datetime.datetime.now()), to_user))
|
||||
except Exception as e:
|
||||
|
@@ -36,6 +36,8 @@ KEY_DEFAULT_REPO = "default_repo"
|
||||
KEY_WEBDAV_SECRET = "webdav_secret"
|
||||
KEY_FILE_UPDATES_EMAIL_INTERVAL = "file_updates_email_interval"
|
||||
KEY_FILE_UPDATES_LAST_EMAILED_TIME = "file_updates_last_emailed_time"
|
||||
KEY_COLLABORATE_EMAIL_INTERVAL = 'collaborate_email_interval'
|
||||
KEY_COLLABORATE_LAST_EMAILED_TIME = 'collaborate_last_emailed_time'
|
||||
|
||||
|
||||
class CryptoOptionNotSetError(Exception):
|
||||
@@ -306,6 +308,42 @@ class UserOptionsManager(models.Manager):
|
||||
def unset_file_updates_last_emailed_time(self, username):
|
||||
return self.unset_user_option(username, KEY_FILE_UPDATES_LAST_EMAILED_TIME)
|
||||
|
||||
def set_collaborate_email_interval(self, username, seconds):
|
||||
return self.set_user_option(username, KEY_COLLABORATE_EMAIL_INTERVAL,
|
||||
str(seconds))
|
||||
|
||||
def get_collaborate_email_interval(self, username):
|
||||
val = self.get_user_option(username, KEY_COLLABORATE_EMAIL_INTERVAL)
|
||||
if not val:
|
||||
return None
|
||||
try:
|
||||
return int(val)
|
||||
except ValueError:
|
||||
logger.error('Failed to convert string %s to int' % val)
|
||||
return None
|
||||
|
||||
def unset_collaborate_email_interval(self, username):
|
||||
return self.unset_user_option(username, KEY_COLLABORATE_EMAIL_INTERVAL)
|
||||
|
||||
def set_collaborate_last_emailed_time(self, username, time_dt):
|
||||
return self.set_user_option(
|
||||
username, KEY_COLLABORATE_LAST_EMAILED_TIME,
|
||||
time_dt.strftime("%Y-%m-%d %H:%M:%S"))
|
||||
|
||||
def get_collaborate_last_emailed_time(self, username):
|
||||
val = self.get_user_option(username, KEY_COLLABORATE_LAST_EMAILED_TIME)
|
||||
if not val:
|
||||
return None
|
||||
|
||||
try:
|
||||
return datetime.strptime(val, "%Y-%m-%d %H:%M:%S")
|
||||
except Exception:
|
||||
logger.error('Failed to convert string %s to datetime obj' % val)
|
||||
return None
|
||||
|
||||
def unset_collaborate_last_emailed_time(self, username):
|
||||
return self.unset_user_option(username, KEY_COLLABORATE_LAST_EMAILED_TIME)
|
||||
|
||||
|
||||
class UserOptions(models.Model):
|
||||
email = LowerCaseCharField(max_length=255, db_index=True)
|
||||
|
@@ -49,7 +49,8 @@ window.app.pageOptions = {
|
||||
})(),
|
||||
|
||||
{% if is_pro %}
|
||||
initialEmailNotificationInterval: {{ email_notification_interval }},
|
||||
fileUpdatesEmailInterval: {{ file_updates_email_interval }},
|
||||
collaborateEmailInterval: {{ collaborate_email_interval }},
|
||||
{% endif %}
|
||||
|
||||
twoFactorAuthEnabled: {% if two_factor_auth_enabled %} true {% else %} false {% endif %},
|
||||
|
@@ -84,8 +84,10 @@ def edit_profile(request):
|
||||
else:
|
||||
webdav_passwd = ''
|
||||
|
||||
email_inverval = UserOptions.objects.get_file_updates_email_interval(username)
|
||||
email_inverval = email_inverval if email_inverval is not None else 0
|
||||
file_updates_email_interval = UserOptions.objects.get_file_updates_email_interval(username)
|
||||
file_updates_email_interval = file_updates_email_interval if file_updates_email_interval is not None else 0
|
||||
collaborate_email_interval = UserOptions.objects.get_collaborate_email_interval(username)
|
||||
collaborate_email_interval = collaborate_email_interval if collaborate_email_interval is not None else 0
|
||||
|
||||
if work_weixin_oauth_check():
|
||||
enable_wechat_work = True
|
||||
@@ -123,7 +125,8 @@ def edit_profile(request):
|
||||
'ENABLE_DELETE_ACCOUNT': ENABLE_DELETE_ACCOUNT,
|
||||
'ENABLE_UPDATE_USER_INFO': ENABLE_UPDATE_USER_INFO,
|
||||
'webdav_passwd': webdav_passwd,
|
||||
'email_notification_interval': email_inverval,
|
||||
'file_updates_email_interval': file_updates_email_interval,
|
||||
'collaborate_email_interval': collaborate_email_interval,
|
||||
'social_next_page': reverse('edit_profile'),
|
||||
'enable_wechat_work': enable_wechat_work,
|
||||
'social_connected': social_connected,
|
||||
|
@@ -26,22 +26,23 @@ class AccountInfoTest(BaseTestCase):
|
||||
def test_update(self, ):
|
||||
self.login_as(self.user)
|
||||
|
||||
resp = self._do_put('name=foo&email_notification_interval=3000')
|
||||
resp = self._do_put('name=foo&file_updates_email_interval=3000&collaborate_email_interval=3000')
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
json_resp = json.loads(resp.content)
|
||||
assert json_resp['email_notification_interval'] == 3000
|
||||
assert json_resp['file_updates_email_interval'] == 3000
|
||||
assert json_resp['collaborate_email_interval'] == 3000
|
||||
assert json_resp['name'] == 'foo'
|
||||
|
||||
def test_update_email_nofification_interval(self, ):
|
||||
self.login_as(self.user)
|
||||
|
||||
resp = self._do_put('email_notification_interval=3000')
|
||||
resp = self._do_put('file_updates_email_interval=3000')
|
||||
self.assertEqual(200, resp.status_code)
|
||||
json_resp = json.loads(resp.content)
|
||||
assert json_resp['email_notification_interval'] == 3000
|
||||
assert json_resp['file_updates_email_interval'] == 3000
|
||||
|
||||
resp = self._do_put('email_notification_interval=0')
|
||||
resp = self._do_put('file_updates_email_interval=0')
|
||||
self.assertEqual(200, resp.status_code)
|
||||
json_resp = json.loads(resp.content)
|
||||
assert json_resp['email_notification_interval'] == 0
|
||||
assert json_resp['file_updates_email_interval'] == 0
|
||||
|
@@ -12,6 +12,7 @@ from seahub.profile.models import Profile
|
||||
from seahub.test_utils import BaseTestCase
|
||||
from seahub.notifications.management.commands.send_notices import Command
|
||||
from seahub.share.utils import share_dir_to_user, share_dir_to_group
|
||||
from seahub.options.models import UserOptions
|
||||
|
||||
try:
|
||||
from seahub.settings import LOCAL_PRO_DEV_ENV
|
||||
@@ -21,6 +22,16 @@ except ImportError:
|
||||
|
||||
class CommandTest(BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(CommandTest, self).setUp()
|
||||
UserOptions.objects.set_file_updates_email_interval(self.user.username, 3600)
|
||||
UserOptions.objects.set_collaborate_email_interval(self.user.username, 3600)
|
||||
|
||||
def tearDown(self):
|
||||
UserOptions.objects.unset_file_updates_last_emailed_time(self.user.username)
|
||||
UserOptions.objects.unset_collaborate_last_emailed_time(self.user.username)
|
||||
super(CommandTest, self).tearDown()
|
||||
|
||||
def test_can_send_repo_share_msg(self):
|
||||
self.assertEqual(len(mail.outbox), 0)
|
||||
UserNotification.objects.add_repo_share_msg(
|
||||
@@ -95,13 +106,13 @@ class CommandTest(BaseTestCase):
|
||||
self.assertEqual(len(mail.outbox), 0)
|
||||
|
||||
detail = file_comment_msg_to_json(self.repo.id, '/foo',
|
||||
self.user.username, 'test comment')
|
||||
UserNotification.objects.add_file_comment_msg('a@a.com', detail)
|
||||
'bar@bar.com', 'test comment')
|
||||
UserNotification.objects.add_file_comment_msg(self.user.username, detail)
|
||||
|
||||
call_command('send_notices')
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
assert mail.outbox[0].to[0] == 'a@a.com'
|
||||
assert 'new comment from user %s' % self.user.username in mail.outbox[0].body
|
||||
assert mail.outbox[0].to[0] == self.user.username
|
||||
assert 'new comment from user %s' % 'bar@bar.com' in mail.outbox[0].body
|
||||
assert '/foo' in mail.outbox[0].body
|
||||
|
||||
def test_send_guest_invitation_notice(self):
|
||||
|
Reference in New Issue
Block a user