Commit 25e965c3 authored by Jérome Perrin's avatar Jérome Perrin

Support Request App

Assorted fixes and new features for support request app:

Features
 * post are now HTML and use the preferred editor ( CKEditor by default )
 * posts are ingested in Web Message and the app uses same data model as erp5_crm ( so it is able to display support request created with "standard" ERP5 interfaces)
 * date of post uses momentjs relative time (New message by Bob 1 hour ago...)

Bug fixes:
 * post API no longer use proxy roles / immediate reindex
 * RSS was re-implemented to list events. The previous approach of listing support requests had an issue that the date of new posts was still the date of the original support request.
 * attached files to the "submit new support request" dialog where not uploaded
 * using a handlebars template we prevent html injection / XSS 
 * increased test coverage

/reviewed-on nexedi/erp5!769
parents a42da4de 47099d29
......@@ -112,7 +112,7 @@
<item>
<key> <string>text_content</string> </key>
<value> <string>CACHE MANIFEST\n
# v1.0.2\n
# v1.0.3\n
CACHE:\n
font-awesome/font-awesome-webfont.woff2\n
echarts-all.js\n
......
......@@ -10,10 +10,32 @@
<script src="renderjs.js" type="text/javascript"></script>
<!-- custom script -->
<script src="handlebars.js" type="text/javascript"></script>
<script src="gadget_erp5_global.js" type="text/javascript"></script>
<script src="gadget_erp5_pt_form_view_discussable.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="gadget_erp5_pt_form_view_discussable.css">
<!-- templates -->
<script id="template-document-list" type="text/x-handlebars-template">
{{#if comments }}
{{#each comments }}
<li>By <strong>{{ user }}</strong> -
<time datetime="{{ date }}" title="{{ date_formatted }}">{{ date_relative }}</time>
<br/>
{{{ text }}}
{{#if attachment_link }}
<br/>
<strong>Attachment: </strong>
<a href="{{attachment_link}}">{{ attachment_name }}</a>
{{/if}}
<hr id="post_item">
</li>
{{/each }}
{{else }}
<p><em>No comment yet.</em></p><hr id="post_item">
{{/if }}
</script>
</head>
<body>
<!-- XXX this is a form replacement -->
......@@ -28,11 +50,15 @@
<p style="background-color:#0E81C2;color:white;margin:1em 0;padding:0.5em">Comments:</p>
<ol id="post_list"></ol>
<h3 class="ui-content-title ui-body-c ui-icon ui-icon-custom ui-icon-random" id="comment-title" name="comment-title">&nbsp;Post a comment</h3>
<textarea id="comment" name="comment" placeholder="Enter your comment here..."></textarea>
<div data-gadget-url="gadget_editor.html"
data-gadget-scope="editor"
data-gadget-sandbox="">
</div>
<div id="file_upload_div">
<input value="" name="attachment" id="attachment" type="file" title="Upload">
</div>
<input data-theme="b" data-inline="true" type="submit" data-i18n="[value]Post Comment" value="Post Comment" data-icon="check" />
<input data-theme="b" data-inline="true" type="submit" data-i18n="[value]Post Comment" value="Post Comment" data-icon="check" disabled class="ui-disabled"/>
</form>
</div>
</body>
......
......@@ -252,8 +252,8 @@
</tuple>
<state>
<tuple>
<float>1506616673.69</float>
<string>UTC</string>
<float>1539136980.8</float>
<string>GMT+9</string>
</tuple>
</state>
</object>
......
......@@ -252,8 +252,8 @@
</tuple>
<state>
<tuple>
<float>1507898069.5</float>
<string>UTC</string>
<float>1539140577.69</float>
<string>GMT+9</string>
</tuple>
</state>
</object>
......
"""Called on a document ingested from a file post on support request app,
after the document is ingested and metadata are discovered.
"""
post = context.getPortalObject().restrictedTraverse(post_relative_url)
# set relation between post and document
post.setSuccessorValueList([context])
post.publish()
context.share()
<?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>post_relative_url</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Document_afterSupportRequestFilePostIngestion</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
from Products.ERP5Type.ImmediateReindexContextManager import ImmediateReindexContextManager
follow_up_value = context.getPortalObject().restrictedTraverse(follow_up)
from Products.ERP5Type.Message import translateString
portal = context.getPortalObject()
follow_up_value = portal.restrictedTraverse(follow_up)
assert follow_up_value.getPortalType() == "Support Request"
follow_up_value.edit() # update modification date
if not web_site_relative_url:
web_site_relative_url = context.getWebSiteValue().getRelativeUrl()
web_site = portal.restrictedTraverse(web_site_relative_url)
with ImmediateReindexContextManager() as immediate_reindex_context_manager:
post = context.PostModule_createHTMLPostFromText(
follow_up=follow_up,
data=data,
immediate_reindex_context_manager=immediate_reindex_context_manager,
)
if file not in ("undefined", None): # XXX "undefined" ? should also be fixed in javascript side
document_kw = {'batch_mode': True,
'redirect_to_document': False,
'file': file}
document = context.Base_contribute(**document_kw)
# set relation between post and document
# XXX successor is used as a way to put a relation between the attachment and the post,
# the actual way should be to use a proper container like an Event that will have
# one or several posts and one or several attachments.
post.setSuccessorValueList([document])
# XXX depending on security model this should be changed accordingly
document.publish()
post = context.PostModule_createHTMLPostFromText(
follow_up=follow_up,
data=data,
source_reference=source_reference,
)
# XXX the UI of support request app should be responsible for generating a unique
# "message id" for each posted message.
if not post.getSourceReference():
post.setSourceReference(post.getId())
ingest_document_tag = 'ingest-%s' % post.getSourceReference()
after_ingest_document_tag = 'after-ingest-%s' % post.getSourceReference()
document = None
if file not in ("undefined", None): # XXX "undefined" ? should also be fixed in javascript side
follow_up_list = []
project = follow_up_value.getSourceProjectValue()
if project is not None:
follow_up_list.append(project.getRelativeUrl())
group = None
section = follow_up_value.getDestinationSectionValue()\
or follow_up_value.getDestinationValue()
if section is not None:
group = section.getGroup()
document_kw = {
'batch_mode': True,
'redirect_to_document': False,
'attach_document_to_context': True,
'follow_up_list': follow_up_list,
'group': group,
'classification': web_site.getLayoutProperty(
'preferred_attached_document_classification') or\
portal.portal_preferences.getPreferredDocumentClassification(),
'file': file,
}
with follow_up_value.defaultActivateParameterDict(
dict(tag=ingest_document_tag), placeless=True):
# XXX this Base_contribute might update in place another document with same reference
# and leave the post with a "dead link" for successor value.
document = follow_up_value.Base_contribute(**document_kw)
# XXX contribution API should allow to call a method on the final ingested document
# after ingestion is complete.
document.activate(
after_tag=ingest_document_tag,
tag=after_ingest_document_tag,
).Document_afterSupportRequestFilePostIngestion(
post_relative_url=post.getRelativeUrl(), )
else:
# when we don't upload a document, we can publish the post now.
post.publish()
post.activate(
after_tag=after_ingest_document_tag
# XXX This API is not agreed. Also, we need to consider the possibility
# of ingesting posts through alarm, which is required when we want to ingest
# post without owners (from anoymous users).
).Post_ingestMailMessageForSupportRequest(
web_site_relative_url=web_site_relative_url)
# to be able to display the just posted data in SupportRequest_getCommentPostListAsJson,
# we store it in a session variable.
successor_name = successor_link = None
if document is not None:
successor_link, successor_name = document.getRelativeUrl(), document.getFilename()
portal.portal_sessions[
'%s.latest_comment' % follow_up_value.getRelativeUrl()]['comment_post_list'] = dict(
user=post.Base_getOwnerTitle(),
date=post.getStartDate().ISO8601(),
text=post.asStrippedHTML(),
attachment_link=successor_link,
attachment_name=successor_name,
message_id=post.getSourceReference(),)
......@@ -50,7 +50,7 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>follow_up, predecessor, data, file</string> </value>
<value> <string>follow_up, predecessor, data, file, source_reference=None, web_site_relative_url=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
from Products.ERP5Type.Message import translateString
portal = context.getPortalObject()
support_request = context.getFollowUpValue()
web_site_value = portal.restrictedTraverse(web_site_relative_url)
# XXX what to do with PData ?
# As a first step just use a string.
data = str(context.getData())
is_html = context.getPortalType() == 'HTML Post'
if is_html:
# sanitize HTML
data = portal.portal_transforms.convertToData(
'text/x-html-safe',
data,
context=context,
mimetype=context.getContentType())
# lookup a resource and a source.
# It's critical for support request app that we can create movement with source and resouce
# because movements without source & resource does not get indexed in stock table. As this
# app uses Inventory API to list the history of movements, movements needs to be indexed.
resource = web_site_value.getLayoutProperty('preferred_event_resource', None)
if not resource:
resource = portal.portal_preferences.getPreferredEventResource()
assert resource, "No resource configured for event"
source_value = portal.portal_membership.getAuthenticatedMember().getUserValue()
if source_value is None:
# try harder to get a source for non-person users.
source_value = support_request.getSourceSectionValue()
web_message = portal.event_module.newContent(
portal_type='Web Message',
title=context.getTitle() if context.hasTitle() else None,
content_type='text/html' if is_html else 'text/plain',
text_content=data,
follow_up_value=support_request,
aggregate_value_list=[context] + context.getSuccessorValueList(
portal_type=portal.getPortalDocumentTypeList()),
resource=resource,
source_value=source_value,
start_date=context.getCreationDate(),
source_reference=context.getSourceReference())
context.archive(
comment=translateString('Ingested as ${web_message_reference}',
mapping={'web_message_reference': web_message.getReference()}))
web_message.stop()
<?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>web_site_relative_url</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Post_ingestMailMessageForSupportRequest</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -25,6 +25,8 @@ if description is not None or file is not None:
predecessor=None,
data="" if description is None else description,
file=file,
web_site_relative_url=context.getWebSiteValue().getRelativeUrl(),
source_reference=source_reference,
)
return support_request.Base_redirect('officejs_support_request_view',
......
......@@ -50,7 +50,7 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>description, file, resource, title, project, **kwargs</string> </value>
<value> <string>description, file, resource, title, project, source_reference=None, **kwargs</string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
"""Returns all support requests events for RSS
"""
from Products.PythonScripts.standard import Object
portal = context.getPortalObject()
document_type_list = portal.getPortalDocumentTypeList()
def makeLine(kw):
return Object(**kw)
getSupportRequest_memo = {}
def getSupportRequestInfo(event):
follow_up = event.getFollowUp()
try:
return getSupportRequest_memo[follow_up]
except KeyError:
support_request = portal.restrictedTraverse(follow_up)
getSupportRequest_memo[follow_up] = (
support_request.getTitle(),
support_request.getResourceTranslatedTitle() or '',
support_request.SupportRequest_getSupportRequestLink(),
)
return getSupportRequest_memo[follow_up]
data_list = []
for brain in portal.portal_simulation.getMovementHistoryList(
portal_type=portal.getPortalEventTypeList(),
only_accountable=False,
follow_up_portal_type='Support Request',
omit_input=True,
# XXX we still don't have getCurrentMovementHistoryList
simulation_state=('started', 'stopped', 'delivered'),
limit=limit,
sort_on=(('stock.date', 'desc'),
('uid', 'desc')),):
event = brain.getObject()
(support_request_title,
support_request_category,
support_request_link) = getSupportRequestInfo(event)
data_list.append(
makeLine({
# XXX or {author} commented on {support_request} / {author} opened new Ticket: {support_request} ?
'title': support_request_title,
'category': support_request_category,
'author': brain.node_title,
'link': support_request_link,
'description': event.asStrippedHTML(),
'pubDate': brain.date,
'guid': event.getSourceReference() or event.absolute_url(),
'thumbnail': ( # XXX this is not really a thumbnail, but it's what RSS style uses for <enclosure/>
# Also, with this `thumbnail` it will look good for image, and most of the time
# users attach a screenshot of their problem.
event.getDefaultAggregate(portal_type=document_type_list)
and event.getDefaultAggregateValue(portal_type=document_type_list).File_getDownloadUrl()
or None)
}
)
)
return data_list
<?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>limit=50, **kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SupportRequestModule_getEventList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -88,24 +88,13 @@
<item>
<key> <string>hidden</string> </key>
<value>
<list>
<string>listbox_delivery_stop_date</string>
<string>listbox_destination_decision_language</string>
<string>listbox_causality_translated_portal_type</string>
<string>listbox_delivery_start_date</string>
<string>listbox_post_start_date_hidden</string>
<string>listbox_post_user</string>
<string>listbox_post_start_date</string>
</list>
<list/>
</value>
</item>
<item>
<key> <string>left</string> </key>
<value>
<list>
<string>listbox_post_content</string>
<string>listbox_support_link</string>
</list>
<list/>
</value>
</item>
<item>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>default</string>
<string>editable</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox_causality_translated_portal_type</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python:getattr(cell.getCausalityValue(), \'getTranslatedPortalType\', lambda:None)()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>editable</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox_delivery_start_date</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_date</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox_delivery_stop_date</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_date_time_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>default</string>
<string>editable</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox_destination_decision_language</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python:cell.hasDestinationDecision() and cell.getDestinationDecisionValue().getLanguage()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>default</string>
<string>editable</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox_post_content</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: cell.SupportRequest_getLastPostContent()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>default</string>
<string>editable</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox_post_start_date</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: cell.SupportRequest_getLastPostDate()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>default</string>
<string>editable</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox_post_user</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: cell.SupportRequest_getLastPostAuthor()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>default</string>
<string>editable</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox_support_link</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: cell.SupportRequest_getSupportRequestLink()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -53,7 +53,7 @@
</item>
<item>
<key> <string>enctype</string> </key>
<value> <string></string> </value>
<value> <string>multipart/form-data</string> </value>
</item>
<item>
<key> <string>group_list</string> </key>
......@@ -75,7 +75,7 @@
<key> <string>bottom</string> </key>
<value>
<list>
<string>my_description</string>
<string>your_description</string>
<string>your_file</string>
</list>
</value>
......
......@@ -10,15 +10,15 @@
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>default</string>
<string>editable</string>
<string>input_order</string>
<string>renderjs_extra</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox_post_start_date_hidden</string> </value>
<value> <string>your_description</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
......@@ -54,12 +54,6 @@
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
......@@ -73,13 +67,19 @@
<value> <string></string> </value>
</item>
<item>
<key> <string>input_order</string> </key>
<value> <string></string> </value>
<key> <string>renderjs_extra</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
......@@ -87,32 +87,32 @@
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_date</string> </value>
<value> <string>my_text_content</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
<value> <string>Base_viewCRMFieldLibrary</string> </value>
</item>
<item>
<key> <string>input_order</string> </key>
<value> <string>ymd</string> </value>
<key> <string>renderjs_extra</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Description</string> </value>
</item>
</dictionary>
</value>
</item>
......@@ -127,7 +127,7 @@
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: cell.SupportRequest_getLastPostDate(is_pure_date=True)</string> </value>
<value> <string>python: [(\'editor\', context.Base_getEditorFieldPreferredTextEditor()), (\'portal_type\', \'HTML Post\'), (\'maximize\', False)]</string> </value>
</item>
</dictionary>
</pickle>
......
from json import dumps
portal = context.getPortalObject()
document_type_list = portal.getPortalDocumentTypeList()
preferred_date_order = portal.portal_preferences.getPreferredDateOrder() or "ymd"
preferred_date_order = "/".join(preferred_date_order)
def formatDate(date):
# XXX modification date & creation date are still in server timezone.
# See merge request !17
#
# if default_time_zone:
# date = date.toZone(default_time_zone)
return date.strftime("%s %%H:%%M" %(
preferred_date_order.
replace("y", "%Y").
replace("m", "%m").
replace("d", "%d"),
))
post_list = portal.portal_catalog(
portal_type="HTML Post",
strict_follow_up_uid=context.getUid(),
sort_on=(('modification_date', 'ascending'),),
validation_state="published",
event_list = portal.portal_simulation.getMovementHistoryList(
portal_type=portal.getPortalEventTypeList(),
strict_follow_up_uid=context.getUid(),
simulation_state=('started', 'stopped', 'delivered', ),
only_accountable=False,
omit_input=True,
sort_on=(('date', 'asc'), ('uid', 'asc',),)
)
comment_list = []
for post in post_list:
owner = post.Base_getOwnerTitle()
time_stamp = formatDate(post.getStartDate())
content = post.asStrippedHTML()
successor_list = post.getSuccessorValueList()
successor_name = successor_link = None
if successor_list:
successor_link, successor_name = successor_list[0].getRelativeUrl(), successor_list[0].getFilename()
comment_list.append((owner, time_stamp, content, successor_link, successor_name))
for event in event_list:
event = event.getObject()
attachment_link = attachment_name = None
attachment = event.getDefaultAggregateValue(portal_type=document_type_list)
if attachment is not None:
attachment_link, attachment_name = attachment.getRelativeUrl(), attachment.getFilename()
comment_list.append((dict(
user=event.getSourceTitle(),
date=event.getStartDate().ISO8601(),
text=event.asStrippedHTML(),
attachment_link=attachment_link,
attachment_name=attachment_name,
message_id=event.getSourceReference(),
)))
just_posted_comment = portal.portal_sessions[
'%s.latest_comment' % context.getRelativeUrl()].pop(
'comment_post_list', None)
if just_posted_comment is not None:
# make sure not to display twice if it was already ingested in the meantime.
if just_posted_comment['message_id'] not in [comment['message_id'] for comment in comment_list]:
comment_list.append(just_posted_comment)
return dumps(comment_list)
portal = context.getPortalObject()
# get the all HTML Posts which related to this Support Request
post_list = portal.portal_catalog(portal_type="HTML Post", strict_follow_up_uid=context.getUid(), sort_on=(('modification_date', 'descending'),), limit=1, validation_state="published") # with id keyword, this function will return a sequence data type which contains one element.
if len(post_list):
return post_list[0].asStrippedHTML()
else:
return None
......@@ -100,6 +100,7 @@
<string>my_source_decision_title</string>
<string>my_source_project_title</string>
<string>my_destination_title</string>
<string>your_preferred_editor</string>
</list>
</value>
</item>
......
......@@ -8,7 +8,7 @@
<thead>
<tr><td rowspan="1" colspan="3">Support Request Zuite</td></tr>
</thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" />
<tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/init" />
<tr>
<td>open</td>
<td>${base_url}/web_site_module/erp5_officejs_support_request_ui/</td>
......@@ -49,16 +49,9 @@
<td>field_your_resource</td>
<td>FeatureRequire</td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//textarea[@id='field_my_description']</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>//textarea[@id='field_my_description']</td>
<td>Post test</td>
</tr>
<tal:block tal:define="text_content string:Post test">
<tal:block metal:use-macro="container/Zuite_CommonTemplateForRenderjsUi/macros/type_ckeditor_text_content"/>
</tal:block>
<tr>
<td>click</td>
<td>//input[@data-i18n='[value]Proceed']</td>
......@@ -74,6 +67,11 @@
<td>Comments:</td>
<td></td>
</tr>
<tr>
<td>waitForText</td>
<td>//ol[@id="post_list"]//li/p</td>
<td>Post test</td>
</tr>
<tr>
<td>waitForTextPresent</td>
<td>Reference</td>
......
......@@ -8,7 +8,7 @@
<thead>
<tr><td rowspan="1" colspan="3">Support Request Zuite</td></tr>
</thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" />
<tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/init" />
<tr>
<td>open</td>
<td>${base_url}/web_site_module/erp5_officejs_support_request_ui/</td>
......@@ -49,16 +49,9 @@
<td>field_your_resource</td>
<td>FeatureRequire</td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//textarea[@id='field_my_description']</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>//textarea[@id='field_my_description']</td>
<td>Post test 1</td>
</tr>
<tal:block tal:define="text_content string:Post test 1">
<tal:block metal:use-macro="container/Zuite_CommonTemplateForRenderjsUi/macros/type_ckeditor_text_content"/>
</tal:block>
<tr>
<td>waitForElementPresent</td>
<td>//input[@data-i18n='[value]Proceed']</td>
......@@ -75,25 +68,136 @@
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//textarea[@id='comment']</td>
<td>waitForText</td>
<td>//ol[@id="post_list"]//li[1]/p</td>
<td>Post test 1</td>
</tr>
<tr>
<td>waitForText</td>
<td>//ol[@id="post_list"]//li[1]/p</td>
<td>Post test 1</td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>//ol[@id="post_list"]//li[1]/strong</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>//textarea[@id='comment']</td>
<td>Post test 2</td>
<td>assertText</td>
<td>//ol[@id="post_list"]//li[1]/time</td>
<td>a few seconds ago</td>
</tr>
<!-- The "just posted" message is available because it is retrieved from memcached,
eventhough it's not ingested yet. But this works only for one message, so to first message
posted when opening the SR and the second one posted as a comment, we need to flush
activities in between.
Not really good on one hand, because if activities are congested, there will be a problem
here, but on the other hand it allows us to see that once ingested messages are also properly
displayed
-->
<tr>
<td>store</td>
<td>javascript{selenium.browserbot.getCurrentWindow().location.href}</td>
<td>current_location</td>
</tr>
<tr>
<td>open</td>
<td tal:content="string:${here/portal_url}/Zuite_waitForActivities"/>
<td/>
</tr>
<tr>
<td>waitForTextPresent</td>
<td>Done.</td>
<td/>
</tr>
<tr>
<td>open</td>
<td>${current_location}</td>
<td></td>
</tr>
<tal:block tal:define="text_content string:Post test 2">
<tal:block metal:use-macro="container/Zuite_CommonTemplateForRenderjsUi/macros/type_ckeditor_text_content"/>
</tal:block>
<tr>
<td>click</td>
<td>//input[@data-i18n='[value]Post Comment']</td>
<td></td>
</tr>
<tr>
<td>waitForText</td>
<td>//ol[@id="post_list"]//li[2]/p</td>
<td>Post test 2</td>
</tr>
<tr>
<td>assertText</td>
<td>//ol[@id="post_list"]//li[1]/p</td>
<td>Post test 1</td>
</tr>
<tr>
<td>assertText</td>
<td>//ol[@id="post_list"]//li[1]/strong</td>
<td>A1 Corporation</td>
</tr>
<tr>
<td>assertText</td>
<td>//ol[@id="post_list"]//li[1]/time</td>
<td>a few seconds ago</td>
</tr>
<!-- flush activities and post one more message, to exercice ingesting post
posted from support request comment, which uses a different code path than
post ingested when submitting a new support request.
-->
<tr>
<td>store</td>
<td>javascript{selenium.browserbot.getCurrentWindow().location.href}</td>
<td>current_location</td>
</tr>
<tr>
<td>open</td>
<td tal:content="string:${here/portal_url}/Zuite_waitForActivities"/>
<td/>
</tr>
<tr>
<td>waitForTextPresent</td>
<td>By</td>
<td>Done.</td>
<td/>
</tr>
<tr>
<td>open</td>
<td>${current_location}</td>
<td></td>
</tr>
<tal:block tal:define="text_content string:Post test 3">
<tal:block metal:use-macro="container/Zuite_CommonTemplateForRenderjsUi/macros/type_ckeditor_text_content"/>
</tal:block>
<tr>
<td>click</td>
<td>//input[@data-i18n='[value]Post Comment']</td>
<td></td>
</tr>
<tr>
<td>waitForText</td>
<td>//ol[@id="post_list"]//li[3]/p</td>
<td>Post test 3</td>
</tr>
<tr>
<td>assertText</td>
<td>//ol[@id="post_list"]//li[1]/p</td>
<td>Post test 1</td>
</tr>
<tr>
<td>assertText</td>
<td>//ol[@id="post_list"]//li[2]/p</td>
<td>Post test 2</td>
</tr>
</tbody></table>
</body>
</html>
\ No newline at end of file
......@@ -8,7 +8,7 @@
<thead>
<tr><td rowspan="1" colspan="3">Support Request Zuite</td></tr>
</thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" />
<tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/init" />
<tr>
<td>open</td>
<td>${base_url}/web_site_module/erp5_officejs_support_request_ui/</td>
......
......@@ -8,7 +8,7 @@
<thead>
<tr><td rowspan="1" colspan="3">Support Request Zuite</td></tr>
</thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" />
<tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/init" />
<tr>
<td>open</td>
<td>${base_url}/web_site_module/erp5_officejs_support_request_ui/</td>
......
......@@ -8,7 +8,7 @@
<thead>
<tr><td colspan="3" tal:content="template/title"></td></tr>
</thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" />
<tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/init" />
<tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/cleanup_module" />
<tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/create_data" />
......
......@@ -8,7 +8,7 @@
<thead>
<tr><td colspan="3" tal:content="template/title"></td></tr>
</thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" />
<tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/init" />
<tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/cleanup_module" />
<tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/create_data" />
......
......@@ -8,7 +8,7 @@
<thead>
<tr><td rowspan="1" colspan="3">Support Request Zuite</td></tr>
</thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" />
<tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/init" />
<tr>
<td>open</td>
<td>${base_url}/web_site_module/erp5_officejs_support_request_ui/</td>
......
......@@ -8,7 +8,7 @@
<thead>
<tr><td rowspan="1" colspan="3">Support Request Zuite</td></tr>
</thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" />
<tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/init" />
<tr>
<td>open</td>
<td>${base_url}/web_site_module/erp5_officejs_support_request_ui/</td>
......
......@@ -8,7 +8,7 @@
<thead>
<tr><td rowspan="1" colspan="3">Support Request Zuite</td></tr>
</thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" />
<tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/init" />
<tr>
<td>open</td>
<td>${base_url}/web_site_module/erp5_officejs_support_request_ui/</td>
......@@ -24,16 +24,9 @@
<td>//input[@data-i18n='[value]Submit New Support Request']</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>//textarea[@id='field_my_description']</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>//textarea[@id='field_my_description']</td>
<td>Post test</td>
</tr>
<tal:block tal:define="text_content string:Post test">
<tal:block metal:use-macro="container/Zuite_CommonTemplateForRenderjsUi/macros/type_ckeditor_text_content"/>
</tal:block>
<tr>
<td>type</td>
<td>//input[@name='field_your_title']</td>
......
......@@ -8,7 +8,7 @@
<thead>
<tr><td rowspan="1" colspan="3">Worklists on support request front page</td></tr>
</thead><tbody>
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" />
<tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/init" />
<tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/cleanup_module" />
<tal:block metal:use-macro="here/Zuite_SupportRequestUITemplate/macros/create_data" />
......
......@@ -52,14 +52,6 @@
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_addManagerAclUserIfNotExisting</string> </value>
......
......@@ -20,6 +20,12 @@ for brain in portal.portal_catalog(
to_delete_list.append(support_request.getId())
portal.support_request_module.manage_delObjects(to_delete_list)
event_to_delete_id_list = []
for event in portal.event_module.contentValues():
if event.getFollowUp(portal_type='Support Request'):
event_to_delete_id_list.append(event.getId())
portal.event_module.manage_delObjects(event_to_delete_id_list)
# Clear worklist cache
portal.portal_caches.clearAllCache()
......
portal = context.getPortalObject()
# This should have been created during setup by Zuite_setPreference, but
# we need to support cases where developer run test in his development
# instance without the setup steps of ERP5TypeFunctionalTestCase
pref = getattr(portal.portal_preferences, "erp5_ui_test_preference", None)
if pref is None:
pref = portal.portal_preferences.newContent(
id="erp5_ui_test_preference",
portal_type="Preference",
priority=1)
if pref.getPreferenceState() == 'disabled':
pref.enable()
# use fck editor, we test with this
pref.setPreferredTextEditor('fck_editor')
# set a preferred event resource, so that the web message we create
# gets indexed properly in stock table.
# XXX This ressource does not make much sense though, using something like
# "support request message post" would be closer to the real resource of
# these events.
preferred_event_resource = 'service_module/erp5_officejs_support_request_ui_test_service_003'
if portal.web_site_module.erp5_officejs_support_request_ui.getLayoutProperty(
'preferred_event_resource', None) != 'preferred_event_resource':
portal.web_site_module.erp5_officejs_support_request_ui.edit(
preferred_event_resource=preferred_event_resource
)
return "Done."
......@@ -54,7 +54,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SupportRequest_getLastPostContent</string> </value>
<value> <string>ERP5Site_setupSupportRequestPreference</string> </value>
</item>
</dictionary>
</pickle>
......
<tal:block xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal">
<tal:block metal:define-macro="init">
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" />
<tr><td>openAndWait</td>
<td>${base_url}/ERP5Site_setupSupportRequestPreference</td><td></td></tr>
<tr><td>assertTextPresent</td>
<td>Done.</td><td></td></tr>
</tal:block>
<tal:block metal:define-macro="cleanup_module">
<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/wait_for_activities" />
<tr><td>openAndWait</td>
......
......@@ -25,23 +25,9 @@
#
##############################################################################
import unittest
from Products.ERP5Type.tests.ERP5TypeFunctionalTestCase import ERP5TypeFunctionalTestCase
class TestSupportRequestUI(ERP5TypeFunctionalTestCase):
foreground = 0
run_only = "officejs_support_request_ui_zuite"
def getBusinessTemplateList(self):
return (
'erp5_web_renderjs_ui',
'erp5_web_renderjs_ui_test',
'erp5_ui_test_core',
'erp5_officejs_support_request_ui'
)
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestSupportRequestUI))
return suite
\ No newline at end of file
del ERP5TypeFunctionalTestCase
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Test Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_recorded_property_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>testSupportRequest</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test.erp5.testSupportRequest</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Test Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.patches.WorkflowTool"/>
</pickle>
<pickle>
<tuple>
<none/>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</tuple>
</pickle>
</record>
</ZopeData>
erp5_ui_test_core
erp5_ui_test
erp5_web_renderjs_ui
erp5_officejs_support_request_ui
\ No newline at end of file
erp5_officejs_support_request_ui
erp5_web_renderjs_ui_test
\ No newline at end of file
test.erp5.testFunctionalSupportRequest
\ No newline at end of file
test.erp5.testFunctionalSupportRequest
test.erp5.testSupportRequest
\ No newline at end of file
......@@ -73,6 +73,7 @@
<value>
<list>
<string>my_title</string>
<string>my_source_reference</string>
</list>
</value>
</item>
......
# XXX do we need two scripts ??
portal = context.getPortalObject()
traverse = context.getPortalObject().restrictedTraverse
# create an HTML Post
post_module = portal.post_module
......@@ -6,14 +9,16 @@ post_module = portal.post_module
now = DateTime()
post_edit_kw = {
"start_date": now,
"follow_up_value": context.getPortalObject().restrictedTraverse(follow_up),
"follow_up_value": traverse(follow_up),
"text_content": data,
"source_reference": source_reference,
"title": title,
}
if predecessor not in (None, ""):
predecessor_value, = portal.portal_catalog(relative_url=predecessor, limit=2)
post_edit_kw["predecessor_value"] = predecessor_value.getObject()
if predecessor:
post_edit_kw["predecessor"] = traverse(predecessor)
post = post_module.newContent(
immediate_reindex=immediate_reindex_context_manager,
portal_type='HTML Post',
**post_edit_kw
)
......
......@@ -50,16 +50,7 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>follow_up, data, predecessor=None, immediate_reindex_context_manager=None</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
<value> <string>follow_up, data, predecessor=None, source_reference=None, title=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
return context.PostModule_createHTMLPost(
title=data.splitlines()[0][:30] if data else None,
source_reference=source_reference,
data=data,
follow_up=follow_up,
predecessor=predecessor,
data="<p>" + data.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace(" ", " &nbsp;").replace("\n", "<br/>") + "</p>",
immediate_reindex_context_manager=immediate_reindex_context_manager,
)
......@@ -50,16 +50,7 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>follow_up, data, predecessor=None, immediate_reindex_context_manager=None</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
<value> <string>follow_up, data, source_reference, predecessor=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="StateDefinition" module="Products.DCWorkflow.States"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>description</string> </key>
<value> <string>When the post have been fully ingested, we archive it.</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>archived</string> </value>
</item>
<item>
<key> <string>permission_roles</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Archived</string> </value>
</item>
<item>
<key> <string>transitions</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>type_list</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>Access contents information</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>Modify portal content</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>View</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
</tuple>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -28,6 +28,8 @@
<key> <string>transitions</string> </key>
<value>
<tuple>
<string>archive</string>
<string>archive_action</string>
<string>hide</string>
<string>hide_action</string>
</tuple>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>archive</string> </value>
</item>
<item>
<key> <string>new_state_id</string> </key>
<value> <string>archived</string> </value>
</item>
<item>
<key> <string>script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Archive Post</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Guard" module="Products.DCWorkflow.Guard"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>roles</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Owner</string>
</tuple>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="TransitionDefinition" module="Products.DCWorkflow.Transitions"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string>Archive Post</string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string>%(content_url)s/Base_viewWorkflowActionDialog?workflow_action=archive_action</string> </value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>archive_action</string> </value>
</item>
<item>
<key> <string>new_state_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>script_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Archive Post (Action)</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Guard" module="Products.DCWorkflow.Guard"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>roles</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
</tuple>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
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