Unify huddles and personals into private messages on the send path

Personals are now just private messages between two people (which
sometimes manifests as a private message with one recipient).  The
new message type on the send path is 'private'.  Note that the receive
path still has 'personal' and 'huddle' message types.

(imported from commit 97a438ef5c0b3db4eb3e6db674ea38a081265dd3)
This commit is contained in:
Zev Benjamin 2012-11-07 18:38:21 -05:00
parent 0c1cccc880
commit 195bdb07c9
19 changed files with 83 additions and 95 deletions

View File

@ -57,7 +57,7 @@ if child_pid == 0:
# Run the humbug => zephyr mirror in the child
time.sleep(3)
humbug_client.send_message({
"type": "personal",
"type": "private",
"content": str(hzkey1),
"recipient": humbug_user,
});

View File

@ -128,7 +128,7 @@ def send_reminders():
message = 'Reminder:\n\n' + '\n'.join('* ' + m for m in messages)
humbug.send_message(dict(
type = 'personal',
type = 'private',
recipient = options.user,
content = message))

View File

@ -247,10 +247,10 @@ def process_notice(notice, log):
'zsig' : zsig, # logged here but not used by app
'content' : body }
if is_huddle:
zeph['type'] = 'personal'
zeph['type'] = 'private'
zeph['recipient'] = huddle_recipients
elif is_personal:
zeph['type'] = 'personal'
zeph['type'] = 'private'
zeph['recipient'] = to_humbug_username(notice.recipient)
else:
zeph['type'] = 'stream'
@ -261,7 +261,7 @@ def process_notice(notice, log):
zeph["subject"] = '(instance "%s")' % (notice.instance,)
# Add instances in for instanced personals
if zeph['type'] == "personal" and notice.instance.lower() != "personal":
if zeph['type'] == "private" and notice.instance.lower() != "personal":
zeph["content"] = "[-i %s]" % (notice.instance,) + "\n" + zeph["content"]
zeph = decode_unicode_byte_strings(zeph)

View File

@ -4,7 +4,7 @@
curl https://humbughq.com/api/v1/send_message \
-d "api-key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-d "email=tabbott@humbughq.com" \
-d "type=personal" -d "content=test" \
-d "type=private" -d "content=test" \
-d "recipient=tabbott@humbughq.com"
curl https://humbughq.com/api/v1/get_messages \

View File

@ -31,7 +31,7 @@ client = api.common.HumbugAPI(email=options.user,
site=options.site)
message_data = {
"type": "personal",
"type": "private",
"content": "test",
"recipient": "tabbott@humbughq.com",
}

View File

@ -92,10 +92,10 @@
value="" placeholder="Subject" autocomplete="off" tabindex="130"/>
</td>
</tr>
<tr id="personal-message">
<tr id="private-message">
<td colspan="2" class="message_header message_header_huddle left_part"></td>
<td class="message_header message_header_huddle right_part">
You and <input type="text" class="recipient_box" name="recipient" id="huddle_recipient"
You and <input type="text" class="recipient_box" name="recipient" id="private_message_recipient"
value="" placeholder="one or more people..." autocomplete="off" tabindex="130"/>
</td>
</tr>
@ -109,7 +109,7 @@
</a>
</li>
<li>
<a href="#personal-message" data-toggle="pill" title="Send a private message" tabindex="110">
<a href="#private-message" data-toggle="pill" title="Send a private message" tabindex="110">
<i class="icon-user"></i>
</a>
</li>

View File

@ -123,7 +123,7 @@ var people_list = [
</a>
</li>
<li title="Feedback">
<a href="#feedback" onclick="compose.start('personal', { 'huddle_recipient': 'feedback@humbughq.com' });">
<a href="#feedback" onclick="compose.start('private', { 'private_message_recipient': 'feedback@humbughq.com' });">
<i class="icon-comment"></i>
<span class="hidden-phone"> Feedback</span>
</a>

View File

@ -309,7 +309,7 @@ def main(args):
site="https://staging.humbughq.com",
api_key=file(api_key_file).read().strip(),
verbose=True)
client.send_message({'type': "personal",
client.send_message({'type': "private",
'recipient': [opts.reviewers],
'content': "I just sent you a review request! Check your email for details."})

View File

@ -46,7 +46,7 @@
{{/if}}
{{/include_recipient}}
<tr zid="{{id}}" id="{{dom_id}}"
class="message_row{{^is_stream}} personal-message{{/is_stream}}{{#include_sender}} include-sender{{/include_sender}}">
class="message_row{{^is_stream}} private-message{{/is_stream}}{{#include_sender}} include-sender{{/include_sender}}">
<td class="message_picture">
{{#include_sender}}
<img class="img-rounded profile_picture user_info_hover"
@ -55,7 +55,7 @@
{{/include_sender}}
</td>
<td class="pointer"><p></p></td>
<td class="messagebox{{^include_sender}} prev_is_same_sender{{/include_sender}}{{^is_stream}} personal-message{{/is_stream}}"
<td class="messagebox{{^include_sender}} prev_is_same_sender{{/include_sender}}{{^is_stream}} private-message{{/is_stream}}"
onclick="select_message_by_id({{id}});"
onmousedown="mousedown();"
onmousemove="mousemove();"

View File

@ -7,35 +7,28 @@ exports.start = function (msg_type, opts) {
opts = $.extend({ message_type: msg_type,
stream: '',
subject: '',
huddle_recipient: '',
private_message_recipient: '',
message: ''
}, opts);
$("#stream").val(opts.stream);
$("#subject").val(opts.subject);
$("#huddle_recipient").val(opts.huddle_recipient);
$("#private_message_recipient").val(opts.private_message_recipient);
$("#new_message_content").val(opts.message);
$('#sidebar a[href="#home"]').tab('show');
if (msg_type !== 'stream') {
// TODO: Just to make sure that compose.composing() output is
// consistent. We really should just standardize our
// terminology
msg_type = "huddle";
}
var focus_area;
if (opts.stream && ! opts.subject) {
focus_area = 'subject';
} else if (opts.stream || opts.huddle_recipient) {
} else if (opts.stream || opts.private_message_recipient) {
focus_area = 'new_message_content';
}
if (msg_type === 'stream') {
exports.show('stream', $("#" + (focus_area || 'stream')));
} else {
exports.show('personal', $("#" + (focus_area || 'huddle_recipient')));
exports.show('private', $("#" + (focus_area || 'private_message_recipient')));
}
hotkeys.set_compose();
@ -64,11 +57,6 @@ function send_message() {
recipient: JSON.stringify(recipients),
content: compose.message_content()};
// TODO: this is just dumb
if (request.type === 'huddle') {
request.type = 'personal';
}
$.ajax({
dataType: 'json', // This seems to be ignored. We still get back an xhr.
url: '/json/send_message',
@ -153,22 +141,22 @@ exports.set_message_type = function (tabname) {
is_composing_message = tabname;
$("#send-status").removeClass(status_classes).hide();
if (tabname === "stream") {
$('#personal-message').hide();
$('#private-message').hide();
$('#stream-message').show();
$('#new_message_type').val('stream');
$("#stream").focus();
} else {
$('#personal-message').show();
$('#private-message').show();
$('#stream-message').hide();
$('#new_message_type').val('personal');
$("#huddle_recipient").focus();
$('#new_message_type').val('private');
$("#private_message_recipient").focus();
}
};
exports.toggle_mode = function () {
if ($("#message-type-tabs li.active").find("a[href=#stream-message]").length !== 0) {
// In stream tab, switch to personals.
exports.show('personal', $("#huddle_recipient"));
// In stream tab, switch to private
exports.show('private', $("#private_message_recipient"));
} else {
exports.show('stream', $("#stream"));
}
@ -195,7 +183,7 @@ function get_or_set(fieldname) {
exports.stream_name = get_or_set('stream');
exports.subject = get_or_set('subject');
exports.message_content = get_or_set('new_message_content');
exports.recipient = get_or_set('huddle_recipient');
exports.recipient = get_or_set('private_message_recipient');
function compose_error(error_text, bad_input) {
$('#send-status').removeClass(status_classes)
@ -267,9 +255,9 @@ function validate_stream_message() {
return true;
}
function validate_huddle_message() {
function validate_private_message() {
if (exports.recipient() === "") {
compose_error("Please specify at least one recipient", $("#huddle_recipient"));
compose_error("Please specify at least one recipient", $("#private_message_recipient"));
return false;
}
@ -284,8 +272,8 @@ exports.validate = function () {
return false;
}
if (exports.composing() === 'huddle') {
return validate_huddle_message();
if (exports.composing() === 'private') {
return validate_private_message();
} else {
return validate_stream_message();
}

View File

@ -23,7 +23,7 @@ exports.autocomplete_needs_update = function (needs_update) {
}
};
var huddle_typeahead_list = [];
var private_message_typeahead_list = [];
exports.update_autocomplete = function () {
stream_list.sort();
@ -33,41 +33,41 @@ exports.update_autocomplete = function () {
return 1;
});
huddle_typeahead_list = $.map(people_list, function (person) {
private_message_typeahead_list = $.map(people_list, function (person) {
return person.full_name + " <" + person.email + ">";
});
autocomplete_needs_update = false;
};
function get_huddle_recipients(query_string) {
function get_pm_recipients(query_string) {
// Assumes email addresses don't have commas or semicolons in them
return query_string.split(/\s*[,;]\s*/);
}
// Returns an array of huddle recipients, removing empty elements.
// Returns an array of private message recipients, removing empty elements.
// For example, "a,,b, " => ["a", "b"]
function get_cleaned_huddle_recipients(query_string) {
var recipients = get_huddle_recipients(query_string);
function get_cleaned_pm_recipients(query_string) {
var recipients = get_pm_recipients(query_string);
recipients = $.grep(recipients, function (elem, idx) {
return elem.match(/\S/);
});
return recipients;
}
function get_last_recipient_in_huddle(query_string) {
var recipients = get_huddle_recipients(query_string);
function get_last_recipient_in_pm(query_string) {
var recipients = get_pm_recipients(query_string);
return recipients[recipients.length-1];
}
// Loosely based on Bootstrap's default highlighter, but with escaping added.
function composebox_typeahead_highlighter(item) {
var query = this.query;
if ($(this.$element).attr('id') === 'huddle_recipient') {
// There could be multiple recipients in a huddle, we want to
// decide what to highlight based only on the most recent one
// we're entering.
query = get_last_recipient_in_huddle(this.query);
if ($(this.$element).attr('id') === 'private_message_recipient') {
// There could be multiple recipients in a private message,
// we want to decide what to highlight based only on the most
// recent one we're entering.
query = get_last_recipient_in_pm(this.query);
}
query = query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
var regex = new RegExp('(' + query + ')', 'ig');
@ -177,14 +177,14 @@ exports.initialize = function () {
highlighter: composebox_typeahead_highlighter,
tabSkips: true
});
$( "#huddle_recipient" ).typeahead({
$( "#private_message_recipient" ).typeahead({
source: function (query, process) {
return huddle_typeahead_list;
return private_message_typeahead_list;
},
items: 4,
highlighter: composebox_typeahead_highlighter,
matcher: function (item) {
var current_recipient = get_last_recipient_in_huddle(this.query);
var current_recipient = get_last_recipient_in_pm(this.query);
// If the name is only whitespace (does not contain any non-whitespace),
// we're between typing names; don't autocomplete anything for us.
if (! current_recipient.match(/\S/)) {
@ -194,7 +194,7 @@ exports.initialize = function () {
return (item.toLowerCase().indexOf(current_recipient.toLowerCase()) !== -1);
},
updater: function (item) {
var previous_recipients = get_cleaned_huddle_recipients(this.query);
var previous_recipients = get_cleaned_pm_recipients(this.query);
previous_recipients.pop();
previous_recipients = previous_recipients.join(", ");
if (previous_recipients.length !== 0) {
@ -210,9 +210,9 @@ exports.initialize = function () {
stopAdvance: true // Do not advance to the next field on a tab or enter
});
$( "#huddle_recipient" ).blur(function (event) {
$( "#private_message_recipient" ).blur(function (event) {
var val = $(this).val();
var recipients = get_cleaned_huddle_recipients(val);
var recipients = get_cleaned_pm_recipients(val);
$(this).val(recipients.join(", "));
});

View File

@ -91,7 +91,7 @@ function process_hotkey(e) {
compose.start('stream');
return process_compose_hotkey;
case 67: // 'C': compose huddle
compose.start('personal');
compose.start('private');
return process_compose_hotkey;
case 114: // 'r': respond to message
respond_to_message();

View File

@ -23,7 +23,7 @@ function preserve_compose(send_after_reload) {
url += "+stream=" + encodeURIComponent(compose.stream_name());
url += "+subject=" + encodeURIComponent(compose.subject());
} else {
url += "+msg_type=huddle";
url += "+msg_type=private";
url += "+recipient=" + encodeURIComponent(compose.recipient());
}
url += "+msg="+ encodeURIComponent(compose.message_content());
@ -55,7 +55,7 @@ $(function () {
// TODO: preserve focus
compose.start(vars.msg_type, {stream: vars.stream,
subject: vars.subject,
huddle_recipient: vars.recipient,
private_message_recipient: vars.recipient,
message: vars.msg});
if (send_now) {
compose.finish();

View File

@ -310,8 +310,8 @@ $(function () {
$('#message-type-tabs a[href="#stream-message"]').on('shown', function (e) {
compose.set_message_type('stream');
});
$('#message-type-tabs a[href="#personal-message"]').on('shown', function (e) {
compose.set_message_type('huddle');
$('#message-type-tabs a[href="#private-message"]').on('shown', function (e) {
compose.set_message_type('private');
});
// Prepare the click handler for subbing to a new stream to which

View File

@ -107,23 +107,23 @@ function respond_to_message(reply_type) {
subject = message.subject;
}
var huddle_recipient = message.reply_to;
var pm_recipient = message.reply_to;
if (reply_type === "personal" && message.type === "huddle") {
// reply_to for huddle messages is the whole huddle, so for
// personals replies we need to set the the huddle recipient
// to just the sender
huddle_recipient = message.sender_email;
pm_recipient = message.sender_email;
}
msg_type = reply_type;
if (msg_type === undefined) {
if (reply_type === 'personal'
|| message.type === 'personal'
|| message.type === 'huddle')
{
msg_type = 'private';
} else {
msg_type = message.type;
}
if (msg_type === "huddle") {
// Huddle messages use the personals compose box
msg_type = "personal";
}
compose.start(msg_type, {'stream': stream, 'subject': subject,
'huddle_recipient': huddle_recipient});
'private_message_recipient': pm_recipient});
}
// Called by mouseover etc.

View File

@ -154,7 +154,7 @@ td.pointer {
border-left: 0px;
}
.messagebox.personal-message {
.messagebox.private-message {
border-color: #444;
border-width: 0px 1px 1px 1px;
background-color: #feffe0;
@ -301,7 +301,7 @@ img.profile_picture {
filter: alpha(opacity=40);
}
.compose_table #personal-message {
.compose_table #private-message {
display: none;
}
@ -349,7 +349,7 @@ input.recipient_box {
#subject.recipient_box {
width: 64%;
}
#huddle_recipient.recipient_box {
#private_message_recipient.recipient_box {
width: 75%;
}

View File

@ -358,7 +358,7 @@ class MessagePOSTTest(AuthedTestCase):
Sending a personal message to a valid username is successful.
"""
self.login("hamlet@humbughq.com")
result = self.client.post("/json/send_message", {"type": "personal",
result = self.client.post("/json/send_message", {"type": "private",
"content": "Test message",
"client": "test suite",
"recipient": "othello@humbughq.com"})
@ -369,7 +369,7 @@ class MessagePOSTTest(AuthedTestCase):
Sending a personal message to an invalid email returns error JSON.
"""
self.login("hamlet@humbughq.com")
result = self.client.post("/json/send_message", {"type": "personal",
result = self.client.post("/json/send_message", {"type": "private",
"content": "Test message",
"client": "test suite",
"recipient": "nonexistent"})

View File

@ -154,17 +154,17 @@ wait_and_send('stream', {
content: 'test message C'
});
wait_and_send('personal', {
wait_and_send('private', {
recipient: 'cordelia@humbughq.com, hamlet@humbughq.com',
content: 'personal A'
});
wait_and_send('personal', {
wait_and_send('private', {
recipient: 'cordelia@humbughq.com, hamlet@humbughq.com',
content: 'personal B'
});
wait_and_send('personal', {
wait_and_send('private', {
recipient: 'cordelia@humbughq.com',
content: 'personal C'
});
@ -193,7 +193,7 @@ wait_for_receive(function () {
});
});
wait_and_send('personal', {
wait_and_send('private', {
recipient: 'cordelia@humbughq.com, hamlet@humbughq.com',
content: 'personal D'
});

View File

@ -483,10 +483,10 @@ def create_mirrored_message_users(request, user_profile):
if "recipient" not in request.POST:
return (False, None)
huddle_recipients = extract_recipients(request)
pm_recipients = extract_recipients(request)
# Then, check that all huddle/personal recipients are in our realm:
for recipient in huddle_recipients:
# Then, check that all private message recipients are in our realm:
for recipient in pm_recipients:
if not same_realm_email(user_profile, recipient):
return (False, None)
@ -498,8 +498,8 @@ def create_mirrored_message_users(request, user_profile):
else:
sender = user_profile
# Create users for huddle/personal recipients, if needed.
for recipient in huddle_recipients:
# Create users for private message recipients, if needed.
for recipient in pm_recipients:
create_user_if_needed(user_profile.realm, recipient,
recipient.split('@')[0],
recipient.split('@')[0],
@ -524,7 +524,7 @@ def send_message_backend(request, user_profile, sender, message_type_name = POST
if client_name == "zephyr_mirror":
# Here's how security works for non-superuser mirroring:
#
# The message must be (1) a huddle/personal message (2) that
# The message must be (1) a private message (2) that
# is both sent and received exclusively by other users in your
# realm which (3) must be the MIT realm and (4) you must have
# received the message.
@ -534,7 +534,7 @@ def send_message_backend(request, user_profile, sender, message_type_name = POST
# you report having sent you a message.
if "sender" not in request.POST:
return json_error("Missing sender")
if message_type_name != "personal" and not is_super_user:
if message_type_name != "private" and not is_super_user:
return json_error("User not authorized for this query")
(valid_input, mirror_sender) = create_mirrored_message_users(request, user_profile)
if not valid_input:
@ -572,16 +572,16 @@ def send_message_backend(request, user_profile, sender, message_type_name = POST
except Stream.DoesNotExist:
return json_error("Stream does not exist")
recipient = Recipient.objects.get(type_id=stream.id, type=Recipient.STREAM)
elif message_type_name == 'personal':
elif message_type_name == 'private':
if "recipient" not in request.POST:
return json_error("Missing recipients")
huddle_recipients = extract_recipients(request)
pm_recipients = extract_recipients(request)
if client_name == "zephyr_mirror":
if user_profile.user.email not in huddle_recipients and not forged:
if user_profile.user.email not in pm_recipients and not forged:
return json_error("User not authorized for this query")
recipient_profile_ids = set()
for recipient in huddle_recipients:
for recipient in pm_recipients:
if recipient == "":
continue
try:
@ -589,7 +589,7 @@ def send_message_backend(request, user_profile, sender, message_type_name = POST
except UserProfile.DoesNotExist:
return json_error("Invalid email '%s'" % (recipient,))
if len(recipient_profile_ids) > 1:
# Make sure the sender is included in the huddle
# Make sure the sender is included in huddle messages
recipient_profile_ids.add(sender.id)
huddle = get_huddle(list(recipient_profile_ids))
recipient = Recipient.objects.get(type_id=huddle.id, type=Recipient.HUDDLE)