Global Reactivity: Ensure quick actions and/or system notes cause the relevant part of the UI of Merge Requests/Issues to update accordingly
Problem to solve
Currently we have an amalgam of components on the Merge Request page that compose the collective mental model of a "Merge Request" for the sure.
Component | Implementation |
---|---|
Open/Merged/Closed label at the top | HAML |
(Sidebar) Milestone | HAML + jQuery |
(Sidebar) Labels | Vue component |
MR Tabs counter | HAML + jQuery |
... | ... |
We have multiple ways to interact with these "values".
Invariably, any change to the MR collective mental state, will generate a system note.
Proposal
What if we loaded the system note with enough metadata to allow a global engine to trigger a notification to the whole page when a new system note is added to the page?
By leveraging the mitt
library (already in the codebase, see [app/assets/javascripts/helpers/event_hub_factory.js](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/assets/javascripts/helpers/event_hub_factory.js)
), we could add an observer to the MR (and any other relevant page) that would be init'ed with the system notes present on page load.
After each polling update, if any new system note was added to the page, it would iterate over each note, extract the metadata and publish a page-wide notification via mitt
.
Any kind of implementation could be quickly updated to react to each notification ensuring a loosely-coupled system to allow for any kind of past/future implementation to stay connected to this "cloud of events".
Update: instead of adding semantic microdata to the markup, we can simply leverage the data from the backend response.
Previous proposal using microdata
Note: metadata could be added to the system note via microdata and ActivityStreams w3c standard.
Markup example
- <li class="timeline-entry note system-note note-wrapper" id="note_4c51cc8bbb0a878e810217f52841fdefb831f1fc">
+ <li class="timeline-entry note system-note note-wrapper" id="note_4c51cc8bbb0a878e810217f52841fdefb831f1fc" itemscope itemtype="https://www.w3.org/ns/activitystreams">
<div class="timeline-entry-inner">
<div class="timeline-icon"><svg><use xlink:href="https://gitlab.com/assets/icons-e0a66cb8e6ca64bcdd2a8f111cbd9e94cf727c1bd5939bc71619df5c973fbc87.svg#label"></use></svg></div>
- <div class="timeline-content">
+ <div class="timeline-content" itemprop="summary">
<div class="note-header">
<div class="note-header-info"><!---->
- <a href="/gitlab-bot" data-user-id="1786152" data-username="gitlab-bot" class="author-name-link js-user-link"> <span class="note-header-author-name bold">🤖 GitLab Bot 🤖</span></a>
+ <a itemprop="actor" content="gitlab-bot" href="/gitlab-bot" data-user-id="1786152" data-username="gitlab-bot" class="author-name-link js-user-link"> <span class="note-header-author-name bold">🤖 GitLab Bot 🤖</span></a>
<!---->
<span class="text-nowrap author-username"><a href="/gitlab-bot" class="author-username-link"><span class="note-headline-light">@gitlab-bot</span></a> <!----></span>
<span class="note-headline-light note-headline-meta">
<span data-qa-selector="system_note_content" class="system-note-message">
- <span>added
+ <span itemprop="type" content="Add">added
<span class="gl-label gl-label-sm">
- <a class="gfm gfm-label has-tooltip gl-link gl-label-link"
+ <a itemprop="object" content="/label/278964/11469047" class="gfm gfm-label has-tooltip gl-link gl-label-link"
title="" data-placement="top" data-container="body" data-reference-type="label" data-label="11469047" data-project="278964" data-link-reference="false" data-link="false" data-original="~11469047" href="/gitlab-org/gitlab/-/issues?label_name=Category%3ASource+Code+Management" data-original-title="Category vision in Create stage: https://gitlab.com/groups/gitlab-org/-/epics/687">
<span style="background-color: #428BCA" data-html="true" data-container="body" class="gl-label-text gl-label-text-light">Category:Source Code Management</span>
</a>
</span>
label
</span> <!---->
</span>
<span class="system-note-separator"><!----></span>
<a href="#note_4c51cc8bbb0a878e810217f52841fdefb831f1fc" class="note-timestamp system-note-separator">
- <time title="Nov 5, 2020 12:56am GMT+0000" datetime="2020-11-05T00:56:07.884Z" class="">4 days ago</time>
+ <time itemprop="published" content="2020-11-05T00:56:07.884Z" title="Nov 5, 2020 12:56am GMT+0000" datetime="2020-11-05T00:56:07.884Z" class="">4 days ago</time>
</a> <!---->
<div class="gl-spinner-container editing-spinner"><span aria-label="Comment is being updated" class="align-text-bottom gl-spinner gl-spinner-dark gl-spinner-sm"></span></div>
</span>
</div>
</div>
</div>
</li>
Please note the increase in markup is negligible but if we want to avoid it, we could implement this the way dompurify
works.
This markup can be parsed into a data structure like:
Handling the system notes coming from polling, we can publish an event on mitt
carrying all enough information to say:
'mr/add/label', {
labelKey: '/label/278964/11469047', // would allow DOM lookup for the exact HTML for accurate colors
changedBy: 'gitlab-bot',
}
The same could be done for others:
'mr/update/status', {
newStatus: 'merged',
}
'mr/update/milestone', {
newMilestone: '13.7',
}
etc.
User experience goal
This would allow different implementations to easily react to any updates without having to rewrite EVERY component on the page to Vue.
From the user perspective this would mean a consistent UI much earlier than if we have to refactor every single component to Vue.
Further details
Permissions and Security
Documentation
- This would require technical documentation to be added to ensure new activities would confirm to this markup format.
Availability & Testing
Ideally, we could start extracting the data structure in tests and confirm they are valid.
What does success look like, and how can we measure that?
A consistent UI when changes are performed by the user or by the system (results of polling or later websockets/real-time updates).