Skip to content

Commit edfd4be

Browse files
committedJun 13, 2014
Hackishly include all items in the conversation stream
1 parent af2311e commit edfd4be

18 files changed

+202
-10
lines changed
 

‎Gemfile

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ gem 'rails', '~> 4.1.1'
3737
gem 'rails-assets-jquery-autosize'
3838
gem 'rails-assets-handlebars'
3939
gem 'rails-assets-moment'
40+
gem 'rails-assets-showdown'
4041
gem 'redcarpet'
4142
gem 'sass-rails', '~> 4.0.0'
4243
gem 'sequential', '>= 0.1'

‎Gemfile.lock

+2
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ GEM
273273
rails-assets-jquery-autosize (1.18.6)
274274
rails-assets-jquery (>= 1.7)
275275
rails-assets-moment (2.5.0)
276+
rails-assets-showdown (0.3.1)
276277
rails_12factor (0.0.2)
277278
rails_serve_static_assets
278279
rails_stdout_logging
@@ -430,6 +431,7 @@ DEPENDENCIES
430431
rails-assets-handlebars
431432
rails-assets-jquery-autosize
432433
rails-assets-moment
434+
rails-assets-showdown
433435
rails_12factor
434436
rake
435437
react-rails!

‎app/assets/javascripts/application.js

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
//= require react
1818
//= require react_ujs
1919
//= require moment
20+
//= require showdown
2021
//= require_tree ./components
2122

2223
$(document).ready(function() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/** @jsx React.DOM */
2+
3+
var AssignmentEvent = React.createClass({
4+
render: function() {
5+
return (
6+
<div className="event">
7+
<a href={this.props.assignmentEvent.user_url}>
8+
<Avatar initials={this.props.assignmentEvent.initials} gravatarUrl={this.props.assignmentEvent.avatar_url} />
9+
</a>
10+
11+
<strong>
12+
<a href={this.props.assignmentEvent.user_url}>
13+
{this.props.assignmentEvent.name}
14+
</a>
15+
16+
{' '} assigned
17+
18+
<a href={this.props.assignmentEvent.assignee_url}>
19+
<Avatar initials={this.props.assignmentEvent.assignee_initials} gravatarUrl={this.props.assignmentEvent.assignee_avatar_url} />
20+
21+
{this.props.assignmentEvent.assignee_name}
22+
</a>
23+
</strong>
24+
25+
{' '} to this conversation {this.timeAgoInWords()}
26+
</div>
27+
);
28+
},
29+
30+
timeAgoInWords: function() {
31+
return moment(this.props.assignmentEvent.updated_at).fromNow();
32+
}
33+
});

‎app/assets/javascripts/components/conversation.js.jsx

+29-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
/** @jsx React.DOM */
22

33
var Conversation = React.createClass({
4+
getInitialState: function() {
5+
return {
6+
messagesVisible: false
7+
};
8+
},
9+
410
render: function() {
511
return (
612
<div className="list-item" key={this.props.conversation.id}>
7-
<a href={this.props.conversation.url} className="conversation conversation-row">
13+
<div onClick={this.toggleMessages} className={this.conversationClassNames()}>
814
<div className="summary">
915
<Avatar initials={this.props.conversation.creator_person.initials} gravatarUrl={this.props.conversation.creator_person.gravatar_url} />
1016

@@ -28,9 +34,30 @@ var Conversation = React.createClass({
2834
</div>
2935

3036
<ConversationTagList tags={this.props.conversation.tags} />
37+
38+
{this.state.messagesVisible ? <Message message={this.props.conversation.stream_items[0]} detail={false} /> : ''}
3139
</div>
32-
</a>
40+
41+
{this.state.messagesVisible ? <ConversationStream items={this.props.conversation.stream_items} /> : ''}
42+
</div>
3343
</div>
3444
);
45+
},
46+
47+
toggleMessages: function(event) {
48+
event.preventDefault();
49+
event.stopPropagation();
50+
51+
this.setState({
52+
messagesVisible: !this.state.messagesVisible
53+
});
54+
},
55+
56+
conversationClassNames: function() {
57+
if(this.state.messagesVisible) {
58+
return ['conversation', 'conversation-detail'].join(' ');
59+
} else {
60+
return ['conversation', 'conversation-row'].join(' ');
61+
}
3562
}
3663
});

‎app/assets/javascripts/components/conversation_list.js.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ var ConversationList = React.createClass({
1919
return (
2020
<div className="list list-conversations">
2121
{this.state.conversations.map(function(conversation) {
22-
return <Conversation conversation={conversation} />
22+
return <Conversation conversation={conversation} key={conversation.id} />
2323
})}
2424
</div>
2525
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/** @jsx React.DOM */
2+
3+
var ConversationStream = React.createClass({
4+
render: function() {
5+
return (
6+
<div className="conversation-stream">
7+
{this.props.items.slice(1).map(function(item) {
8+
return <ConversationStreamItem item={item} key={item.id} />
9+
})}
10+
</div>
11+
);
12+
}
13+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/** @jsx React.DOM */
2+
3+
var ConversationStreamItem = React.createClass({
4+
render: function() {
5+
return this.componentForType(this.props.item.type);
6+
},
7+
8+
componentForType: function(type) {
9+
switch(type) {
10+
case 'message':
11+
return this.messageComponent();
12+
case 'tagevent':
13+
return this.tagEventComponent();
14+
case 'assignmentevent':
15+
return this.assignmentEventComponent();
16+
}
17+
},
18+
19+
messageComponent: function() {
20+
return (
21+
<Message message={this.props.item} detail={true} key={this.props.item.id} />
22+
)
23+
},
24+
25+
tagEventComponent: function() {
26+
return (
27+
<TagEvent tagEvent={this.props.item} key={this.props.item.id} />
28+
)
29+
},
30+
31+
assignmentEventComponent: function() {
32+
return (
33+
<AssignmentEvent assignmentEvent={this.props.item} key={this.props.item.id} />
34+
)
35+
}
36+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/** @jsx React.DOM */
2+
3+
var Message = React.createClass({
4+
render: function() {
5+
return (
6+
<div className="message">
7+
{this.props.detail ? <Avatar initials={this.props.message.person.initials} gravatarUrl={this.props.message.person.gravatar_url} /> : ''}
8+
9+
{this.props.detail ? <div className="person">{this.props.message.person.nickname}</div> : ''}
10+
11+
<div className="content" dangerouslySetInnerHTML={{__html: this.markdownContent()}} />
12+
</div>
13+
);
14+
},
15+
16+
markdownContent: function() {
17+
var converter = new Showdown.converter();
18+
return converter.makeHtml(this.props.message.content);
19+
}
20+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/** @jsx React.DOM */
2+
3+
var TagEvent = React.createClass({
4+
render: function() {
5+
return (
6+
<div className="event">
7+
<a href={this.props.tagEvent.user_url}>
8+
<Avatar initials={this.props.tagEvent.initials} gravatarUrl={this.props.tagEvent.avatar_url} />
9+
</a>
10+
11+
<strong>
12+
<a href={this.props.tagEvent.user_url}>
13+
{this.props.tagEvent.name}
14+
</a>
15+
16+
{' '} tagged
17+
</strong>
18+
19+
{' '} this conversation with {' '}
20+
21+
<a href={this.props.tagEvent.tag_url} className="tag">
22+
#{this.props.tagEvent.tag}
23+
</a>
24+
25+
{' '} {this.timeAgoInWords()}
26+
</div>
27+
);
28+
},
29+
30+
timeAgoInWords: function() {
31+
return moment(this.props.tagEvent.updated_at).fromNow();
32+
}
33+
});

‎app/models/conversation.rb

+3-1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ class Conversation < ActiveRecord::Base
6666

6767
scope :welcome_email, -> { where(subject: WelcomeConversation::SUBJECT) }
6868

69+
scope :with_messages, -> { where('(SELECT COUNT(messages.id) FROM messages WHERE messages.conversation_id = conversations.id) > 0') }
70+
6971
sequential column: :number,
7072
scope: :account_id
7173

@@ -98,7 +100,7 @@ def just_unarchived?
98100
end
99101

100102
def creator_person
101-
first_message.person
103+
first_message && first_message.person
102104
end
103105

104106
def messages_count

‎app/models/conversation_summarizer.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def summary
1616
end
1717

1818
def tweet_sized_snippet_from_first_message
19-
first_message.content[0...LENGTH]
19+
first_message && first_message.content[0...LENGTH]
2020
end
2121

2222
def first_message

‎app/models/conversations_inbox.rb

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ def queue_index(conversation)
6161
def open_conversations
6262
conversations = preloaded_conversations.unresolved
6363
conversations = conversations.unassigned_or_assigned_to(user)
64+
conversations = conversations.with_messages
6465
conversations
6566
end
6667
end

‎app/serializers/assignment_event_serializer.rb

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
class AssignmentEventSerializer < BaseSerializer
22
include ActionView::Helpers::DateHelper
33
include AvatarHelper
4+
include Rails.application.routes.url_helpers
45

5-
attributes :initials, :avatar_url, :name, :assignee_initials, :assignee_avatar_url, :assignee_name, :updated_at_in_words
6+
attributes :initials, :avatar_url, :name, :user_url, :assignee_initials, :assignee_avatar_url, :assignee_name, :assignee_url, :updated_at_in_words
67

78
def initials
89
object.user.person.initials
@@ -16,6 +17,10 @@ def name
1617
object.user.person.name
1718
end
1819

20+
def user_url
21+
search_account_conversations_path(object.conversation.account, assignee: object.user.id)
22+
end
23+
1924
def assignee_initials
2025
object.assignee.person.initials
2126
end
@@ -28,6 +33,10 @@ def assignee_name
2833
object.assignee.person.name
2934
end
3035

36+
def assignee_url
37+
search_account_conversations_path(object.conversation.account, assignee: object.assignee.id)
38+
end
39+
3140
def updated_at_in_words
3241
time_ago_in_words(object.updated_at)
3342
end

‎app/serializers/conversation_serializer.rb

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ class ConversationSerializer < BaseSerializer
33
has_one :creator_person
44
has_many :messages
55
has_many :participants
6+
has_many :stream_items
67

78
def include_messages?
89
options.fetch(:include_messages, true)
@@ -23,4 +24,8 @@ def summary
2324
def url
2425
"/#{account_slug}/#{number}"
2526
end
27+
28+
def stream_items
29+
ConversationStream.new(object).stream_items
30+
end
2631
end

‎app/serializers/message_serializer.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
class MessageSerializer < BaseSerializer
2-
attributes :body, :body_html
2+
attributes :body, :body_html, :content
33

44
has_one :conversation, embed: :ids
5-
has_one :person, embed: :ids
5+
has_one :person
66

77
has_many :attachments
88

‎app/serializers/tag_event_serializer.rb

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
class TagEventSerializer < BaseSerializer
22
include ActionView::Helpers::DateHelper
33
include AvatarHelper
4+
include Rails.application.routes.url_helpers
45

5-
attributes :tag, :initials, :avatar_url, :name, :updated_at_in_words
6+
attributes :tag, :initials, :avatar_url, :name, :updated_at_in_words, :user_url, :tag_url
67

78
def initials
89
object.user.person.initials
@@ -19,4 +20,12 @@ def name
1920
def updated_at_in_words
2021
time_ago_in_words(object.updated_at)
2122
end
23+
24+
def user_url
25+
search_account_conversations_path(object.conversation.account, assignee: object.user.id)
26+
end
27+
28+
def tag_url
29+
search_account_conversations_path(object.conversation.account, tag: object.tag)
30+
end
2231
end

‎db/schema.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
# These are extensions that must be enabled in order to support this database
1717
enable_extension "plpgsql"
1818
enable_extension "hstore"
19+
enable_extension "pg_stat_statements"
1920
enable_extension "uuid-ossp"
2021

2122
create_table "accounts", id: false, force: true do |t|
@@ -107,7 +108,6 @@
107108
end
108109

109110
add_index "conversations", ["account_id", "archived"], name: "index_conversations_on_account_id_and_archived", using: :btree
110-
add_index "conversations", ["account_id"], name: "index_conversations_on_account_id", using: :btree
111111
add_index "conversations", ["hidden"], name: "index_conversations_on_hidden", using: :btree
112112
add_index "conversations", ["user_id"], name: "index_conversations_on_user_id", using: :btree
113113

0 commit comments

Comments
 (0)
Please sign in to comment.