Commit df57bbe4 authored by Jérome Perrin's avatar Jérome Perrin

rss_style: include guid in tickets RSS

SlapOS ticket RSS have one entry for each ticket, with the content of
the latest event as description, but when using a feed reader application,
we'd like to have a new entry for each message posted to the ticket, to
be notified that a new message was posted.

Feed reader applications fetch the RSS and have to determine if each
entry is already in their database or not. In a scenario like below, we
observe different behaviors for different applications:

  1. a user open a ticket "help" with a first message "I need help !".
    The RSS entry for this ticket is:

    <item>
      <link>https://slapos.example.com/#/support_request_module/1</link>
      <title>Help</title>
      <pubDate>Mon, 01 Jan 2001 10:57:10 +0100</pubDate>
      <description>I need help !</description>
    </item>

   => feed reader fetches the RSS and add a new entry with title "Help"

  2. support operator reply with a second message "how can I help you ?"
    The RSS entry for the ticket becomes:

    <item>
      <link>https://slapos.example.com/#/support_request_module/1</link>
      <title>Help</title>
      <pubDate>Mon, 02 Jan 2001 08:32:12 +0100</pubDate>      <== this is different
      <description>How can I help you ?</description>           <== this is different
    </item>

now depending on the implementation of the feed reader, it will either:

 - add a new entry, this is the best behavior. In the RSS readers I tested,
  liferea and newsboat do this. Probably they consider that the entry is
   different because it has a new pubDate and/or description.
 - update the existing entry "in place" - this is not so good, because
  this does not appear as a new entry and the original message is no
  longer in the RSS reader. TinyTinyRSS behaves like this (at least in
  version 17.4, which is a bit old)
 - consider they already have the entry and don't do anything - this is
  even worse, because user can not see there was a reply. FreshRSS
  behaves like this.

A RSS for tickets will always have the problem that the feed reader must
refresh often enough if we want to have a copy of all messages in the
feed reader (and that's why ERP5 CRM's uses a RSS of Events and not
Tickets), but we can make this situation better by using guid on messages.

By constructing a guid [1] that will become different every time a new
message was posted, feed readers consider each item as a a new, different
entry. At least this is the case for all the feed reader I tested, except
ERP5 builtin feed reader which only consider the `link` attribute.

Strictly speaking this implementation has a "problem" that these guids
are not permalinks, but I believe that's something we should address in
erp5_rss_style (if guid does not look like an URL, set isPermalink="false"),
but in practice it does not seem to be a problem.

1: https://validator.w3.org/feed/docs/rss2.html#ltguidgtSubelementOfLtitemgt
parent 19b3eac1
Pipeline #18632 failed with stage
in 0 seconds
......@@ -28,6 +28,8 @@ from DateTime import DateTime
from Products.ERP5Type.tests.utils import createZODBPythonScript
import json
import feedparser
def getFakeSlapState():
return "destroy_requested"
......@@ -1943,3 +1945,43 @@ return "Visited by SupportRequest_trySendNotificationMessage %s %s" % (message_t
support_request.getSimulationState())
class TestSlapOSSupportRequestRSS(TestCRMSkinsMixin):
def test_WebSection_viewTicketListAsRSS(self):
person = self.makePerson()
module = self.portal.support_request_module
support_request = module.newContent(
portal_type="Support Request",
title='Help',
destination_decision_value=person,
)
self.portal.event_module.newContent(
portal_type='Web Message',
follow_up_value=support_request,
text_content='I need help !',
source_value=person,
).start()
support_request.validate()
self.tic()
self.login(person.getUserId())
self.portal.portal_skins.changeSkin('RSS')
parsed = feedparser.parse(self.portal.WebSection_viewTicketListAsRSS())
self.assertFalse(parsed.bozo)
first_entry_id = [item.id for item in parsed.entries]
self.assertEqual([item.summary for item in parsed.entries], ['I need help !'])
self.portal.event_module.newContent(
portal_type='Web Message',
follow_up_value=support_request,
text_content='How can I help you ?',
destination_value=person,
).start()
self.tic()
self.portal.portal_skins.changeSkin('RSS')
parsed = feedparser.parse(self.portal.WebSection_viewTicketListAsRSS())
self.assertFalse(parsed.bozo)
self.assertEqual([item.summary for item in parsed.entries], ['How can I help you ?'])
self.assertNotEqual([item.id for item in parsed.entries][0], first_entry_id)
......@@ -127,6 +127,10 @@
<string>title</string>
<string>Title</string>
</tuple>
<tuple>
<string>Ticket_getRSSGuid</string>
<string>guid</string>
</tuple>
<tuple>
<string>modification_date</string>
<string>pubDate</string>
......
......@@ -127,6 +127,10 @@
<string>title</string>
<string>Title</string>
</tuple>
<tuple>
<string>Ticket_getRSSGuid</string>
<string>guid</string>
</tuple>
<tuple>
<string>modification_date</string>
<string>pubDate</string>
......
latest_event_relative_url = ''
latest_event = context.Ticket_getLatestEvent()
if latest_event:
latest_event_relative_url = latest_event.getRelativeUrl()
return '{}-{}'.format(
context.getRelativeUrl(),
latest_event_relative_url,
)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Ticket_getRSSGuid</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -127,6 +127,10 @@
<string>title</string>
<string>Title</string>
</tuple>
<tuple>
<string>Ticket_getRSSGuid</string>
<string>guid</string>
</tuple>
<tuple>
<string>modification_date</string>
<string>pubDate</string>
......
......@@ -127,6 +127,10 @@
<string>title</string>
<string>Title</string>
</tuple>
<tuple>
<string>Ticket_getRSSGuid</string>
<string>guid</string>
</tuple>
<tuple>
<string>modification_date</string>
<string>pubDate</string>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment