Skip to content

Instantly share code, notes, and snippets.

@jayd3e
Created September 26, 2012 16:56
Show Gist options
  • Save jayd3e/3789169 to your computer and use it in GitHub Desktop.
Save jayd3e/3789169 to your computer and use it in GitHub Desktop.
Tiered Comment Notifications

Clustercomment

So I have been struggling with this problem for a bit now. The root of the problem is how does one properly notify people of when they receive comments on their content, when comments are tiered(meaning they form a tree of comments). For the purposes of this discussion, let's say our comments look something like this:

- comment(1) by danz
 |- comment(2) by jayd3e
   |- comment(4) by timtim
     |- comment(5) by jayd3e
   |- comment(6) by bburd
 |- comment(3) by aj
   |- comment(7) by mlitzo

The top comment denotes the root comment, and each '|-' denotes a child comment. You can figure out the parent of each child comment by moving 2 spaces to the left, and by moving upward to the first comment that is located at that tier. Using the example above, comment(7) is a child of comment(3), and comment(5) is a child of comment(4). We could also say that comment(5) is not only a child of comment(4), but also a child of comment(2) and comment(1) by relation. I will be using this form of notation to drive this discussion.

So imagine this tree of comments as it began, and then try to picture each individual comment getting added one by one, until it forms the result we see above. From the comment ids, we can see that:

  1. danz started the conversation with comment(1) by asking a question.
  2. jayd3e attempt to answer the question stated in comment(1) by creating comment(2).
  3. aj chimed into the conversation, by creating his own answer to comment(1) through comment(3).
  4. timtim decided he had an issue with jayd3e's comment(2), so it told jayd3e about it by creating comment(4).
  5. offended by this, jayd3e naturally flames timtim by creating comment(5)
  6. bburd, ignoring timtim's unnecessary comment(4), creates comment(6) to thank jayd3e for his insightful comment, what a guy XD.
  7. mlitzo realizes that all of the previous conversation around jayd3e's comment(2) is not of interest to her, so she decides to comment on aj's comment(3).

This may seem trivial, but a lot is going on here. For one, we have multiple people participating in three completely separate conversations connected by a single starting point. It's kind of hard to see that there are three separate conversations by simply looking at the tree, but it's far easier to understand logically if you paid close attention to the storyline. The first conversation takes place between danz, jayd3e, timtim. Danz asks a question, jayd3e attempts to answer it, timtim has an issue with jayd3e's answer, jayd3e flames timtim. The second conversation takes place between danz, jayd3e, and bburd. Danz asks a question, jayd3e answers it, and bburd thanks jayd3e. Lastly, the third conversation takes place between danz, aj, and mlitzo. Danz asks a question, aj answers it, and mlitzo comments on the answer. An interesting thing to note, is that each of the events in a conversation do not have to necessarily happen consecutively. For example in the context of the third conversation, mlitzo's comment happens directly after aj's answer, but in real time, all of the comments surrounding jayd3e's answer are created before her's exists.

So now that we have a good idea of how each of these individual comments play out over time, we can ask ourselves the one question that this entire discussion revolves around. As this scenario is playing out, how do we effectively notify each of the individual parties about what is currently happening in the context of a conversation? I've been using the word 'conversation' quite a bit to describe what is really going on here, so it's about time we give it an official definition. A conversation is a dialogue between two or more people ABOUT a comment. It's important to note two aspects of this definition, a conversation must be between at least two people, and a conversation has to be ABOUT something, in our case a comment. We make the distinction that it's ABOUT something, so that we can attach it to one or more comment objects in our data model.

How It's Done

So far, I've used two different algorithms to attempt to solve this problem. The first I'm calling "Notify All the Things" and the second I'm calling "Reddity." Both of these methods are described below. I also go on to state the pitfalls of each method, and why I ultimately decided to not go with it. This is more or less the history portion of this discussion, so if you're not interested in what I've already done, and you just want to skip to the point where I'm talking about the notification system I want, the skip to the "What I Want" section.

###Notify All The Things ####Data Model comments -------- id body author parent_comment # id of an adjacent comment comment_notification # attached comment_notification

comment_notifications
---------------------
id
comment_id
commenters # list of authors

notifications
-------------
id
notification_id # comment_notification id
user_id

####Description When a user creates a comment, traverse up the tree of comments. For each comment, if that comment ALREADY HAS a comment_notification attached to it, then add the author of the newly created comment to the commenters of the comment_notification. If the newly created comment DOESN'T have a comment_notification attached to it, then created one and add the author of the newly created comment to the commenters list. In both cases, if the author of the newly created comment is also the author of the current comment, then don't send out a notification. This is kind of confusing, so here is some pseudocode:

created = # newly created comment
current = created
while current.parent_comment:
    parent = current.parent_comment
    if current.comment_notification:
        # make the notification active
        # assume this doesn't allow repeat commenters
        comment_notification.commenters.append(created.author)
    else:
        comment_notification = CommentNotification(comment=current)
        comment_notification.commenters.append(created.author)

    if not created.author is current.author:
        # notify user that someone has created a child of their comment
        db.add(Notification(user=current.author, comment_notification=comment_notification))
    current = parent

####Behavior So this route has some interesting implications that may not seem completely obvious. Let's say we have a comment chain like so:

- comment(1) by danz
 |- comment(2) by jayd3e
   |- comment(4) by timtim

Let's say timtim just created a comment, and we need to notify all relevant parties about it. It would make sense that both danz and jayd3e need to be notified that something down the line has changed. So as stated above, we traverse up the tree, creating comment_notifications where we need to, and notifying each party up the chain. So in this case, danz and jayd3e both get notifications about timtim's comment, like so:

jayd3e's notifications:
* timtim commented on your comment, saying "This comment suks!"

danz's notifications:
* jayd3e and timtim commented on your comment.  timtim said "This comment suks!"

Even if danz had already seen that notification and deactivated it, then by creating a new notification, we are reactivating the link between the user and notification, so it pops up on danz's interface once again. So we see the expected outcome in this scenario. Let's try another slightly different scenario:

- comment(1) by danz
 |- comment(2) by jayd3e
   |- comment(4) by timtim
     |- comment(5) by jayd3e

We have jayd3e starting a flamewar with timtim again. Let's see how our algorithm reacts this time. It does the same thing as the first example, this time however, jayd3e is re-entering a conversation he already belongs to. So this is where if not created.author is current.author: comes into play, and stops us from notifying a jayd3e about his own comment. So it works in that scenario as well. Now here's where we start to see some problems:

- comment(1) by danz
 |- comment(2) by jayd3e
   |- comment(4) by timtim
     |- comment(5) by jayd3e
       |- comment(6) by bburd

This time bburd chimes in. We traverse up the tree as normal; however, this time we don't have unique users at every step of the way. We actually encounter jayd3e twice in this case. So this results in our algorithm sending him a notification for the comment_notification attached to comment(2) AND comment(5). This is why I came to call this method "Notify All the Things," because it ends up notifying you of every connection that a newly created comment has with your comments. A less annoying version of this can be created, by keeping a list of notified_authors, like so:

notified_authors = []
created = # newly created comment
current = created
while current.parent_comment:
    parent = current.parent_comment
    if current.comment_notification:
        # make the notification active
        # assume this doesn't allow repeat commenters
        comment_notification.commenters.append(created.author)
    else:
        comment_notification = CommentNotification(comment=current)
        comment_notification.commenters.append(created.author)

    if not created.author is current.author and not current.author in notified_authors:
        notified_authors.append(current.author)
        # notify user that someone has created a child of their comment
        db.add(Notification(user=current.author, comment_notification=comment_notification))
    current = parent

This way, only your closest relative of the newly created comment gets notified, but nothing else upstream. This still has problems, b/c although you are no longer getting notified about relatives upstream, the comment_notifications still exist for those comments and contain relavent information to understand the complete conversation. By finding the closest relative, we are only seeing the members of the conversation UNDER that comment, and we don't see all of the members of the conversation. To illustrate this, check out this series of events:

- comment(1) by danz

danz's notifications:
none

- comment(1) by danz
 |- comment(2) by jayd3e

danz's notifications:
* jayd3e commented on comment(1)

jayd3e's notifications:
none

- comment(1) by danz
 |- comment(2) by jayd3e
   |- comment(4) by timtim

danz's notifications:
* jayd3e and timtim commented on comment(1).

jayd3e's notifications:
* timtim commented on comment(2)

timtim's notifications:
none

- comment(1) by danz
 |- comment(2) by jayd3e
   |- comment(4) by timtim
     |- comment(5) by jayd3e

danz's notifications:
* jayd3e and timtim commented on comment(1).

jayd3e's notifications:
* timtim and you commented on comment(2)

timtim's notifications:
* jayd3e commented on comment(4)

- comment(1) by danz
 |- comment(2) by jayd3e
   |- comment(4) by timtim
     |- comment(5) by jayd3e
       |- comment(6) by bburd

danz's notifications:
* jayd3e and timtim commented on comment(1).

jayd3e's notifications:
* bburd commented on comment(5)
* timtim, bburd, and you commented on comment(2)

timtim's notifications:
* jayd3e commented on comment(4)

bburd's notifications:
none

Notice how in the last step, jayd3e get's multiple notifications. One for each of his two comments. Each of these two comments has a particular strength, one contains every member in the conversation, while the has a more specific link to the "tip" of the conversation. The user wants to be able to see everyone involved in the conversation through the the first notification, but they want to be able to click on the notification get taken to the "tip" of the dialogue, not all the way back to comment(2) where they entered it. This problem only becomes more complex when you have multiple deviations in the tree of comments, not fun.

###Reddity ####Description Reddit's methods of notification are far simpler than described above. Basically, you receive an individual notification(an "orangered") each time someone comments on a comment you have created. So the algorithm becomes greatly simplified, like so:

created = # newly created comment
current = created
parent = current.parent_comment
if parent.comment_notification:
    # make the notification active
    # assume this doesn't allow repeat commenters
    parent.comment_notification.commenters.append(created.author)
else:
    comment_notification = CommentNotification(comment=current)
    comment_notification.commenters.append(created.author)
    parent.comment_notification = comment_notification

if not created.author is parent.author:
    # notify user that someone has created a child of their comment
    db.add(Notification(user=current.author, comment_notification=parent.comment_notification))
current = parent

So basically, we check to see if the parent comment has already been commented on before. If it has, we just append our user to the list of commenters in the notification. If it hasn't been commented on in the past, then we create a new comment_notification, and then append our user to its list of commenters. Regardless of the case, we always have a notification sent out to our user, unless a user is commenting on his/her own comment.

####Behavior This method results if far more and less meaningful notifications getting sent out to each individual user. Let's move through a similar situation as our previous example:

- comment(1) by danz

danz's notifications:
none

- comment(1) by danz
 |- comment(2) by jayd3e

danz's notifications:
* jayd3e commented on comment(1)

jayd3e's notifications:
none

- comment(1) by danz
 |- comment(2) by jayd3e
   |- comment(4) by timtim

danz's notifications:
* jayd3e commented on comment(1).

jayd3e's notifications:
* timtim commented on comment(2)

timtim's notifications:
none

- comment(1) by danz
 |- comment(2) by jayd3e
   |- comment(4) by timtim
     |- comment(5) by jayd3e

danz's notifications:
* jayd3e commented on comment(1).

jayd3e's notifications:
* timtim commented on comment(2)

timtim's notifications:
* jayd3e commented on comment(4)

- comment(1) by danz
 |- comment(2) by jayd3e
   |- comment(4) by timtim
     |- comment(5) by jayd3e
       |- comment(6) by bburd

danz's notifications:
* jayd3e commented on comment(1).

jayd3e's notifications:
* bburd commented on comment(5)
* timtim commented on comment(2)

timtim's notifications:
* jayd3e commented on comment(4)

bburd's notifications:
none

- comment(1) by danz
 |- comment(2) by jayd3e
   |- comment(4) by timtim
     |- comment(5) by jayd3e
       |- comment(6) by bburd
   |- comment(7) by mlitzo

danz's notifications:
* jayd3e commented on comment(1).

jayd3e's notifications:
* mlitzo commented on comment(2)
* bburd commented on comment(5)
* timtim commented on comment(2)

timtim's notifications:
* jayd3e commented on comment(4)

bburd's notifications:
none

mlitzo's notifications:
none

So as you can see, jayd3e is carrying on two different conversations, which are related, but should be continued separately; however, he has received a total of three different notifications, for each time someone commented on his comment. This does effectively notify jayd3e of where someone just interacted with him, but it gives him no context as to the chain of events that happened before this interaction. Another important thing to note, is that each time another user commented on comment(2), there were two people who were notified of what was going on. The first being the person who wrote the comment, and the second being jayd3e, who received a notification about the comment. Everyone else who belongs to the conversation, and could potentially benefit from the information that they assisted in generating, is completely left out of this system. Which is the main problem I have with it.

What I Want

I want an algorithm that notifies people based on conversations. Which means it's intelligent enough to understand which dialogues are occurring between groups of people, and send out notifications specific to EACH conversation. To illustrate what needs to happen at each step of the way, I think it's best to just provide an example in a similar format to what I have been using above:

- comment(1) by danz

danz's notifications:
none

- comment(1) by danz
 |- comment(2) by jayd3e

danz's notifications:
* jayd3e commented on comment(1)

jayd3e's notifications:
none

- comment(1) by danz
 |- comment(2) by jayd3e
   |- comment(4) by timtim

danz's notifications:
* jayd3e and timtim commented on comment(1).

jayd3e's notifications:
* timtim commented on comment(2)

timtim's notifications:
none

- comment(1) by danz
 |- comment(2) by jayd3e
   |- comment(4) by timtim
     |- comment(5) by jayd3e

danz's notifications:
* jayd3e and timtim commented on comment(1).

jayd3e's notifications:
* timtim and you commented on comment(2)

timtim's notifications:
* jayd3e commented on comment(4)

- comment(1) by danz
 |- comment(2) by jayd3e
   |- comment(4) by timtim
     |- comment(5) by jayd3e
       |- comment(6) by bburd

danz's notifications:
* jayd3e, timtim, and bburd commented on comment(1).

jayd3e's notifications:
* bburd commented on comment(5)

timtim's notifications:
* jayd3e and bburd commented on comment(4)

bburd's notifications:
none

- comment(1) by danz
 |- comment(2) by jayd3e
   |- comment(4) by timtim
     |- comment(5) by jayd3e
       |- comment(6) by bburd
   |- comment(7) by mlitzo

danz's notifications:
* 4 people commented on comment(1).

jayd3e's notifications:
* mlitzo commented on comment(2).
* bburd commented on comment(5)

timtim's notifications:
* jayd3e and bburd commented on comment(4)

bburd's notifications:
none

mlitzo's notifications:
none

- comment(1) by danz
 |- comment(2) by jayd3e
   |- comment(4) by timtim
     |- comment(5) by jayd3e
       |- comment(6) by bburd
       |- comment(8) by adahlb
   |- comment(7) by mlitzo

danz's notifications:
* 5 people commented on comment(1).

jayd3e's notifications:
* bburd and adahlb commented on comment(5)
* mlitzo commented on comment(2).

timtim's notifications:
* jayd3e and bburd commented on comment(4)

bburd's notifications:
none

mlitzo's notifications:
none

adahlb's notifications:
none

- comment(1) by danz
 |- comment(2) by jayd3e
   |- comment(4) by timtim
     |- comment(5) by jayd3e
       |- comment(6) by bburd
       |- comment(8) by adahlb
   |- comment(7) by mlitzo
     |- comment(9) by adahlb

danz's notifications:
* 5 people commented on comment(1).

jayd3e's notifications:
* bburd and adahlb commented on comment(5)
* mlitzo and adahlb commented on comment(2).

timtim's notifications:
* jayd3e and bburd commented on comment(4)

bburd's notifications:
none

mlitzo's notifications:
* adahlb commented on comment(7)

adahlb's notifications:
none

- comment(1) by danz
 |- comment(2) by jayd3e
   |- comment(4) by timtim
     |- comment(5) by jayd3e
       |- comment(6) by bburd
       |- comment(8) by adahlb
   |- comment(7) by mlitzo
     |- comment(9) by adahlb
       |- comment(10) by jayd3e

danz's notifications:
* 5 people commented on comment(1).

jayd3e's notifications:
* mlitzo, adahlb, and you commented on comment(2).
* bburd and adahlb commented on comment(5)

timtim's notifications:
* jayd3e and bburd commented on comment(4)

bburd's notifications:
none

mlitzo's notifications:
* adahlb and jayd3e commented on comment(7)

adahlb's notifications:
* jayd3e commented on comment(10)

- comment(1) by danz
 |- comment(2) by jayd3e
   |- comment(4) by timtim
     |- comment(5) by jayd3e
       |- comment(6) by bburd
       |- comment(8) by adahlb
   |- comment(7) by mlitzo
     |- comment(9) by adahlb
       |- comment(10) by jayd3e
         |- comment(11) by bburd

danz's notifications:
* 5 people commented on comment(1).

jayd3e's notifications:
* bburd commented on comment(10).
* bburd and adahlb commented on comment(5)

timtim's notifications:
* jayd3e, bburd, and adahlb commented on comment(4)

bburd's notifications:
none

mlitzo's notifications:
* adahlb, jayd3e, and bburd commented on comment(7)

adahlb's notifications:
* jayd3e and bburd commented on comment(9)

- comment(1) by danz
 |- comment(2) by jayd3e
   |- comment(4) by timtim
     |- comment(5) by jayd3e
       |- comment(6) by bburd
       |- comment(8) by adahlb
   |- comment(7) by mlitzo
     |- comment(9) by adahlb
       |- comment(10) by jayd3e
         |- comment(11) by bburd
         |- comment(12) by timtim

danz's notifications:
* 5 people commented on comment(1).

jayd3e's notifications:
* bburd and timtim commented on comment(10).
* bburd and adahlb commented on comment(5)

timtim's notifications:
* jayd3e and bburd commented on comment(4)

bburd's notifications:
none

mlitzo's notifications:
* 4 people commented on comment(7)

adahlb's notifications:
* jayd3e, bburd, and timtim commented on comment(9)

- comment(1) by danz
 |- comment(2) by jayd3e
   |- comment(4) by timtim
     |- comment(5) by jayd3e
       |- comment(6) by bburd
       |- comment(8) by adahlb
   |- comment(7) by mlitzo
     |- comment(9) by adahlb
       |- comment(10) by jayd3e
         |- comment(11) by bburd
           |- comment(13) by danz
         |- comment(12) by timtim

danz's notifications:
* 5 people commented on comment(1).

jayd3e's notifications:
* bburd, timtim, and danz commented on comment(10).
* bburd and adahlb commented on comment(5)

timtim's notifications:
* jayd3e, bburd, and adahlb commented on comment(4)

bburd's notifications:
* danz commented on comment(11)

mlitzo's notifications:
* 5 people commented on comment(7)

adahlb's notifications:
* 4 people commented on comment(9)

The main thing that distinguishes this system from everything I've mentioned so far, is that it understands if a specific node has spawned multiple conversations, or is a part of a single conversation. If it has spawned multiple conversation, a single notification should get sent out that combines all of the conversations. If a specific node is only a member of a single conversation, then we only send out a notification for that one conversation. If it belongs to more than one conversation that originate from the same node, then separate notifications should be sent out for each conversation.

@erwinpratama
Copy link

Interesting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment