Archive for April, 2009

28 Apr 09

WordPress to MindTouch plug-in

WordPress is a fantastic open-source blogging application, and I’m excited to be able to release a WordPress to MindTouch connector plugin, which allows your WordPress posts to synchronize with any MindTouch.

What does this synchronization mean? It means that any published post you make in WordPress automatically copies its contents to MindTouch as a new page. Any subsequent edits and deletions made in WordPress also update the MindTouch page, so MindTouch’s content will always remain up to date with your WordPress blog.

Subsequent updates to your blog entries will automatically version inside MindTouch, so you can easily track the evolution of your blog entry through your various rewrites.

With this plugin, federation of multiple WordPress blogs into a single MindTouch becomes possible. MindTouch becomes the connector for your separate WordPress installs; you can easily search across all the contents of your WordPress blogs inside MindTouch and see the tag associations across all entries. Even better – subscribe once to your MindTouch, and watch the RSS updates come in from multiple sources! And with our full API, you could easily pull this data and throw it somewhere else!

This plugin is a great example of how easy it is to access MindTouch’s API to publish and retrieve content in PHP. Using the included dream.php library makes it very easy to make REST-based calls. For example, here is the call to create a new page inside MindTouch:

$Result = $Plug->At('pages', '=' . urlencode(urlencode($pagepath)), 'contents')
	->With('abort', 'exists' )
	->With('title', $post->post_title)
	->Post($pagecontents);

Although this plugin is WordPress specific, there’s no reason other systems like Drupal couldn’t also benefit from this type of functionality :) So if you’re willing to give it a shot, drop by our forums or IRC (#mindtouch on FreeNode).

Download the WordPress plugin (READ the README!).

28 Apr 09

A User Timeline: Publishing events for retrieval by DekiScript

As promised previously, this article will go over building an extension to MindTouch using the Event Bus. This article is primarily an overview; for the detailed walkthrough of the code check out the tutorial. The service described is a sample showing how the Event Bus could be used in a real world scenario, however while the code dealing the the Event Bus is production ready code, there a number of additional features the timeline should have to real use. I will cover the possible improvements to the sample that would make the service a production feature.

What is a User Timeline
The purpose of this service is to capture all comments made by a user or about a user as a timeline of their activity. Borrowing some twitterism, we can use the convention of a user name preceded with an @ to let our users easily mention other users in their comments. And since comments are always associated with a page, the timeline automatically captures the context in which the user’s comments were made. An example of this would be telling another user about a page in the Wiki, by simply saying “Hey @bob, check out this page” in the comments of that page.

The service should provide the following functionality:

  • Retrieve the timeline by username
  • Track @replies to the user in their timeline
  • Provide DekiScript for accessing the timeline with max entries and start offset

Implementation Approach 1: Directly integrate into the existing code

One way that this service could be built is by modifying the main MindTouch service itself, creating a new set of database calls that scour the database for all comments eligible for the timeline, and adding new API calls to expose this data. This approach has multiple drawbacks:

  • Having to be familiar with the internal workings of MindTouch and its Database schema
  • Introducing a maintenance burden to merge code changes when the next release comes out
  • If used heavily, the timeline DB queries will become too expensive

Implementation Approach 2: Feed off the Event Bus

Fortunately, all comment creation, editing and deletion generates events that we can subscribe to, allowing us to receive the data in real time and store it ourselves for efficient, per user retrieval.

This article gives you an overview of a service for the event driven approach, for more detail read the “Creating a Comment Timeline using the Deki Event Bus” tutorial. The service described has the following responsibilities:

  • Set up and tear down subscriptions on Start/Stop
  • Capture events, process them for @replies and retain them as per user timelines
  • Expose a DekiScript function to retrieve a user’s timeline

The channels we are interested in are event://{wikiid}/deki/comments/create and event://{wikiid}/deki/comments/update. A subscription endpoint is any DreamService feature that accepts a POST. The subscription document that we submit in the service’s Start() coroutine looks like this (using the XDoc class):

XDoc subscription = new XDoc("subscription-set")
  .Elem("uri.owner", Self.Uri.AsServerUri().ToString())
  .Start("subscription")
    .Elem("channel", string.Format("event://{0}/deki/comments/create", _wikiId))
    .Elem("channel", string.Format("event://{0}/deki/comments/update", _wikiId))
    .Add(DreamCookie.NewSetCookie("service-key", InternalAccessKey, Self.Uri)
                    .AsSetCookieDocument)
    .Start("recipient")
      .Attr("authtoken", _apikey)
      .Elem("uri", Self.Uri.AsServerUri().At("notify").ToString())
    .End()
  .End();

With the subscription set up, the service will receive all comment events, leaving us to simply process the incoming data and store it. The format for a comment event looks like this (a complete list ofevents published by Deki can be found here):

<deki-event wikiid="..." event-time="...">
  <channel>...</channel>
  <uri>...</uri>
  <pageid>...</pageid>
  <uri.page>...</uri.page>
  <user id="..." anonymous="...">
    <uri>...</uri>
  </user>
  <path>...</path>
  <!-- content/@type and value are only included if smaller than 255 chars -->
  <content uri="..." type="...">...</content>
</deki-event>

Since we want to expose the users (sender and recipient in case of @reply) and page related to a comment, we need to call back into the Deki API to retrieve the appropriate API documents in order to create the timeline event. For the sender and the page, we are provided uri’s that we can query. However, for @reply users, we only have the username, so we have to construct a Uri into the Deki API to query by username. All of these requests are done using the Plug class and require the apikey, in case any of the resources we are trying to access are restricted.

For simplicity the user retrieval is factored into a separate coroutine GetUserInfo, which can retrieve the user document based on either Uri or username. Using a coroutine (i.e. invoking it via Coroutine.Invoke() instead of straight method invocation) allows us to use yield to asychronously request the user data rather than blocking the current thread, as regular methods have to. For more information on Coroutines and the Yield pattern check out my previous article “Dream Asynchronicity Coroutines“.

Finally we expose a DekiScript function for retrieving the timeline:

messagestream.timeline(username : str)

The function requires a username. It returns a list of timeline event maps. We chose DekiScript data as the return format instead of formatted html, because it gives us a lot more flexibility in using the timeline. Instead of having to change the service to filter the list, or change the Html generated, we can use DekiScript templates to keep the logic editable directly via the Wiki interface.

Improving the Timeline

As stated at the beginning, the subscription and event processing of this sample is fully implemented, but the service lacks a number of additional features to make it useful in production. The main areas that need improvement are persistence and performance.

The sample punts on persistence and keeps the timeline in memory, which is both undesirable for scalability and does not survive a service restart. Instead of keeping the events in a dictionary, the service should serialize each event to disk or into a database and retrieve the events when messagestream.timeline is called.

From a performance perspective, the repeated calls back to the Deki API and the requirement for the user of the DekiScript function to fetch the user and page data would likely create a lot of undesirable work for your Wiki. Both could easily be addressed by fetching the data on demand and keeping the data in a memory cache for a short time period, so that repeated access to the same timeline would not require rebuilding all the information every time.

While the sample shouldn’t be put into production, we hope that the ideas and  code provided will serve as a good base for building Event Bus driven extensions.

24 Apr 09

MindTouch SSO: auto-login from your existing app

Authentication nirvana
Authentication Nirvana

One of the first concerns admins have when considering or deploying MindTouch to an intranet is how everyone will log in. It’s essential that the bar is set as low as possible for your users to get started by telling your users that their existing credentials for another application will already work with MindTouch. Better yet, having them be logged in automatically into the wiki will lower the bar to making contributions.

This is the first in a series of posts to detail by example the various approaches you can take to integrate MindTouch into your environment in regards to authentication. This post focuses on allowing your existing intranet portal site or another application where users log in manually to automatically log them into your MindTouch installation.

First a little background about how MindTouch keeps users logged in. Like many apps, it uses a simple cookie to determine who the current user is. This cookie is issued by the API (POST: users/authenticate) in response to valid credentials (for local users or to an external authentication module) and is passed back through the UI out to the browser where it’s saved. All subsequent requests to the UI and to the API have this cookie passed and requests are processed under the context of the user described in the cookie. Since the UI here simply acts as a middleman to the API, having another actor such as your portal site or 3rd party app perform the same authentication request becomes simple.

This single sign on technique requires that you

  • Have access to modify source code or hook into your existing app with which you’d like to integrate your authentication
  • Your MindTouch installation is hosted in a subdomain of your existing app. For example your portal is: http://intranet.company.com then your install must be at or below http://wiki.intranet.company.com
  • Enable trusted authentication in your MindTouch installation

This technique should generally be used if you

  • Want your users that are logged into an application you have to automatically be signed into your MindTouch instance
  • Don’t have an LDAP directory or the users you want to be able to login do not exist in the directory
  • Have many external or non Windows users making integrated windows auth / NTLM / Kerberos auth impossible

As described above, this requires an http call from your existing app to the API’s POST:users/authenticate feature in your app’s sign in logic before it returns to the user.  In this request, the username should be set and an empty password provided using the standard Authorization header. Two query parameters are needed: apikey={your apikey} and authprovider=1. By providing the apikey you’re saying that you want the user created if it doesn’t exist or to authenticate without providing a password. The authprovider query parameter tells MindTouch which authentication provider to use. 1 is built in authentication and no calls to external providers are done. If a different authprovider id is specified, an external user lookup and group sync will be performed but the request will fail if the user doesn’t exist externally. You’ll need to configure your MindTouch install to ensure trusted authentication is enabled by setting “security/allow-trusted-auth” to “true” in control panel -> configuration.

Some example code using built in C# methods:

public static string BuildMindTouchAuthToken(string username) {
    string authtoken = null;

    //These settings should be looked up from configuration
    string dekiApiUri = "http://wiki.intranet.mycompany.com/@api/deki";
    string apikey = "somekey";
    string authProviderId = "1"; 

    string authRequestUri = string.Format("{0}/users/authenticate?authprovider={1}&APIKEY={2}", dekiApiUri.TrimEnd('/'), authProviderId, APIKEY);

    //Perform HTTP request to MindTouch's POST: users/authenticate
    HttpWebRequest authRequest = HttpWebRequest.Create(authRequestUri) as HttpWebRequest;
    authRequest.Credentials = new NetworkCredential(username, string.Empty);
    authRequest.Method = "POST";
    authRequest.PreAuthenticate = true;
    authRequest.ContentLength = 0;
    authRequest.CookieContainer = new CookieContainer();
    HttpWebResponse authResponse = authRequest.GetResponse() as HttpWebResponse;
    if(authResponse.StatusCode == HttpStatusCode.OK) {
        authtoken = authResponse.Cookies["authtoken"].Value;
    }

    return authtoken;
}

This will return an auth token for the provided user that you will then need to return back to the browser with a wildcard subdomain. Here’s ASP.NET code:

    string authtoken = BuildMindTouchAuthToken(username);
    if(!string.IsNullOrEmpty(authtoken)) {
        HttpCookie authTokenCookie = new HttpCookie("authtoken", authtoken);
        authTokenCookie.Domain = ".wiki.intranet.mycompany.com";
        authTokenCookie.Expires = DateTime.UtcNow.AddDays(7); //Set to same as MindTouch's security/cookie-expire-secs. (7 days by default)
        Response.SetCookie(authTokenCookie);
    }

Once this cookie is returned back to the browser, every user that logs into your application also gets automatically logged in to your MindTouch install. I’ll be creating a section describing this and other authentication techniques in the dev wiki. Meanwhile, if you have any questions or ideas about this don’t hesitate to leave a note! Otherwise reach me at the usual. Happy integrating!

irc://irc.freenode.net/#mindtouch

twitter: maxmass

23 Apr 09

MindTouch Lyons Postmortem

2972523219_436cd77d25_o
Photo by yomostro

We’ve completed our internal development postmortem for the Lyons-family of MindTouch releases (9.02 for open-source; “MindTouch 2009″ for commercial customers).

Postmortems are important to our engineering team because they are an open forum for discussing the faults during each release cycle. These problems may not even directly relate to software regressions or bugs, but the processes which support our engineers that help us push out each release. I take notes during these meetings, find the common threads of suggested improvements from our team, and make sure they are written down so they can be reviewable at any time by any member of the team – this helps the team collectively learn what it takes to ship software in an effective manner.

We follow-up with these postmortem with actionable items – processes that help address the issues. Sometimes these processes are later removed (maybe the problem was specific just to that release) – but many of our organic processes are a direct result of these postmortems, and they play a huge role in our ability to continue shipping more complex releases on more platforms with less regressions with the same team!

For those of you who are curious how these post-mortem meetings work, we begin by first reviewing the issues raised in the previous postmortem, then move onto the issues raised in the current release.

From the Kilen Woods release, we had 10 items to improve:

  1. Improve testing procedure
  2. Be wary of writeable resources
  3. Communicating open-source versus commercial
  4. Shipping delay
  5. SVN syncing to SF.net
  6. Localization
  7. Binaries from buildbot
  8. Blogging
  9. Keeping installation documentation current
  10. Changing our release strategy

I devised a tongue-in-cheek rating system on the postmortem page for rating our success . Our overall success rate for that release was acceptable – we achieved five of them outright, four of them were neutral, and we only completely failed on one (“Keeping installation documentation current”).

For Lyons, we identified four problems that we will fix for the next major release:

  1. Release Shipping Delays
  2. Specifications
  3. Productization
  4. Disconnect between teams

You can get a summary of these issues and how we’re addressing them in future releases on the postmortem page.

And now for the best part: How were the shortcomings in this release that affected YOU? It’s important for us to also hear from the community on how we can improve the experience around MindTouch – not specifically from a product standpoint, but in other aspects as well (community? better  communication?)

22 Apr 09

Tag Clouds & Directories with DekiScript

Tags are a great way to hyperlink content automatically. However, tags are not just a way to relate content, they can also be used to visualize frequency and content organization. Two templates specifically designed to take advantage of the DekiScript innovations in MindTouch 2009 are Template:TagCloud and Template:TagDirectory. These templates not only address some of the most common use cases for tags, but are also great examples of how DekiScript is used to create dynamic, interactive content.

Template:TagCloud

Template:TagCloud was originally created by the forum member iktest and refined by several others. Template:TagCloud creates a tag cloud based on a search query, which means you can visualize clouds for virtually any part of your wiki. The template is parameterized and allows for customization of the number of tags shown, showing or hiding the number of related pages, order of tags, coloring, and much more.

Tag Cloud Demo

Check out how easy it is to use this template in the live demo and the how it’s done page.

Template:TagDirectory

Another great use of tags is to organize content into categories. This is a common use case for FAQ sections (Frequently Asked Questions). The Template:TagDirectory creates a two-column layout of pages organized by tag. Categories (i.e. tags) are listed in alphabetical order and laid out in such a way that the left and right columns have a minimal difference in height. Within each category, pages are listed from most popular to least. All of this is done automatically and very simply.

Tag Directory

Lastly, importing the templates to your own wiki needs just one word of caution. Make sure to save them as Template:TagCloud and Template:TagDirectory without the trailing { … } and ( … ) respectively. Please join the discussion on the forums with your feedback and comments.

Copyright © 2011 MindTouch, Inc. Powered by