Record entity history¶
Journals are the comments and changes of an entity. They are stored in the journals table, with a journal_details record for each changed attribute. Easy8 shows them as the history on the detail page of an entity.
The history shows EasySetting.value("easy_extensions_journal_history_limit") journals, 10 by default. A "Show all" button at the bottom loads the rest.
Model¶
Add acts_as_easy_journalized to the model. It adds the journals association and the methods that store changes. The options are documented in ActsAsEasyJournalized#acts_as_easy_journalized.
| Example of acts_as_easy_journalized usage | |
|---|---|
To journalize attribute changes, call init_journal before you set the attributes. save then creates the JournalDetail records.
Formatting of journal details¶
EasyJournalHelper#show_easy_journal_detail renders each JournalDetail as "Label changed from old to new" by default. Columns listed in the format_detail_* options of acts_as_easy_journalized get a dedicated format:
| Option | Rendering |
|---|---|
format_detail_date_columns |
values formatted with format_date |
format_detail_time_columns |
values formatted with format_time |
format_detail_reflection_columns |
*_id columns resolved to the name of the associated record |
format_detail_boolean_columns |
0/1 rendered as Yes/No |
format_detail_hours_columns |
values formatted with easy_format_hours |
format_detail_enum_columns |
values resolved through human_enum_name |
format_detail_diff_columns |
long texts rendered as "Label updated (diff)" with a link to the word diff |
format_detail_hash_diff_columns |
json/hash columns rendered as a git-like key diff, see below |
Hash and array values of columns listed in format_detail_hash_diff_columns are stored in journal_details as JSON (Journal#add_attribute_detail). Hash details render only the added, removed and changed keys:
| Example of hash diff column | |
|---|---|
Non-string values and multiline strings (including keys) are serialised as JSON. Array details fall back to the default "changed from/to" rendering.
Controller¶
Call init_journal in the update action. Show the history from a separate journals action that renders the journals through AJAX.
| Example of update action | |
|---|---|
Include the EasyControllersConcerns::EasyJournals concern in your controller and define the journals method as below.
| Example of journals action for render history | |
|---|---|
Users permissions¶
Add the journals action to the view_ permission of your plugin:
| Example of permissions | |
|---|---|
Routes¶
Define a route for the journals action. For example:
Views¶
Async loading using tabs¶
Render the tabs partial:
<div id="entity-detail-bottom">
<%= render partial: "common/entity_tabs", locals: { tabs: my_model_tabs(@my_model), tabs_container: "entity-detail-bottom" } %>
</div>
Define a helper method that returns the array of tabs, with a trigger that loads the history through AJAX:
| Example of helper method for tabs | |
|---|---|
Pagination¶
EasyExtensions::JournalsTabsManager accepts page: and per_page:. With per_page, journals are sliced from the newest one and the history shows a "Load more" link instead of "Show all". JavaScript (EASY.utils.easyJournalLoadMoreHistory) fetches the next page and inserts older journals above the list, or below it when the user prefers comments in reverse order.
| Example of paginated journals action | |
|---|---|
Subclass the manager and override journal_scope to change which journals a tab shows. Pass the subclass to render_easy_journals(entity, tab_manager_class: MyTabsManager).
User reactions¶
Users can react to comments with emojis. To initialise the reaction components for journals loaded after the first page ("Show all"), define a GraphQL Type and a Query field for your entity.
In the Type, call has_journals. You can override the methods it adds.
| Example of journals usage in GraphQL | |
|---|---|
Then extend Query with a field that finds the entity:
| Example of extending EasyGraphql::Types::Query | |
|---|---|
Issue email notifications¶
Issue journals are not mailed one by one. Every journal that passes Journal#send_email_notifications? creates one EasyMailQueue row (table easy_mail_queue). ProcessMailQueueJob runs from Sidekiq cron (default */15 * * * *, override with PROCESS_MAIL_QUEUE_CRON_INTERVAL), claims all rows (loads, then deletes them) and hands them to ProcessMailQueue.
ProcessMailQueue groups the journals by issue and builds one e-mail per recipient:
- Recipients come from
Issue#get_notified_users_for_issue_edit(journal)for each journal, so a user only gets the journals they may see (private notes, visible details). - The author's own journals are skipped for users with
no_self_notified. attrandcfdetails are squashed to the latest change per attribute; every other detail (relations, attachments) is kept.EmailNotificationsMailer#issue_changesrenders the journals grouped by author, withMessage-IDfrom the first journal andReferenceson the issue.
No journal is queued for issues in template or planned projects, for journals with notify == false, or when the author has no_notification_ever. Other journalized entities (for example CRM cases) do not use the queue. Mails such as issue_add, mentions or reminders are sent immediately by their mailers.
Notification centre rows are described in Notification centre.