Background

Currently, each activity message is stored with an "owner" and a "viewer." The "owner" is the location the message appears -- typically a user or group stream -- and the "viewer" is the person who is allowed to see the message (typically the acting user, group members, the acting user's friends...).

However, the "owner" and "viewer" are static. That is, if someone posts a node into a group, then a message will be saved with each member of the group as the owner. If one of those group members then leaves the group, that group message will still be in their stream. Similarly, if a message is posted into a private group, only members of the group can see it. If someone then joins that private group, they won't see any of the past messages because there are no activity messages with that person as a viewer.

Additionally, the controls for what messages appear where and to whom are pretty confusing because of the conflation between "owners" and "viewers."

One result of the current situation is that there is no public, site-wide stream. See https://drupal.org/node/1255314#comment-4891814 for more about this.

So what we need is a new way to approach visibility -- a way that lets us determine when activity messages are viewed who can see them and where they appear instead of when activity messages are created. For this to work, we need this method to be part of a database query.

Approach

When you set up an activity message template, you will be able to say "I want messages generated from this template to appear in any of the following places:"

  • My stream
  • My followers' streams
  • The active groups' streams
  • Streams of members of the active groups
  • My profile
  • The global, site-wide stream

When you look at your stream, it dynamically pulls in these things:

  • Your activity designated to show up in "my stream"
  • The activity of people you follow designated to show up in "my followers' streams"
  • Activity in your groups designated to show up in "streams of members of the active groups"
  • Statuses you are @mentioned in
  • Status messages to you

When you look at your profile, you only see messages designated to show up in "my profile." Similarly, when you look at a group, you only see messages designated to show up in that group; and when you look at the global, site-wide stream, you only see messages designated to show up there.

By "dynamically pulls in these things" I mean that for example the list of groups you're in at that current time is pulled in, instead of statically storing that you're in a certain group at the time a message is saved.

By doing this, we lose some flexibility, but I think that it's flexibility that hardly anyone would ever use. (If you're someone who would use it, please speak up!) And on the other hand, I think we gain quite a lot by making this last static part of activity streams dynamic. It's much simpler, easier to understand, targeted at the common use cases, and delivers fewer surprises.

Implementation

In practice, you will be able to choose where an activity message appears in the template, and what kinds of messages show up in "my stream" in a View filter or argument. Several different Views may be required now that we'll have "streams," "profiles"/"groups," and "global" as separate displays. activity_log.entity_groups.inc will have to be re-thought-out. The database schema will need to change -- we'll need to drop the stream_owner_type and stream_owner_id and viewer columns, and probably add a new table for additional data like the active groups, status recipients/@mentions, etc.

Unfortunately, we don't have any node access wins this way -- we're still stuck with the method of reducing Views results after they've been generated.

Comments are welcome -- feel free to let me know what you think about this plan, what your specific requirements are, or what you think typical requirements might be. Contributions are also welcome -- feel free to dive in and start creating patches for any of this if you feel able.

Comments

pribeh’s picture

Dynamic is definitely the way to go for relationships. Twitter and Facebook do this. I just discovered though that GooglePlus might not do this - unless caching is affecting what I see in my stream.

I guess my UX/User-Experience argument for going dynamic would be that most users expect to no longer see activity from a specific user/group within their stream if they choose to axe that relationship. When a user axes a relationship on a social network it's usually due to some annoyance (spam-like behaviour or diverging interests). I actually expect Google staff to get complaints from users about said static-relationship model's symptoms (residue).

Obviously, I'm not much of a help technically here myself, but I am going to add a comment and question. First, although I found the stream_owner_type and viewer columns intuitive myself, some others were confused by it. Employing different views for the different display types (via a filter or argument) might be more user intuitive to some site builders. As for my question, is having to use Views' filters (reducing views results) more taxing then employing node access or just more work? Meaning, why is it a disadvantage?

InTheLyonsDen’s picture

Ice,

As I was expressing to Pribeh after reading this post, this seems like an approach that will be much more straight forward to apply in practice.

Central dynamic control of streams is key. It would also be nice to easily have an 'infinite' templates of streams using arguments and filters views to be able to tie to. Per content type control would be great too.

I will gladly test and comment through the development cycle. I have a live project that I would love to use Activity Log in. What is your time table of the re-architect effort?

Thanks!

icecreamyou’s picture

@pribeh:

Employing different views for the different display types (via a filter or argument) might be more user intuitive to some site builders.

There will need to be 3 different Views (or at least 3 different displays) under the proposed model: stream, profile, and global.

Is having to use Views' filters (reducing views results) more taxing then employing node access or just more work? Meaning, why is it a disadvantage?

They're not comparable. The Views filters needed for the proposed model would identify messages intended for you, whereas node access checks whether you are allowed to see a certain message. For example, if you created a node in a group, an activity message would be created that is intended to be shown to all group members -- but node access would still let anyone with appropriate permissions view that message even if it wouldn't make any sense to them. Node access will be the final layer of filtering; that is, once we've used Views filters to identify activity messages intended for the current user, then we'll use node access to filter out messages about restricted nodes from the Views result set. That's what I meant about not having any node access wins here -- because we still have to do the node access filtering after we've retrieved the Views results, we'll end up with fewer results per page than expected if any results have to be filtered out.

@InTheLyonsDen:

It would also be nice to easily have an 'infinite' templates of streams using arguments and filters views to be able to tie to. Per content type control would be great too.

I am not sure what you mean by this.

What is your time table of the re-architect effort?

I'm going back to school at the end of the week, and I want to focus on getting FBSS fully ported to D7 before then (and that probably won't be completed before I go back to school either). I don't know yet how much time I'll have for development work once I go back to school, but I'm expecting to be pretty busy. So I really can't say right now. (We're talking weeks to months... I would be surprised if this hasn't been resolved by Thanksgiving.)

ezra-g’s picture

When configuring rules to log activity, we have actions such as

Log activity for the acting user
Log activity for the acting user's relationships
Log activity for the joined group

Since we'll be determining where messages show up at view time rather than at render time, will these actions still make sense, or should they be removed?

icecreamyou’s picture

I think mostly they will still make sense. The way the visibility is assigned will change, but you'll still want different templates for different streams.

icecreamyou’s picture

Status: Active » Postponed

Postponing this for the D7 port.

The real challenge here is figuring out a way to store data that allows Views to retrieve the required data from the database in a single query without additional processing in PHP, while meeting all of the requirements of this module (dynamic data, supporting grouped messages, node access controls, context awareness, etc.). Here's my current thinking: we'll need two database tables, {activity_log_access} and {activity_log_messages}. For a non-grouped activity record, a new message will be inserted into {activity_log_messages} when the relevant Rule is triggered, along with an access control record in {activity_log_access}. If the action isn't about an entity that requires access control, the record will just be a placeholder (I think this is necessary but I'm not sure). For grouped messages, an existing record in {activity_log_message} would be updated instead, and a new access control record added to {activity_log_access} if necessary (i.e. if the action is about an entity that requires access control). The schemas would be as follows:

access: mid | entity_id | entity_type
messages: mid | timestamp | template_id | acting_uid | rules_states | context_type | context_id | group_on

The "access" table basically just holds references to entities related to the relevant message. The "messages" table fields require some explanation:

  • The timestamp is usually the time the message was created but can be updated when new activity is added to a grouped record or sometimes when someone comments on or otherwise interacts with that activity.
  • The template_id is a foreign key to the activity message template table (not discussed here since it won't change much, but it's important to note that there will be a new "stream_type" column in it that implies the existing stream owner type, stream owner ID, and viewer fields).
  • The acting_uid is the user ID of the person who executed the action.
  • The rules_states is a serialized array of the Rules states needed to evaluate the tokens. If the message is ungrouped, there will only be one item in the array; if it's grouped, there can be many.
  • The context_type and context_id will typically identify a group. They're used to provide additional information to SQL queries.
  • The group_on field is a string that allows messages to be grouped on arbitrary values. Grouping will of course be based on the template ID and action recency, but having an arbitrary grouping column will provide the flexibility to allow e.g. node comments to be grouped on their parent node as well as the current possibilities of grouping by target entity or acting user (or both).

Ultimately we want to end up with a query something like this (I'm doing this from memory, I may have gotten the OG and UR tables wrong):

SELECT m.*, t.*
FROM {activity_log_access} a
LEFT JOIN {activity_log_messages} m
  ON a.mid = m.mid
LEFT JOIN {activity_log_templates} t
  ON m.template_id = t.template_id
WHERE
  (t.stream_type = 'user' AND
    m.acting_uid = :current_user_id) OR /* content you've created, actions you've taken, etc. */
  (t.stream_type = 'user' AND
    a.entity_type = 'user' AND
    a.entity_id = :current_user_id) OR /* actions other people have taken on you, like sending you a message */
  (t.stream_type = 'group_members' AND
    m.context_type = 'og' AND
    m.context_id IN
      (SELECT nid FROM {og_uid} WHERE uid = :current_user_id)) OR /* your groups' activity */
  (t.stream_type = 'followers' AND
    :current_user_id IN (
      (SELECT requestee_id FROM {user_relationships} WHERE {requester_id} = :current_user_id) OR
      (SELECT requester_id FROM {user_relationships} WHERE {requestee_id} = :current_user_id)
    )) /* followed users' activity */
GROUP BY m.mid
ORDER BY m.timestamp DESC, m.mid DESC

We can run access control on this query because we have access-controlled entities clearly separated; and even though we're running access control at view time, we can still group messages when they are created because grouped messages will only appear in a single context (since they're tied to a single template). We are able to pull in all the data we need in a single query, and have the flexibility to do fun things like group messages in ways we couldn't before. And the system is now fully dynamic since viewers and associated permissions are evaluated at view time instead of when the action occurs. I'm sure there will be a performance cost to having queries like this, but given the right indexes I don't think it will be too bad.

As you can probably tell if you've followed me up to this point, there will need to be some sort of mechanism for defining new stream types; and we will probably need to have different Views for different contexts.

So figuring all this out was probably half the battle. The other half is dealing with Rules' entirely new architecture for D7 and figuring out again how to thread data through the needles that Rules tries really hard to make as small as possible. But I'll leave that for another time. Meanwhile the other major task is probably figuring out how to simplify the "Log activity" action. But since this architecture simplifies the visibility settings by design, we're already much of the way there on that as well.

zinbawe’s picture

I am quite new in drupal but I am really interested in solving this issue because I need that the new users of a group can see in the activity stream the old events as well. Does anyone make it possible? If it is yes can you explain me how you make it and where I have to modify/add the code?