I can Publish a Model's Instances

(User Story, Closed -> Can't Repro, Priority: Critical, Test Status: No automated tests yet , Reported By Justin du Coeur, )
Summary: This is the controlling Epic for the new Publication Feature.
We should spin off specific Stories as we go, but this is the initial braindump, adapted from email to Eric on 4/7/17.

Design Notes

Insofar as possible, all of this will live in a new querki.publication package.

New Permissions

We need several new Permissions:
  • Can Create / Read / Edit pre-Publication Instances (default: Editors)
  • Can Publish (default: Editors)
  • Can Read post-Publication Instances (default: Public)
In principle, the Create / Read / Edit should be separate, but that may well be pointless complication at the UX level without sufficient benefit.

PublicationActor

The PublicationActor is a new member of the SpaceRouter troupe. (It is probably undesireable to divide this into Actor-per-Model, and almost certainly overkill.) It is in charge of the Publication history, and is a separate Persistent Actor. Note that PublicationEvents live here, not in the main Space stream.
Its State is the complete Publication History -- this isn't cheap, but shouldn't be too expensive in practice. This includes the state of each Published Instance at the time of Publication. Yes, this is all horribly denormalized, but in practice ought to work well and efficiently.
Publish commands should all be security-checked by this Actor. Only people with the "Can Publish" permission should be about to do it.
To avoid possible race conditions and security leaks between the UserSpaceSession, the Space and the Publication Actors, the Publish commands should probably include the filtered SpaceState view that this command is acting upon.
When a Publication Command is received by the PublicationActor, it renders the Default View as of that time, and bakes that into the History, so that we can get back to it.
We want to be able to access the PublicationActor cheaply, without waking up the rest of the troupe. This probably implies that all the members of the troupe should become lazy vals, instead of straight vals as they are now. Is there any downside to this, aside from maybe slowing down initial access? We may need to examine the startup protocols for, eg, Conversations and Sessions -- those probably require Space to be kicked to life before they can fully initialize. But PublicationActor should not require anything else to be awake.

Database

PublicationEvents are edge-trigger, and is stored as such in the PublicationActor. It is not yet clear whether this wants to be a single Event type, or different ones for Publish and Update; I suspect the latter. This event may contain:
  • The Thing(s) being Published/Updated. This should be the actual serialized state of the Thing(s), as a DHThingState.
  • The View(s) of the Thing(s), pre-baked with the SpaceState as of the moment of Publication.
  • Whether this is a Minor Update.
  • The Summary and/or Details of this event, if any.
  • Timestamp and who did it, as usual.
Publishing adds one of these events to the stream.

RSS

RSS URLs should go to a separate Controller, optimized for this. That Controller sends a message directly to the SpaceRouter, and thence to the PublicationActor.
When the PublicationActor receives an RSS request, it parses the parameters, if any (initially, we won't bother with any, but the concept should exist in the pipeline). It goes through the PublicationHistory, back some limited time, fetching the ThingState and pre-baked View, and uses that to assemble the RSS XML, which it then returns.
We might eventually pre-bake the XML and store that as part of the State, but think about whether that's worth it -- I suspect not. A compromise might be to treat that purely as an in-memory optimization: when we bake the XML for particular parameters, we cache that in the Actor and re-use it if the same query comes in again before timeout. But storing the baked XML for every combination, at every event, seems way too profligate for something that has no historical value.
A possibly better option, if I can make the logistics work: we publish the pre-baked RSS for the standard (no parameters, which is what we'll be doing first and is probably the 90% case) XML directly to S3, replacing the previous version with each update, and point requests at that. We might only begin doing this after someone first asks to see the RSS feed, so that we don't waste the bytes if they're never being used. This can probably be handled as a later optimization, though.
Actually, no: this needs to be done upfront. The problem is that we time out the troupe for Space. So if an RSS reader is pinging the feed every five minutes, the troupe will never time out -- it'll just live in memory permanently. So sending the RSS off to S3 looks like the right idea, and we should do it from the start.
Eventually, we will allow you to specify a separate RSS View, to be used instead of Default View.

Recent Changes

The Recent Changes page is based on an underlying _getChanges() function, as originally described in I should be able to view Recent Changes in this Space, which sends a request to the PublicationActor. Internally, it probably gets back a bunch of PublicationEvents, which it then turns into proper Querki Things and passes down the chain.
The Recent Changes page's content is simply displaying the Recent Changes View on the Space itself. This has a standard default, but can be customized as desired. The page has a standard URL, _recentChanges, with optional parameters, so that you can link to it.

Editor UI

The following changes need to happen in the Editor UIs. Initially, they may happen only in the Advanced Editor (so that we can get this up and running), in which case the Instance Editor should kick automatically over to the Advanced Editor if it's editing something Publishable. But in the long run, the Instance Editor should be made to work this way as well.
The FullEditInfo structure from the server to client should contain the Publishable flag. (We might replace the current "derivingName" flag with a property bag.) The Advanced Editor alters a bit if this flag is set:
  • A message is displayed (probably at the top) that you are editing an Unpublished Instance.
  • The "Done" button is replaced with "Finish and Publish", and "Done for now, but don't Publish". If the user doesn't have the Can Publish permission, "Finish and Publish" is disabled with a tooltip to that effect.
If the Instance has already been Published:
  • A message is displayed to that effect.
  • The buttons at the bottom are replaced with "Finish and Publish Update" and "Done for now, but don't Publish".
  • There is an optional Property named "Always Update when Published Instances Are Edited" flag that you can put on the Model; if set, this suppresses the "don't Publish" button, so the button at the bottom always Updates.
  • A "Minor Changes" checkbox is displayed near the buttons at the bottom; it is taken into effect when you press "Finish and Publish Update".
The "Always Update" flag is designed to force things to "fail positive" -- to make it difficult to change Published Instances and forget to Update. It is appropriate for living-information Spaces like Eric's FAQ, where it should be an explicit decision to hide changes. (It is the replacement for the "Save" concept outlined in the original I should be able to view Recent Changes in this Space -- I believe it achieves much the same effect, while preserving Querki's live-saving ethos.)

What it Looks Like

The high concept here is that you can designate a Space as containing Publications. This may always be available, or it might be a Feature you have to turn on explicitly. If you do so, it may contain one or more Models that have a new concept of "Publish". Publishing an Instance:
  • Changes the read access to the Instance.
  • Exposes the Instance on the RSS feed.
  • Eventually, this will publish the Instance on connected FB / TW / etc accounts.
Editors can see a list of new Instances of this Model that have not yet been Published.
The button to Publish a Thing will eventually be available in at least three places:
  • If the Model is Publishable, the Editor will replace the traditional "Done" button with "Finish and Publish", and "Done for now, but don't Publish".
  • There will be a menu pick to Publish.
  • From the list of Unpublished Instances, it can be Published directly.
The Publishable flag might be an ordinary Property to start, but should probably become something more special in the Editor UI, likely behind the standard RSS icon.
By default, a Publishable Model's Instances start as visible to Editors; when I Publish, they become Public. In the medium term, both of these should be formal Permissions, which I can control as usual on the Security page for the Model.
I can access RSS feeds:
  • For all Publishable Models in this Space
  • For a specific Publishable Model
  • Filtered by a specified Tag (should eventually be able to specify multiple Tags, and control which Property we're looking in?)
If a Model is not marked as Publishable, then I should get an if I try to fetch an RSS feed for it.
I (anyone) should be able to easily get the RSS URL for any Publishable Model.
There is also a concept of formally "Updating" a Published Instance. For the moment, changes to Published Instances are simply live, but you can:
  • Get a list of the changed but un-Updated Instances.
  • Update a Published Instance, which marks it explicitly; this sets the state back to Published.
  • Publish a "minor" Update, which will not usually show up in changelogs; this sets the state back to Published.
The implication is that Instances of a Publishable Model are essentially a state machine, with states:
  • Pre-publication
  • Published
  • Changed but not yet republished.
All Publish events, even minor Updates, may have an optional Summary and Details associated with them. This is a comment about this particular Publication.
Eventually, it should be possible to Publish / Update multiple Things at once, with a unified
Everyone (Public) should be able to see a standard Recent Changes page, which is a standard reverse-chrono list of changes.
  • By default, this does not include minor Updates. (Eventually, there should be a flag allowing me to see them.)
  • By default, this should show just the past N days. (1 month?) I should be able to "scroll" backwards for older events, or input a date range, or something like that.
  • The Space owner should be able to customize this view in a number of ways -- the page should, if possible, simply be a standard-but-customizable QL/QText expression.
  • There should be a standard button to get to this view. Spaces that have customized their headers should be able to replicate this button, if desired. They should also be able to link to this page as a normal link. (That is, it has an ordinary URL.)
  • It should be possible to get a version of this page that is filtered for specific Models, date ranges, etc. (So the parameters should be URL-specifiable.)
  • This page should show the Summary of each Publication event, if available, and the Details if you click on it. (Or something like that.)
  • For Update events, I should be able to see a text-diff highlighting the changes. This should be done with classed spans / divs so that Spaces can customize the style.
In the medium term, if I am a Querki User, I should be able to sign up for direct Notifications on a Publishable Model or Space, and get notified when new items are Published / Updated.