Commit df2f22d0 authored by Rafael Monnerat's avatar Rafael Monnerat

extend *slap-interface-workflow API to CRM/PDM

See merge request !360
parents 1d1bcf8b 4eeac9b7
......@@ -1253,3 +1253,208 @@ class TestSlapOSCorePersonRequestToken(SlapOSTestCaseMixin):
self.assertEqual(
token.getPortalType(), "One Time Restricted Access Token")
self.assertEqual(token.getUrlMethod(), "POST")
class TestSlapOSCorePersonNotify(SlapOSTestCaseMixin):
def afterSetUp(self):
SlapOSTestCaseMixin.afterSetUp(self)
self.person = self.makePerson()
self.tic()
def beforeTearDown(self):
pass
def test_Person_notify_mandatory_argument(self):
self.assertRaises(TypeError, self.person.notify)
self.assertRaises(TypeError, self.person.notify, support_request_title="a")
self.assertRaises(TypeError, self.person.notify, support_request_title="a", support_request_description="b")
def test_Person_notify_unknown_aggregate(self):
self.assertRaises(KeyError, self.person.notify,
support_request_title="a", support_request_description="b", aggregate="c")
def test_Person_notify_computer_node(self):
compute_node, _ = self._makeComputeNode()
self._test_Person_notify(compute_node)
def test_Person_notify_instance_tree(self):
person = self.portal.person_module.template_member\
.Base_createCloneDocument(batch_mode=1)
instance_tree = self.portal\
.instance_tree_module.template_instance_tree\
.Base_createCloneDocument(batch_mode=1)
instance_tree.validate()
new_id = self.generateNewId()
instance_tree.edit(
title= "Test hosting sub ticket %s" % new_id,
reference="TESTHST-%s" % new_id,
destination_section_value=person
)
self._test_Person_notify(instance_tree)
def test_Person_notify_software_installation(self):
self._makeComputeNode()
software_installation = self.portal\
.software_installation_module.template_software_installation\
.Base_createCloneDocument(batch_mode=1)
software_installation.edit(
url_string=self.generateNewSoftwareReleaseUrl(),
aggregate=self.compute_node.getRelativeUrl(),
reference='TESTSOFTINSTS-%s' % self.generateNewId(),
title='Start requested for %s' % self.compute_node.getUid()
)
software_installation.validate()
software_installation.requestStart()
self._test_Person_notify(software_installation)
def _test_Person_notify(self, aggregate_value):
# Step 1: Notify
self.person.notify(
support_request_title="A",
support_request_description="B",
aggregate=aggregate_value.getRelativeUrl()
)
# Step 2: Check return
support_request_relative_url = self.person.REQUEST.get(
"support_request_relative_url", None)
self.assertNotEqual(None, support_request_relative_url)
support_request_in_progress = self.person.REQUEST.get(
"support_request_in_progress", None)
self.assertEqual(support_request_in_progress, support_request_relative_url)
support_request = self.portal.restrictedTraverse(support_request_in_progress)
self.assertEqual(support_request.getSimulationState(),
"validated")
self.assertEqual(support_request.getTitle(), "A")
self.assertEqual(support_request.getDescription(), "B")
self.assertNotEqual(support_request.getStartDate(), None)
self.assertEqual(support_request.getDestinationDecision(),
self.person.getRelativeUrl())
self.assertEqual(support_request.getAggregateValue(),
aggregate_value)
self.assertEqual(support_request.getResource(),
"service_module/slapos_crm_monitoring")
# Step 3: Reset REQUEST and check in progress before catalog
self.person.REQUEST.set(
"support_request_relative_url", None)
support_request_relative_url = self.person.REQUEST.get(
"support_request_relative_url", None)
self.assertEqual(None, support_request_relative_url)
self.person.notify(
support_request_title="A",
support_request_description="B",
aggregate=aggregate_value.getRelativeUrl()
)
support_request_relative_url = self.person.REQUEST.get(
"support_request_relative_url", None)
self.assertNotEqual(None, support_request_relative_url)
self.assertEqual(support_request_in_progress, support_request_relative_url)
self.tic()
# Step 4: Reset parameters and check if the support request is got again.
self.person.REQUEST.set(
"support_request_relative_url", None)
support_request_relative_url = self.person.REQUEST.get(
"support_request_relative_url", None)
self.assertEqual(None, support_request_relative_url)
support_request_in_progress = self.person.REQUEST.set(
"support_request_in_progress", None)
support_request_in_progress = self.person.REQUEST.set(
"support_request_in_progress", None)
self.assertEqual(support_request_in_progress, None)
self.commit()
self.person.notify(
support_request_title="A",
support_request_description="B",
aggregate=aggregate_value.getRelativeUrl()
)
support_request_relative_url = self.person.REQUEST.get(
"support_request_relative_url", None)
self.assertNotEqual(None, support_request_relative_url)
support_request_in_progress = self.person.REQUEST.get(
"support_request_in_progress", None)
self.assertEqual(support_request_in_progress, support_request_relative_url)
# Check if it is the same Support Request as before
self.assertEqual(support_request.getRelativeUrl(),
support_request_relative_url)
# Step 5: Retry the same thing, but now on suspended state
support_request.suspend()
self.tic()
self.person.REQUEST.set(
"support_request_relative_url", None)
support_request_relative_url = self.person.REQUEST.get(
"support_request_relative_url", None)
self.assertEqual(None, support_request_relative_url)
support_request_in_progress = self.person.REQUEST.set(
"support_request_in_progress", None)
support_request_in_progress = self.person.REQUEST.set(
"support_request_in_progress", None)
self.assertEqual(support_request_in_progress, None)
self.commit()
self.person.notify(
support_request_title="A",
support_request_description="B",
aggregate=aggregate_value.getRelativeUrl()
)
support_request_relative_url = self.person.REQUEST.get(
"support_request_relative_url", None)
self.assertNotEqual(None, support_request_relative_url)
support_request_in_progress = self.person.REQUEST.get(
"support_request_in_progress", None)
self.assertEqual(support_request_in_progress, support_request_relative_url)
# Check if it is the same Support Request as before and still suspended
self.assertEqual(support_request.getRelativeUrl(),
support_request_relative_url)
self.assertEqual(support_request.getSimulationState(), "suspended")
# Step 6: If the support request is closed, create indeed a new one.
support_request.invalidate()
self.tic()
self.person.REQUEST.set("support_request_relative_url", None)
support_request_relative_url = self.person.REQUEST.get(
"support_request_relative_url", None)
self.assertEqual(None, support_request_relative_url)
support_request_in_progress = self.person.REQUEST.set(
"support_request_in_progress", None)
support_request_in_progress = self.person.REQUEST.set(
"support_request_in_progress", None)
self.assertEqual(support_request_in_progress, None)
self.commit()
self.person.notify(
support_request_title="A",
support_request_description="B",
aggregate=aggregate_value.getRelativeUrl()
)
support_request_relative_url = self.person.REQUEST.get(
"support_request_relative_url", None)
self.assertNotEqual(None, support_request_relative_url)
support_request_in_progress = self.person.REQUEST.get(
"support_request_in_progress", None)
self.assertEqual(support_request_in_progress, support_request_relative_url)
# Check if it is the another Support Request
self.assertEqual(support_request.getSimulationState(), "invalidated")
self.assertNotEqual(support_request.getRelativeUrl(),
support_request_relative_url)
from DateTime import DateTime
portal = context.getPortalObject()
aggregate_value = portal.restrictedTraverse(source_relative_url)
if aggregate_value.getPortalType() == "Compute Node":
destination_decision = aggregate_value.getSourceAdministration()
elif aggregate_value.getPortalType() == "Software Instance":
destination_decision = aggregate_value.getSpecialiseValue().getDestinationSection()
elif aggregate_value.getPortalType() == "Instance Tree":
destination_decision = aggregate_value.getDestinationSection()
elif aggregate_value.getPortalType() == "Software Installation":
destination_decision = aggregate_value.getDestinationSection()
else:
destination_decision = None
if portal.ERP5Site_isSupportRequestCreationClosed(destination_decision):
# Stop ticket creation
return
person = state_change['object']
portal = person.getPortalObject()
# Get required arguments
kwargs = state_change.kwargs
# Required args
# Raise TypeError if all parameters are not provided
try:
support_request_title = kwargs['support_request_title']
description = kwargs['support_request_description']
# Aggregate can be None, so it isn't included on the kwargs
aggregate = kwargs["aggregate"]
except KeyError:
raise TypeError, "Person_requestSupportRequest takes exactly 3 arguments"
aggregate_value = portal.restrictedTraverse(aggregate)
support_request_in_progress = portal.portal_catalog.getResultValue(
portal_type = 'Support Request',
title = title,
title = support_request_title,
simulation_state = ["validated", "submitted", "suspended"],
default_aggregate_uid = aggregate_value.getUid(),
)
if support_request_in_progress is not None:
return support_request_in_progress
context.REQUEST.set("support_request_relative_url",
support_request_in_progress.getRelativeUrl())
context.REQUEST.set("support_request_in_progress",
support_request_in_progress.getRelativeUrl())
return
support_request_in_progress = context.REQUEST.get("support_request_in_progress", None)
if support_request_in_progress is not None:
support_request = portal.restrictedTraverse(support_request_in_progress, None)
if support_request and support_request.getTitle() == title and \
if support_request and support_request.getTitle() == support_request_title and \
support_request.getAggregateUid() == aggregate_value.getUid():
return portal.restrictedTraverse(support_request_in_progress)
context.REQUEST.set("support_request_relative_url", support_request_in_progress)
return
# Ensure resoure is Monitoring
resource = portal.service_module.\
......@@ -45,15 +47,14 @@ support_request = portal.restrictedTraverse(
.Base_createCloneDocument(batch_mode=1)
support_request.edit(
title = title,
title = support_request_title,
description = description,
start_date = DateTime(),
destination_decision=destination_decision,
aggregate_value = aggregate_value,
destination_decision=person.getRelativeUrl(),
aggregate_value=aggregate_value,
resource=resource
)
support_request.validate()
context.REQUEST.set("support_request_relative_url", support_request.getRelativeUrl())
context.REQUEST.set("support_request_in_progress", support_request.getRelativeUrl())
return support_request
......@@ -2,7 +2,7 @@
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
<global name="Workflow Script" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
......@@ -50,11 +50,25 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>message_title, message, destination_relative_url</string> </value>
<value> <string>state_change</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SupportRequest_trySendNotificationMessage</string> </value>
<value> <string>script_Person_notify</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Script</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Person_notify</string> </value>
</item>
</dictionary>
</pickle>
......
person = state_change['object']
portal = person.getPortalObject()
# Get required arguments
kwargs = state_change.kwargs
# Required args
# Raise TypeError if all parameters are not provided
try:
support_request_title = kwargs['support_request_title']
resource = kwargs['support_request_resource']
description = kwargs['support_request_description']
# Aggregate can be None, so it isn't included on the kwargs
aggregate = kwargs.get("support_request_aggregate", None)
except KeyError:
raise TypeError, "Person_requestSupportRequest takes exactly 4 arguments"
tag = "%s_%s_SupportRequestInProgress" % (person.getUid(),
support_request_title)
if (portal.portal_activities.countMessageWithTag(tag) > 0):
# The software instance is already under creation but can not be fetched from catalog
# As it is not possible to fetch informations, it is better to raise an error
raise NotImplementedError(tag)
support_request_portal_type = "Support Request"
module = portal.getDefaultModule(portal_type=support_request_portal_type)
support_request = module.newContent(
portal_type=support_request_portal_type,
title=support_request_title,
description=description,
resource=resource,
destination_decision_value=person,
aggregate=aggregate,
specialise="sale_trade_condition_module/slapos_ticket_trade_condition",
activate_kw={'tag': tag}
)
context.REQUEST.set("support_request_relative_url", support_request.getRelativeUrl())
support_request.approveRegistration()
......@@ -2,7 +2,7 @@
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
<global name="Workflow Script" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
......@@ -50,11 +50,25 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
<value> <string>state_change</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>UpgradeDecision_upgradeInstanceTree</string> </value>
<value> <string>script_Person_requestSupportRequest</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Script</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Person_requestSupportRequest</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -16,12 +16,14 @@
<key> <string>categories</string> </key>
<value>
<tuple>
<string>destination/portal_workflow/person_slap_interface_workflow/transition_request_network</string>
<string>destination/portal_workflow/person_slap_interface_workflow/transition_notify</string>
<string>destination/portal_workflow/person_slap_interface_workflow/transition_request_compute_node</string>
<string>destination/portal_workflow/person_slap_interface_workflow/transition_request_network</string>
<string>destination/portal_workflow/person_slap_interface_workflow/transition_request_organisation</string>
<string>destination/portal_workflow/person_slap_interface_workflow/transition_request_project</string>
<string>destination/portal_workflow/person_slap_interface_workflow/transition_request_site</string>
<string>destination/portal_workflow/person_slap_interface_workflow/transition_request_software_instance</string>
<string>destination/portal_workflow/person_slap_interface_workflow/transition_request_support</string>
<string>destination/portal_workflow/person_slap_interface_workflow/transition_request_token</string>
</tuple>
</value>
......
......@@ -17,10 +17,14 @@
<value>
<tuple>
<string>destination/portal_workflow/person_slap_interface_workflow/transition_draft</string>
<string>destination/portal_workflow/person_slap_interface_workflow/transition_notify</string>
<string>destination/portal_workflow/person_slap_interface_workflow/transition_request_compute_node</string>
<string>destination/portal_workflow/person_slap_interface_workflow/transition_request_network</string>
<string>destination/portal_workflow/person_slap_interface_workflow/transition_request_organisation</string>
<string>destination/portal_workflow/person_slap_interface_workflow/transition_request_project</string>
<string>destination/portal_workflow/person_slap_interface_workflow/transition_request_site</string>
<string>destination/portal_workflow/person_slap_interface_workflow/transition_request_software_instance</string>
<string>destination/portal_workflow/person_slap_interface_workflow/transition_request_support</string>
<string>destination/portal_workflow/person_slap_interface_workflow/transition_request_token</string>
</tuple>
</value>
......
......@@ -2,61 +2,61 @@
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Interaction Workflow Interaction" module="erp5.portal_type"/>
<global name="Workflow Transition" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>categories</string> </key>
<key> <string>action</string> </key>
<value>
<tuple>
<string>before_commit_script/portal_workflow/slapos_erp5_interaction_workflow/script_WebMessage_stopAndReopenTicket</string>
</tuple>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<key> <string>action_name</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>interaction_WebMessage_setFollowUp</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Interaction Workflow Interaction</string> </value>
</item>
<item>
<key> <string>portal_type_filter</string> </key>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>Web Message</string>
<string>action_type/workflow</string>
<string>before_script/portal_workflow/person_slap_interface_workflow/script_Person_checkConsistency</string>
<string>after_script/portal_workflow/person_slap_interface_workflow/script_Person_notify</string>
</tuple>
</value>
</item>
<item>
<key> <string>portal_type_group_filter</string> </key>
<key> <string>description</string> </key>
<value>
<tuple/>
<none/>
</value>
</item>
<item>
<key> <string>temporary_document_disallowed</string> </key>
<value> <int>0</int> </value>
<key> <string>guard_permission</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>trigger_method_id</string> </key>
<key> <string>icon</string> </key>
<value>
<tuple>
<string>_setFollowUp*</string>
</tuple>
<none/>
</value>
</item>
<item>
<key> <string>trigger_once_per_transaction</string> </key>
<value> <int>1</int> </value>
<key> <string>id</string> </key>
<value> <string>transition_notify</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Transition</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Notify</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
......
......@@ -2,61 +2,61 @@
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Interaction Workflow Interaction" module="erp5.portal_type"/>
<global name="Workflow Transition" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>categories</string> </key>
<key> <string>action</string> </key>
<value>
<tuple>
<string>before_commit_script/portal_workflow/slapos_erp5_interaction_workflow/script_SupportRequest_createInitialWebMessage</string>
</tuple>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<key> <string>action_name</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>interaction_SupportRequest_setSpecialise</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Interaction Workflow Interaction</string> </value>
</item>
<item>
<key> <string>portal_type_filter</string> </key>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>Support Request</string>
<string>action_type/workflow</string>
<string>before_script/portal_workflow/person_slap_interface_workflow/script_Person_checkConsistency</string>
<string>after_script/portal_workflow/person_slap_interface_workflow/script_Person_requestSupportRequest</string>
</tuple>
</value>
</item>
<item>
<key> <string>portal_type_group_filter</string> </key>
<key> <string>description</string> </key>
<value>
<tuple/>
<none/>
</value>
</item>
<item>
<key> <string>temporary_document_disallowed</string> </key>
<value> <int>0</int> </value>
<key> <string>guard_permission</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>trigger_method_id</string> </key>
<key> <string>icon</string> </key>
<value>
<tuple>
<string>_setSpecialise*</string>
</tuple>
<none/>
</value>
</item>
<item>
<key> <string>trigger_once_per_transaction</string> </key>
<value> <int>1</int> </value>
<key> <string>id</string> </key>
<value> <string>transition_request_support</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Transition</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Request Support</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
......
......@@ -7,4 +7,8 @@
<type>Regularisation Request</type>
<workflow>edit_workflow, pricing_interaction_workflow, ticket_interaction_workflow, ticket_workflow</workflow>
</chain>
<chain>
<type>Support Request</type>
<workflow>ticket_slap_interface_workflow</workflow>
</chain>
</workflow_chain>
\ No newline at end of file
......@@ -29,11 +29,12 @@ request_title = 'Allocation scope of %s changed to %s' % (compute_node_reference
request_description = 'Allocation scope has been changed to ' \
'%s for %s' % (target_allocation_scope, compute_node_reference)
support_request = context.Base_generateSupportRequestForSlapOS(
request_title,
request_description,
compute_node.getRelativeUrl()
)
person.notify(support_request_title=request_title,
support_request_description=request_description,
aggregate=context.getRelativeUrl())
support_request_relative_url = context.REQUEST.get("support_request_relative_url")
support_request = portal.restrictedTraverse(support_request_relative_url)
if support_request is not None:
if support_request.getSimulationState() != "validated":
......@@ -53,8 +54,8 @@ if support_request is not None:
message = notification_message.asText(
substitution_method_parameter_dict={'mapping_dict': mapping_dict})
event = support_request.SupportRequest_trySendNotificationMessage(
request_title, message, person.getRelativeUrl())
support_request.notify(message_title=request_title, message=message)
event = support_request.REQUEST.get("ticket_notified_item")
if event is not None:
# event added, suspend ticket
......
from DateTime import DateTime
portal = context.getPortalObject()
if portal.ERP5Site_isSupportRequestCreationClosed():
# Stop ticket creation
return
if context.getMonitorScope() == "disabled":
person = context.getSourceAdministrationValue(portal_type="Person")
if not person or \
context.getMonitorScope() == "disabled" or \
portal.ERP5Site_isSupportRequestCreationClosed():
return
software_installation_list = portal.portal_catalog(
......@@ -50,15 +49,13 @@ for software_installation in software_installation_list:
(software_installation.getUrlString(), compute_node_title, software_installation.getCreationDate())
if should_notify:
support_request = context.Base_generateSupportRequestForSlapOS(
ticket_title,
description,
software_installation.getRelativeUrl()
)
person.notify(support_request_title=ticket_title,
support_request_description=description,
aggregate=software_installation.getRelativeUrl())
person = context.getSourceAdministrationValue(portal_type="Person")
if not person:
return support_request
support_request = context.REQUEST.get("support_request_relative_url")
if support_request is None:
return
# Send Notification message
notification_reference = 'slapos-crm-compute_node_software_installation_state.notification'
......@@ -75,10 +72,7 @@ for software_installation in software_installation_list:
message = notification_message.asText(
substitution_method_parameter_dict={'mapping_dict':mapping_dict})
support_request.SupportRequest_trySendNotificationMessage(
ticket_title,
message, person.getRelativeUrl())
support_request.notify(message_title=ticket_title, message=message)
support_request_list.append(support_request)
return support_request_list
from DateTime import DateTime
portal = context.getPortalObject()
if portal.ERP5Site_isSupportRequestCreationClosed():
# Stop ticket creation
return
if context.getMonitorScope() == "disabled":
person = context.getSourceAdministrationValue(portal_type="Person")
if not person or \
context.getMonitorScope() == "disabled" or \
portal.ERP5Site_isSupportRequestCreationClosed():
return
if context.getAllocationScope("open").startswith("close"):
......@@ -18,7 +17,6 @@ ticket_title = "[MONITORING] Lost contact with compute_node %s" % reference
description = ""
last_contact = "No Contact Information"
d = context.getAccessStatus()
# Ignore if data isn't present.
if d.get("no_data") == 1:
......@@ -33,20 +31,19 @@ else:
# Nothing to notify.
return
support_request = context.Base_generateSupportRequestForSlapOS(
ticket_title,
description,
context.getRelativeUrl()
)
person.notify(support_request_title=ticket_title,
support_request_description=description,
aggregate=context.getRelativeUrl())
person = context.getSourceAdministrationValue(portal_type="Person")
if not person:
return support_request
support_request_relative_url = context.REQUEST.get("support_request_relative_url")
if support_request_relative_url is None:
return
support_request = portal.restrictedTraverse(support_request_relative_url)
# Send Notification message
notification_reference = 'slapos-crm-compute_node_check_state.notification'
notification_message = portal.portal_notifications.getDocumentValue(
reference=notification_reference)
reference='slapos-crm-compute_node_check_state.notification')
if notification_message is None:
message = """%s""" % description
......@@ -55,10 +52,8 @@ else:
'compute_node_id':reference,
'last_contact':last_contact}
message = notification_message.asText(
substitution_method_parameter_dict={'mapping_dict':mapping_dict})
substitution_method_parameter_dict={'mapping_dict': mapping_dict})
support_request.SupportRequest_trySendNotificationMessage(
ticket_title,
message, person.getRelativeUrl())
support_request.notify(message_title=ticket_title, message=message)
return support_request
portal = context.getPortalObject()
person = context.getDestinationSectionValue()
if person is None or portal.ERP5Site_isSupportRequestCreationClosed(person.getRelativeUrl()):
# Stop ticket creation
return
ticket_title = "Instance Tree %s is failing." % context.getTitle()
error_message = instance.SoftwareInstance_hasReportedError(include_message=True)
......@@ -10,27 +16,23 @@ if error_message:
else:
error_message = "No message!"
support_request = context.Base_generateSupportRequestForSlapOS(
ticket_title,
description,
context.getRelativeUrl())
person.notify(support_request_title=ticket_title,
support_request_description=description,
aggregate=context.getRelativeUrl())
if support_request is None:
support_request_relative_url = context.REQUEST.get("support_request_relative_url")
if support_request_relative_url is None:
return
person = context.getDestinationSectionValue(portal_type="Person")
if not person:
return
support_request = portal.restrictedTraverse(support_request_relative_url)
if support_request.getSimulationState() not in ["validated", "suspended"]:
support_request.validate()
# Send Notification message
message = description
notification_reference = notification_message_reference
notification_message = portal.portal_notifications.getDocumentValue(
reference=notification_reference)
reference=notification_message_reference)
if notification_message is not None:
mapping_dict = {'instance_tree_title':context.getTitle(),
'instance': instance.getTitle(),
......@@ -39,5 +41,5 @@ if notification_message is not None:
message = notification_message.asText(
substitution_method_parameter_dict={'mapping_dict':mapping_dict})
return support_request.SupportRequest_trySendNotificationMessage(
ticket_title, message, person.getRelativeUrl())
support_request.notify(message_title=ticket_title, message=message)
return context.REQUEST.get("ticket_notified_item")
""" Close Support Request which are related to a Destroy Requested Instance. """
portal = context.getPortalObject()
if context.getSimulationState() == "invalidated":
return
document = context.getAggregateValue()
if document is not None and document.getSlapState() == "destroy_requested":
person = context.getDestinationDecision(portal_type="Person")
if not person:
return
if context.getSimulationState() != "invalidated":
context.invalidate()
# Send Notification message
message = """ Closing this ticket as the Instance Tree was destroyed by the user.
"""
notification_reference = "slapos-crm-support-request-close-destroyed-notification"
portal = context.getPortalObject()
notification_message = portal.portal_notifications.getDocumentValue(
reference=notification_reference)
reference="slapos-crm-support-request-close-destroyed-notification")
if notification_message is not None:
mapping_dict = {'instance_tree_title':document.getTitle()}
mapping_dict = {'instance_tree_title': document.getTitle()}
message = notification_message.asText(
substitution_method_parameter_dict={'mapping_dict':mapping_dict})
return context.SupportRequest_trySendNotificationMessage(
"Instance Tree was destroyed was destroyed by the user", message, person)
context.notify(message_title="Instance Tree was destroyed was destroyed by the user",
message=message)
context.invalidate()
return context.REQUEST.get("ticket_notified_item")
......@@ -2,7 +2,6 @@ if context.getSimulationState() == "invalidated":
return
document = context.getAggregateValue(portal_type="Instance Tree")
if document is None:
return
......
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2002-2012 Nexedi SA and Contributors. All Rights Reserved.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from erp5.component.test.SlapOSTestCaseMixin import SlapOSTestCaseMixin #, simulate
import transaction
class TestSlapOSCoreTicketSlapInterfaceWorkflow(SlapOSTestCaseMixin):
def afterSetUp(self):
SlapOSTestCaseMixin.afterSetUp(self)
portal = self.getPortalObject()
self.ticket_trade_condition = portal.sale_trade_condition_module.slapos_ticket_trade_condition
person_user = self.makePerson()
self.tic()
# Login as new user
self.login(person_user.getUserId())
new_person = self.portal.portal_membership.getAuthenticatedMember().getUserValue()
self.assertEqual(person_user.getRelativeUrl(), new_person.getRelativeUrl())
self.support_request = portal.support_request_module.newContent(
portal_type="Support Request",
destination_decision=person_user.getRelativeUrl(),
specialise=self.ticket_trade_condition.getRelativeUrl()
)
# Value set by the init
self.assertTrue(self.support_request.getReference().startswith("SR-"),
"Reference don't start with SR- : %s" % self.support_request.getReference())
def beforeTearDown(self):
transaction.abort()
def test_SupportRequest_approveRegistration_no_reference(self):
self.support_request.setReference(None)
self.assertRaises(ValueError, self.support_request.approveRegistration)
def test_SupportRequest_approveRegistration_already_validated(self):
# Login as admin since user cannot re-approve a validated project
self.login()
self.support_request.validate()
# Don't raise if support request is validated
self.assertEqual(self.support_request.approveRegistration(), None)
def test_SupportRequest_approveRegistration(self):
person = self.portal.portal_membership.getAuthenticatedMember().getUserValue()
self.support_request.approveRegistration()
self.tic()
self.logout()
self.login(person.getUserId())
self.assertEqual(self.support_request.getSimulationState(),
'validated')
self.assertEqual(self.support_request.getSourceSection(),
self.ticket_trade_condition.getSourceSection())
self.assertEqual(self.support_request.getSourceTrade(),
self.ticket_trade_condition.getSourceSection())
self.assertEqual(self.support_request.getSource(),
self.ticket_trade_condition.getSource())
self.assertNotEqual(self.support_request.getStartDate(),
None)
event = self.support_request.getCausalityValue()
self.assertNotEqual(event, None)
event_relative_url = self.support_request.REQUEST.get("event_relative_url")
self.assertEqual(event.getRelativeUrl(), event_relative_url)
self.assertEqual(event.getTitle(), self.support_request.getTitle())
def test_SupportRequest_requestEvent_noParameter(self):
self.assertRaises(TypeError, self.support_request.requestEvent)
self.assertRaises(TypeError, self.support_request.requestEvent, event_title="A")
self.assertRaises(TypeError, self.support_request.requestEvent, event_content="A")
def test_SupportRequest_requestEvent(self):
person = self.portal.portal_membership.getAuthenticatedMember().getUserValue()
self.support_request.approveRegistration()
self.tic()
self.logout()
self.login(person.getUserId())
self.support_request.requestEvent(
event_title="A",
event_content="B"
)
self.tic()
event_relative_url = self.support_request.REQUEST.get("event_relative_url")
event = self.portal.restrictedTraverse(event_relative_url)
self.assertEqual(event.getSimulationState(), "stopped")
self.assertEqual(self.support_request.getSimulationState(),
'validated')
self.assertEqual(self.support_request.getDestinationDecision(),
event.getSource())
self.assertEqual(person, event.getSourceValue())
self.assertEqual(self.support_request.getResource(),
event.getResource())
self.assertEqual(self.support_request,
event.getFollowUpValue())
self.assertEqual(event.getTitle(), "A")
self.assertEqual(event.getTextContent(), "B")
self.assertEqual(event.getContentType(), "text/plain")
self.assertEqual(event.getPortalType(), "Web Message")
self.assertEqual(event.getDestination(),
self.support_request.getSource())
def test_SupportRequest_notify_noParameter(self):
self.assertRaises(TypeError, self.support_request.notify)
self.assertRaises(TypeError, self.support_request.notify, message_title="A")
self.assertRaises(TypeError, self.support_request.notify, message="A")
self.assertRaises(TypeError, self.support_request.notify, destination_relative_url="A")
def test_SupportRequest_notify(self):
person = self.portal.portal_membership.getAuthenticatedMember().getUserValue()
self.support_request.approveRegistration()
self.tic()
self.logout()
self.login()
self.support_request.notify(
message_title="A",
message="B")
event = self.support_request.REQUEST.get("ticket_notified_item")
self.assertEqual(event.getSimulationState(), "delivered")
self.assertEqual(self.support_request.getSimulationState(),
'validated')
self.assertEqual(self.support_request.getDestinationDecision(),
event.getDestination())
self.assertEqual(person, event.getDestinationValue())
self.assertEqual("service_module/slapos_crm_information",
event.getResource())
self.assertEqual(self.support_request,
event.getFollowUpValue())
self.assertEqual(event.getTitle(), "A")
self.assertEqual(event.getTextContent(), "B")
self.assertEqual(event.getContentType(), "text/html")
self.assertEqual(event.getPortalType(), "Web Message")
self.assertEqual(event.getSource(),
self.support_request.getSource())
# Retry now to see if doesn't create a new message
self.support_request.notify(
message_title="A",
message="B")
self.tic()
self.assertEqual(event,
self.support_request.REQUEST.get("ticket_notified_item"))
# Retry, now it must create a new one
self.support_request.notify(
message_title="C",
message="B")
self.tic()
self.assertNotEqual(event,
self.support_request.REQUEST.get("ticket_notified_item"))
# Remove completly the ticket_notified_item and try to create a new one
# It should find it anyway from catalog.
self.support_request.REQUEST.set("ticket_notified_item", None)
self.commit()
# Retry, now it must create a new one
self.support_request.notify(
message_title="A",
message="B")
self.tic()
self.assertEqual(event,
self.support_request.REQUEST.get("ticket_notified_item"))
<?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>testSlapOSCloudTicketSlapInterfaceWorkflow</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test.erp5.testSlapOSCloudTicketSlapInterfaceWorkflow</string> </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>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.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<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>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_count</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_mt_index</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>_tree</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>source/portal_workflow/ticket_slap_interface_workflow/state_draft</string>
</tuple>
</value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>person_slap_interface_workflow</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ticket_slap_interface_workflow</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>manager_bypass</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow</string> </value>
</item>
<item>
<key> <string>state_variable</string> </key>
<value> <string>slap_state</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Ticket Slap Interface Workflow</string> </value>
</item>
<item>
<key> <string>workflow_managed_permission</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Length" module="BTrees.Length"/>
</pickle>
<pickle> <int>0</int> </pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
</ZopeData>
ticket = state_object["object"]
ticket = state_change["object"]
from DateTime import DateTime
portal = context.getPortalObject()
# XXX unhardcode the trade condition, by adding a preference
if ticket.getSpecialise() != "sale_trade_condition_module/slapos_ticket_trade_condition":
if ticket.getSimulationState() != "draft":
return
if ticket.getSimulationState() != "draft":
if ticket.getReference() in [None, ""]:
raise ValueError("Reference is missing on the Ticket")
# Get the user id of the context owner.
local_role_list = ticket.get_local_roles()
for group, role_list in local_role_list:
if 'Owner' in role_list:
user_id = group
break
person = portal.portal_catalog.getResultValue(user_id=user_id)
if person is None:
# Value was created by super user, so there isn't a point on continue
return
ticket.validate()
# XXX unhardcode the trade condition, by adding a preference
if ticket.getSpecialise() != "sale_trade_condition_module/slapos_ticket_trade_condition":
return
trade_condition = portal.sale_trade_condition_module.slapos_ticket_trade_condition
web_message = context.Ticket_createInitialEvent(state_object)
ticket.edit(
source_section = trade_condition.getSourceSection(),
source_section=trade_condition.getSourceSection(),
source_trade=trade_condition.getSourceSection(),
source=trade_condition.getSource(),
causality_value=web_message)
source=trade_condition.getSource())
ticket.setStartDate(DateTime())
# Copy original post into the original message.
web_message.edit(
title=ticket.getTitle(),
# content_type="text/plain",
text_content=ticket.getDescription(),
source=ticket.getDestinationDecision(),
# destination=trade_condition.getSource(),
# resource=ticket.getResource(),
# start_date=DateTime(),
follow_up_value=ticket,
ticket.requestEvent(
event_title=ticket.getTitle(),
event_content=ticket.getDescription()
)
event_relative_url = context.REQUEST.get("event_relative_url")
ticket.setCausality(event_relative_url)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Script" module="erp5.portal_type"/>
</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>state_change</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>script_Ticket_approveRegistration</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Script</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Ticket_approveRegistration</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -50,7 +50,7 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>state_object</string> </value>
<value> <string>state_change</string> </value>
</item>
<item>
<key> <string>description</string> </key>
......@@ -60,7 +60,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>script_Ticket_createInitialEvent</string> </value>
<value> <string>script_Ticket_checkConsistency</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
......
ticket = state_change["object"]
from DateTime import DateTime
portal = ticket.getPortalObject()
# Get required arguments
kwargs = state_change.kwargs
# Required args
# Raise TypeError if all parameters are not provided
try:
message_title = kwargs['message_title']
message = kwargs['message']
except KeyError:
raise TypeError("Ticket_notify takes exactly 2 arguments")
resource = portal.service_module.slapos_crm_information.getRelativeUrl()
# create Web message if needed for this ticket
last_event = ticket.portal_catalog.getResultValue(
title=message_title,
follow_up_uid=ticket.getUid(),
sort_on=[('delivery.start_date', 'DESC')],
)
if last_event:
# User has already been notified for this problem.
ticket.REQUEST.set("ticket_notified_item", last_event)
return
transactional_event = ticket.REQUEST.get("ticket_notified_item", None)
if transactional_event is not None:
if (transactional_event.getFollowUpUid() == ticket.getUid()) and \
(transactional_event.getTitle() == message_title):
ticket.REQUEST.set("ticket_notified_item", transactional_event)
return
template = portal.restrictedTraverse(
portal.portal_preferences.getPreferredWebMessageTemplate())
event = template.Base_createCloneDocument(batch_mode=1)
event.edit(
title=message_title,
text_content=message,
start_date = DateTime(),
resource = resource,
source=ticket.getSource(),
destination=ticket.getDestinationDecision(),
follow_up=ticket.getRelativeUrl(),
)
event.stop()
event.deliver()
ticket.serialize()
ticket.REQUEST.set("ticket_notified_item", event)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Script" module="erp5.portal_type"/>
</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>state_change</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>script_Ticket_notify</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Script</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Ticket_notify</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
ticket = state_change["object"]
from DateTime import DateTime
portal = context.getPortalObject()
# Get required arguments
kwargs = state_change.kwargs
# Required args
# Raise TypeError if all parameters are not provided
try:
title = kwargs['event_title']
text_content = kwargs['event_content']
except KeyError:
raise TypeError, "Ticket_requestEvent takes exactly 2 argument"
web_message = portal.event_module.newContent(
portal_type="Web Message",
title=title,
text_content=text_content,
source=ticket.getDestinationDecision(),
content_type="text/plain",
destination=ticket.getSource(),
resource=ticket.getResource(),
follow_up=ticket.getRelativeUrl()
)
web_message.stop(comment="Submitted from the renderjs app")
if portal.portal_workflow.isTransitionPossible(ticket, "validate"):
ticket.validate(comment="See %s" % web_message.getRelativeUrl())
ticket.REQUEST.set("event_relative_url", web_message.getRelativeUrl())
......@@ -50,13 +50,14 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>state_object</string> </value>
<value> <string>state_change</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Owner</string>
</tuple>
</value>
</item>
......@@ -68,7 +69,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>script_SupportRequest_createInitialWebMessage</string> </value>
<value> <string>script_Ticket_requestEvent</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
......@@ -76,9 +77,7 @@
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
<value> <string>Ticket_requestEvent</string> </value>
</item>
</dictionary>
</pickle>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow State" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>acquire_permission</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>destination/portal_workflow/ticket_slap_interface_workflow/transition_approve_registration</string>
<string>destination/portal_workflow/ticket_slap_interface_workflow/transition_notify</string>
<string>destination/portal_workflow/ticket_slap_interface_workflow/transition_request_event</string>
</tuple>
</value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>state_draft</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow State</string> </value>
</item>
<item>
<key> <string>state_permission_role_list_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>state_type</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Draft</string> </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>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Transition" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>action_name</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/workflow</string>
<string>before_script/portal_workflow/ticket_slap_interface_workflow/script_Ticket_checkConsistency</string>
<string>after_script/portal_workflow/ticket_slap_interface_workflow/script_Ticket_approveRegistration</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>guard_permission</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>transition_approve_registration</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Transition</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Approve Registration</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Transition" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>action_name</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/workflow</string>
<string>before_script/portal_workflow/ticket_slap_interface_workflow/script_Ticket_checkConsistency</string>
<string>after_script/portal_workflow/ticket_slap_interface_workflow/script_Ticket_notify</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>guard_permission</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>transition_notify</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Transition</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Notify</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Transition" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>action_name</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/workflow</string>
<string>before_script/portal_workflow/ticket_slap_interface_workflow/script_Ticket_checkConsistency</string>
<string>after_script/portal_workflow/ticket_slap_interface_workflow/script_Ticket_requestEvent</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>guard_permission</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>transition_request_event</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Transition</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Request Event</string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Variable" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>automatic_update</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>The last transition</string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>variable_action</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Variable</string> </value>
</item>
<item>
<key> <string>status_included</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>variable_default_expression</string> </key>
<value> <string>transition/getReference|nothing</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Variable" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>automatic_update</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>The name of the user who performed the last transition</string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>variable_actor</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Variable</string> </value>
</item>
<item>
<key> <string>status_included</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>variable_default_expression</string> </key>
<value> <string>user/getUserName</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Variable" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>automatic_update</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Comments about the last transition</string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>variable_comment</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Variable</string> </value>
</item>
<item>
<key> <string>status_included</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>variable_default_expression</string> </key>
<value> <string>python:state_change.kwargs.get(\'comment\', \'\')</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Variable" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>automatic_update</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Error message if validation failed</string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>variable_error_message</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Variable</string> </value>
</item>
<item>
<key> <string>status_included</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Variable" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>automatic_update</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Provides access to workflow history</string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>variable_history</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Variable</string> </value>
</item>
<item>
<key> <string>status_included</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>variable_default_expression</string> </key>
<value> <string>state_change/getHistory</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Variable" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>automatic_update</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>portal type (use as filter for worklists)</string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>variable_portal_type</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Variable</string> </value>
</item>
<item>
<key> <string>status_included</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Variable" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>automatic_update</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Time of the last transition</string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>variable_time</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Variable</string> </value>
</item>
<item>
<key> <string>status_included</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>variable_default_expression</string> </key>
<value> <string>state_change/getDateTime</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -32,6 +32,12 @@
</tuple>
</value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>ticket_workflow</string> </value>
......
......@@ -4,3 +4,4 @@ Regularisation Request | edit_workflow
Regularisation Request | pricing_interaction_workflow
Regularisation Request | ticket_interaction_workflow
Regularisation Request | ticket_workflow
Support Request | ticket_slap_interface_workflow
\ No newline at end of file
test.erp5.testSlapOSCRMSkins
test.erp5.testSlapOSCRMRegularisationRequestSkins
test.erp5.testSlapOSCRMAlarm
test.erp5.testSlapOSCloudTicketSlapInterfaceWorkflow
\ No newline at end of file
ticket_slap_interface_workflow
ticket_workflow
\ No newline at end of file
......@@ -123,14 +123,6 @@
<type>Software Instance</type>
<workflow>local_permission_slapos_interaction_workflow</workflow>
</chain>
<chain>
<type>Support Request</type>
<workflow>slapos_erp5_interaction_workflow</workflow>
</chain>
<chain>
<type>Web Message</type>
<workflow>slapos_erp5_interaction_workflow</workflow>
</chain>
<chain>
<type>Wechat Event</type>
<workflow>local_permission_slapos_interaction_workflow</workflow>
......
# Added this script to be running as Owner
portal = context.getPortalObject()
return portal.event_module.newContent(
portal_type="Web Message",
)
web_message = state_object["object"]
portal = context.getPortalObject()
person = portal.portal_membership.getAuthenticatedMember().getUserValue()
if person is None:
return
if web_message.getSimulationState() != "draft":
return
if not web_message.hasStartDate():
web_message.setStartDate(DateTime())
# The user isn't the customer, so it is not comming from the user
# interface.
ticket = web_message.getFollowUpValue()
if ticket.getDestinationDecision() != person.getRelativeUrl():
return
# The user isn't the sender, so it is not comming from UI, but from
# an alarm.
source = web_message.getSource()
if source != person.getRelativeUrl():
return
edit_kw = {"content_type":"text/plain"}
# Copy destination and resource from ticket.
if web_message.getDestination() is None:
edit_kw["destination"] = ticket.getSource()
if web_message.getResource() is None:
edit_kw["resource"] = ticket.getResource()
web_message.edit(**edit_kw)
web_message.stop(comment="Submitted from the renderjs app")
if portal.portal_workflow.isTransitionPossible(ticket, "validate"):
ticket.validate(comment="See %s" % web_message.getRelativeUrl())
......@@ -34,6 +34,4 @@ Sale Packing List | local_permission_slapos_interaction_workflow
Slave Instance | local_permission_slapos_interaction_workflow
Software Installation | local_permission_slapos_interaction_workflow
Software Instance | local_permission_slapos_interaction_workflow
Support Request | slapos_erp5_interaction_workflow
Web Message | slapos_erp5_interaction_workflow
Wechat Event | local_permission_slapos_interaction_workflow
\ No newline at end of file
......@@ -11,9 +11,10 @@
.declareAcquiredMethod("updatePanel", "updatePanel")
.declareAcquiredMethod("jio_getAttachment", "jio_getAttachment")
.declareAcquiredMethod("getSetting", "getSetting")
.declareAcquiredMethod("getSettingList", "getSettingList")
.declareAcquiredMethod("getUrlFor", "getUrlFor")
.declareAcquiredMethod("redirect", "redirect")
.declareAcquiredMethod("jio_post", "jio_post")
.declareAcquiredMethod("jio_putAttachment", "jio_putAttachment")
.declareAcquiredMethod("jio_get", "jio_get")
.declareAcquiredMethod("notifySubmitting", "notifySubmitting")
.declareAcquiredMethod("notifySubmitted", 'notifySubmitted')
......@@ -26,6 +27,7 @@
return this.triggerSubmit();
})
.onEvent('submit', function () {
var gadget = this;
return gadget.notifySubmitting()
......@@ -33,28 +35,31 @@
return gadget.getDeclaredGadget('form_view');
})
.push(function (form_gadget) {
return form_gadget.getContent();
return RSVP.all([form_gadget.getContent(),
gadget.getSettingList(['me', 'hateoas_url'])]);
})
.push(function (content) {
var property, doc = {};
for (property in content) {
if ((content.hasOwnProperty(property)) &&
// Remove undefined keys added by Gadget fields
(property !== "undefined") &&
// Remove default_*:int keys added by ListField
!(property.endsWith(":int") && property.startsWith("default_"))) {
doc[property] = content[property];
}
}
return gadget.jio_post(doc);
.push(function (result) {
var doc = result[0],
me = result[1][0],
url = result[1][1];
return gadget.jio_putAttachment(me,
url + me + "/Person_requestSupport",
{title: doc.title,
description: doc.description,
resource: doc.resource});
})
.push(function (attachment) {
return jIO.util.readBlobAsText(attachment.target.response);
})
.push(function (key) {
.push(function (response) {
return JSON.parse(response.target.result);
})
.push(function (result) {
return gadget.notifySubmitted({message: gadget.message_translation, status: 'success'})
.push(function () {
// Workaround, find a way to open document without break gadget.
return gadget.redirect({"command": "change",
"options": {"jio_key": key, "page": "slap_controller"}});
"options": {"jio_key": result.relative_url, "page": "slap_controller"}});
});
});
})
......@@ -88,20 +93,19 @@
gadget.getDeclaredGadget('form_view'),
gadget.jio_getAttachment("ticket_resource_list",
hateoas_url + "Ticket_getResourceItemListAsJSON"),
window.getSettingMe(gadget),
gadget.getTranslationList(translation_list)
]);
})
.push(function (result) {
gadget.message_translation = result[3][0];
page_title_translation = result[3][10];
gadget.message_translation = result[2][0];
page_title_translation = result[2][10];
return result[0].render({
erp5_document: {
"_embedded": {"_view": {
"my_title": {
"description": result[3][1],
"title": result[3][2],
"description": result[2][1],
"title": result[2][2],
"default": "",
"css_class": "",
"required": 1,
......@@ -112,7 +116,7 @@
},
"my_description": {
"description": "",
"title": result[3][3],
"title": result[2][3],
"default": "",
"css_class": "",
"required": 1,
......@@ -122,8 +126,8 @@
"type": "TextAreaField"
},
"my_resource": {
"description": result[3][0],
"title": result[3][4],
"description": result[2][0],
"title": result[2][4],
"default": "",
"items": result[1],
"css_class": "",
......@@ -132,52 +136,6 @@
"key": "resource",
"hidden": 0,
"type": "ListField"
},
"my_destination_decision": {
"description": result[3][0],
"title": result[3][5],
"default": result[2],
"css_class": "",
"required": 1,
"editable": 1,
"key": "destination_decision",
"hidden": 1,
"type": "StringField"
},
"my_specialise": {
"description": "",
"title": result[3][6],
// Auto Set a hardcoded trade Condition
// Please replace it by a getSetting.
"default": "sale_trade_condition_module/slapos_ticket_trade_condition",
"css_class": "",
"required": 1,
"editable": 1,
"key": "specialise",
"hidden": 1,
"type": "StringField"
},
"my_portal_type": {
"description": result[3][0],
"title": result[3][7],
"default": "Support Request",
"css_class": "",
"required": 1,
"editable": 1,
"key": "portal_type",
"hidden": 1,
"type": "StringField"
},
"my_parent_relative_url": {
"description": "",
"title": result[3][9],
"default": "support_request_module",
"css_class": "",
"required": 1,
"editable": 1,
"key": "parent_relative_url",
"hidden": 1,
"type": "StringField"
}
}},
"_links": {
......@@ -193,7 +151,7 @@
[["my_resource"]]
], [
"center",
[["my_title"], ["my_description"], ["my_specialise"], ["my_destination_decision"], ["my_portal_type"], ["my_parent_relative_url"]]
[["my_title"], ["my_description"]]
]]
}
});
......
......@@ -279,7 +279,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>987.11835.35244.22135</string> </value>
<value> <string>997.18055.28455.10120</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -297,7 +297,7 @@
</tuple>
<state>
<tuple>
<float>1602260862.83</float>
<float>1641264462.37</float>
<string>UTC</string>
</tuple>
</state>
......
import json
portal = context.getPortalObject()
person = portal.portal_membership.getAuthenticatedMember().getUserValue()
request = context.REQUEST
response = request.RESPONSE
if person is None:
response.setStatus(403)
else:
request_kw = {"support_request_title": title,
"support_request_description": description,
"support_request_resource": resource,
"support_request_aggregate": aggregate}
person.requestSupport(**request_kw)
support_relative_url = request.get('support_request_relative_url')
response.setHeader('Content-Type', "application/json")
return json.dumps({
"relative_url": support_relative_url
})
......@@ -50,11 +50,11 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
<value> <string>title, description, resource, aggregate=None</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>UpgradeDecision_getComputeNode</string> </value>
<value> <string>Person_requestSupport</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -2,14 +2,10 @@
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
......@@ -24,18 +20,6 @@
<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>
......@@ -49,12 +33,24 @@
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>UpgradeDecision_getInstanceTree</string> </value>
<value> <string>testSlapOSJSServerTicketRelated</string> </value>
</item>
<item>
<key> <string>output_encoding</string> </key>
<value> <string>utf-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <unicode></unicode> </value>
</item>
</dictionary>
</pickle>
......
......@@ -2,14 +2,10 @@
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
......@@ -24,18 +20,6 @@
<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>
......@@ -49,12 +33,24 @@
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>UpgradeDecision_upgradeComputeNode</string> </value>
<value> <string>testSlapOSJSServiceTicketRelated</string> </value>
</item>
<item>
<key> <string>output_encoding</string> </key>
<value> <string>utf-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <unicode></unicode> </value>
</item>
</dictionary>
</pickle>
......
......@@ -5,6 +5,6 @@
</chain>
<chain>
<type>Upgrade Decision</type>
<workflow>edit_workflow, ticket_interaction_workflow, upgrade_decision_workflow</workflow>
<workflow>edit_workflow, ticket_interaction_workflow, upgrade_decision_workflow, upgrade_slap_interface_workflow</workflow>
</chain>
</workflow_chain>
\ No newline at end of file
portal = context.getPortalObject()
portal.portal_catalog.searchAndActivate(
portal_type="Upgrade Decision Line",
simulation_state=["confirmed", "draft", "planned"],
......
......@@ -41,22 +41,18 @@ for software_release in software_release_list:
# If exist upgrade decision in progress try to cancel it
decision_in_progress = newer_release.\
SoftwareRelease_getUpgradeDecisionInProgress(compute_node.getUid())
if decision_in_progress and \
not decision_in_progress.UpgradeDecision_tryToCancel(
newer_release.getUrlString()):
if decision_in_progress:
decision_in_progress.reviewRegistration(
software_release_url=newer_release.getUrlString())
if decision_in_progress.getSimulationState() != "cancelled":
continue
upgrade_decision = newer_release.SoftwareRelease_createUpgradeDecision(
source_url=compute_node.getRelativeUrl(),
title=title)
if context.getUpgradeScope() == "auto":
upgrade_decision.start()
elif context.getUpgradeScope("ask_confirmation") == "ask_confirmation" \
and upgrade_decision.getSimulationState() != "planned":
upgrade_decision.plan()
upgrade_decision.setStartDate(DateTime())
upgrade_decision.approveRegistration(
upgrade_scope=compute_node.getUpgradeScope("ask_confirmation"))
upgrade_decision_list.append(upgrade_decision)
return upgrade_decision_list
......@@ -2,7 +2,7 @@ from DateTime import DateTime
portal = context.getPortalObject()
instance_tree = context
upgrade_scope = context.getUpgradeScope()
upgrade_scope = context.getUpgradeScope("ask_confirmation")
if upgrade_scope in ["never", "disabled"]:
return
......@@ -22,7 +22,6 @@ if instance_tree.getSlapState() == "destroy_requested":
return
tag = "%s_requestUpgradeDecisionCreation_inProgress" % instance_tree.getUid()
activate_kw = {'tag': tag}
if portal.portal_activities.countMessageWithTag(tag) > 0:
# nothing to do
return
......@@ -52,22 +51,18 @@ if newer_release is None:
decision_in_progress = newer_release.\
SoftwareRelease_getUpgradeDecisionInProgress(instance_tree.getUid())
if decision_in_progress and \
not decision_in_progress.UpgradeDecision_tryToCancel(
newer_release.getUrlString()):
if decision_in_progress:
decision_in_progress.reviewRegistration(
software_release_url=newer_release.getUrlString())
if decision_in_progress.getSimulationState() != "cancelled":
return
upgrade_decision = newer_release.SoftwareRelease_createUpgradeDecision(
source_url=instance_tree.getRelativeUrl(),
title=decision_title
)
with upgrade_decision.defaultActivateParameterDict(activate_kw):
upgrade_decision.plan()
upgrade_decision.setStartDate(DateTime())
if upgrade_scope == "auto":
upgrade_decision.start()
# Prevent concurrent transaction to create 2 upgrade decision for the same instance_tree
instance_tree.serialize()
upgrade_decision.approveRegistration(
upgrade_scope=upgrade_scope)
return upgrade_decision
instance_tree = context.getAggregateValue(portal_type="Instance Tree")
software_release = context.getAggregateValue(portal_type="Software Release")
upgrade_decision = context.getParentValue()
if upgrade_decision.getSimulationState() == "cancelled":
return
if software_release.getValidationState() == "archived":
upgrade_decision.cancel(comment="Software Release is archived.")
return
if instance_tree is not None:
if instance_tree.getUpgradeScope() in ['never', 'disabled']:
upgrade_decision.cancel("Upgrade scope was disabled on the related Instance Tree")
elif instance_tree.getSlapState() == "destroy_requested":
upgrade_decision.cancel(comment="Instance Tree is destroyed.")
elif instance_tree.getUrlString() == software_release.getUrlString():
upgrade_decision.cancel(comment="Instance tree is already upgraded.")
return
compute_node = context.getAggregateValue(portal_type="Compute Node")
if compute_node is not None:
if compute_node.getUpgradeScope() in ['never', 'disabled']:
upgrade_decision.cancel("Upgrade scope was disabled on the related Instance Tree")
return
elif compute_node.getAllocationScope() in ["closed/forever", "closed/termination"]:
upgrade_decision.cancel(comment="Compute Node is closed.")
return
already_deployed = len(context.portal_catalog(limit=1,
portal_type="Compute Partition",
parent_reference=compute_node.getReference(),
software_release_url=software_release.getUrlString()))
if already_deployed:
upgrade_decision.cancel(comment="Compute Node already has the software release")
context.getParentValue().reviewUpgrade()
document_list = []
for decision_line in context.contentValues():
try:
document_list.extend(
decision_line.getAggregateValueList(portal_type=document_portal_type))
except:
raise ValueError(context)
if len(document_list) > 1:
raise ValueError("It is only allowed to have more them 1 %s" % document_list)
if len(document_list) == 0:
return None
return document_list[0]
......@@ -50,11 +50,11 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>title, description, source_relative_url</string> </value>
<value> <string>document_portal_type="Instance Tree"</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_generateSupportRequestForSlapOS</string> </value>
<value> <string>UpgradeDecision_getAggregateValue</string> </value>
</item>
</dictionary>
</pickle>
......
compute_node_list = []
for decision_line in context.contentValues():
compute_node_list.extend(
decision_line.getAggregateValueList(portal_type="Compute Node"))
if len(compute_node_list) > 1:
raise ValueError("It is only allowed to have more them 1 Compute Node")
if len(compute_node_list) == 0:
return None
return compute_node_list[0]
instance_tree_list = []
for decision_line in context.contentValues():
instance_tree_list.extend(
decision_line.getAggregateValueList(portal_type="Instance Tree"))
if len(instance_tree_list) > 1:
raise ValueError("It is only allowed to have more them 1 Instance Tree")
if len(instance_tree_list) == 0:
return None
return instance_tree_list[0]
software_release_list = []
for decision_line in context.contentValues():
software_release_list.extend(
decision_line.getAggregateValueList(portal_type="Software Release"))
if len(software_release_list) > 1:
raise ValueError("It is only allowed to have more them 1 Software Release")
if len(software_release_list) == 0:
return None
return software_release_list[0]
portal = context.getPortalObject()
instance_tree = context.UpgradeDecision_getInstanceTree()
compute_node = context.UpgradeDecision_getComputeNode()
software_release = context.UpgradeDecision_getSoftwareRelease()
instance_tree = context.UpgradeDecision_getAggregateValue("Instance Tree")
compute_node = context.UpgradeDecision_getAggregateValue("Compute Node")
software_release = context.UpgradeDecision_getAggregateValue("Software Release")
if instance_tree is not None:
if instance_tree.getUrlString() == software_release.getUrlString():
......
from DateTime import DateTime
# Review if the upgrade is applicable before notify.
context.reviewUpgrade()
if context.getSimulationState() != 'planned':
# XXX Don't notify the ones which are not planned.
return
......@@ -10,9 +13,9 @@ person = context.getDestinationDecisionValue(portal_type="Person")
if not person:
raise ValueError("Inconsistent Upgrade Decision, No Destination Decision")
instance_tree = context.UpgradeDecision_getInstanceTree()
compute_node = context.UpgradeDecision_getComputeNode()
software_release = context.UpgradeDecision_getSoftwareRelease()
instance_tree = context.UpgradeDecision_getAggregateValue("Instance Tree")
compute_node = context.UpgradeDecision_getAggregateValue("Compute Node")
software_release = context.UpgradeDecision_getAggregateValue("Software Release")
software_product_title = software_release.getAggregateTitle(
portal_type="Software Product")
......@@ -21,8 +24,8 @@ mapping_dict = {
'software_release_name': software_release.getTitle(),
'software_release_reference': software_release.getReference(),
'new_software_release_url': software_release.getUrlString(),
}
if instance_tree is not None:
notification_message_reference = 'slapos-upgrade-instance-tree.notification'
title = "New Upgrade available for %s" % instance_tree.getTitle()
......@@ -30,11 +33,8 @@ if instance_tree is not None:
'instance_tree_title': instance_tree.getTitle(),
'old_software_release_url': instance_tree.getUrlString()})
elif compute_node is not None:
notification_message_reference = 'slapos-upgrade-compute-node.notification'
title = "New Software available for Installation at %s" % compute_node.getTitle()
mapping_dict.update(**{'compute_node_title': compute_node.getTitle(),
'compute_node_reference': compute_node.getReference()})
......@@ -49,8 +49,10 @@ notification_message = portal.portal_notifications.getDocumentValue(
message = notification_message.asEntireHTML(
substitution_method_parameter_dict={'mapping_dict': mapping_dict})
event = context.SupportRequest_trySendNotificationMessage(title,
message, person.getRelativeUrl())
context.notify(message_title=title,
message=message,
destination_relative_url=person.getRelativeUrl())
event = context.REQUEST.get("upgrade_decision_notified_item")
if event is not None:
context.confirm()
......@@ -12,9 +12,10 @@ person = context.getDestinationDecisionValue(portal_type="Person")
if not person:
raise ValueError("Inconsistent Upgrade Decision, No Destination Decision")
instance_tree = context.UpgradeDecision_getInstanceTree()
compute_node = context.UpgradeDecision_getComputeNode()
software_release = context.UpgradeDecision_getSoftwareRelease()
instance_tree = context.UpgradeDecision_getAggregateValue("Instance Tree")
compute_node = context.UpgradeDecision_getAggregateValue("Compute Node")
software_release = context.UpgradeDecision_getAggregateValue("Software Release")
software_product_title = software_release.getAggregateTitle(
portal_type="Software Product")
......@@ -51,8 +52,10 @@ notification_message = portal.portal_notifications.getDocumentValue(
message = notification_message.asEntireHTML(
substitution_method_parameter_dict={'mapping_dict': mapping_dict})
event = context.SupportRequest_trySendNotificationMessage(title,
message, person.getRelativeUrl())
context.notify(message_title=title,
message=message,
destination_relative_url=person.getRelativeUrl())
event = context.REQUEST.get("upgrade_decision_notified_item")
if event is not None:
context.setStopDate(DateTime())
......
if context.UpgradeDecision_upgradeInstanceTree():
return True
if context.UpgradeDecision_upgradeComputeNode():
return True
return False
return context.requestUpgrade()
......@@ -50,7 +50,7 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>**kw</string> </value>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
<?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>new_url_string</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>UpgradeDecision_tryToCancel</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
if context.getSimulationState() != 'started':
# Update Decision is not on started state, Upgrade is not possible!
return False
compute_node = context.UpgradeDecision_getComputeNode()
software_release = context.UpgradeDecision_getSoftwareRelease()
if compute_node is None:
return False
if software_release is None:
return False
software_release_url = software_release.getUrlString()
compute_node.requestSoftwareRelease(
software_release_url=software_release_url,
state="available")
context.stop()
return True
<?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>testSlapOSCloudUpgradeSlapInterfaceWorkflow</string> </value>
</item>
<item>
<key> <string>default_source_reference</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test.erp5.testSlapOSCloudUpgradeSlapInterfaceWorkflow</string> </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>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.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<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>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -284,11 +284,12 @@ class TestSlapOSPDMCreateUpgradeDecisionSkins(TestSlapOSPDMMixinSkins):
self.assertNotEqual(up_decision, None)
self.assertEqual(up_decision.getSimulationState(), 'planned')
self.assertEqual(up_decision.UpgradeDecision_getInstanceTree().\
self.assertEqual(up_decision.UpgradeDecision_getAggregateValue("Instance Tree").\
getReference(), instance_tree.getReference())
self.assertEqual(up_decision.UpgradeDecision_getSoftwareRelease().\
getUrlString(), software_release2.getUrlString())
self.assertEqual(software_release2.getUrlString(),
up_decision.UpgradeDecision_getAggregateValue("Software Release").\
getUrlString())
self.tic()
up_decision2 = instance_tree.InstanceTree_createUpgradeDecision()
......@@ -331,7 +332,7 @@ class TestSlapOSPDMCreateUpgradeDecisionSkins(TestSlapOSPDMMixinSkins):
up_decision2 = instance_tree.InstanceTree_createUpgradeDecision()
self.assertEqual(up_decision2.getSimulationState(), 'planned')
self.assertEqual(up_decision.getSimulationState(), 'cancelled')
release = up_decision2.UpgradeDecision_getSoftwareRelease()
release = up_decision2.UpgradeDecision_getAggregateValue("Software Release")
self.assertEqual(release.getUrlString(),
software_release3.getUrlString())
......@@ -414,7 +415,7 @@ class TestSlapOSPDMCreateUpgradeDecisionSkins(TestSlapOSPDMMixinSkins):
decision2 = instance_tree.InstanceTree_createUpgradeDecision()
self.assertEqual(decision2.getSimulationState(), 'planned')
self.assertEqual(up_decision.getSimulationState(), 'rejected')
release = decision2.UpgradeDecision_getSoftwareRelease()
release = decision2.UpgradeDecision_getAggregateValue("Software Release")
self.assertEqual(release.getUrlString(),
software_release3.getUrlString())
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_count</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_mt_index</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>_tree</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>source/portal_workflow/upgrade_slap_interface_workflow/state_draft</string>
</tuple>
</value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>person_slap_interface_workflow</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>upgrade_slap_interface_workflow</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>manager_bypass</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow</string> </value>
</item>
<item>
<key> <string>state_variable</string> </key>
<value> <string>slap_state</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Upgrade Slap Interface Workflow</string> </value>
</item>
<item>
<key> <string>workflow_managed_permission</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Length" module="BTrees.Length"/>
</pickle>
<pickle> <int>0</int> </pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
</ZopeData>
upgrade_decision = state_change["object"]
from DateTime import DateTime
portal = upgrade_decision.getPortalObject()
document = upgrade_decision.UpgradeDecision_getAggregateValue("Instance Tree")
if document is None:
document = upgrade_decision.UpgradeDecision_getAggregateValue("Compute Node")
if document is None:
raise ValueError("No Compute Node or Instance Tree associated to upgrade.")
# Get required arguments
kwargs = state_change.kwargs
# Required args
# Raise TypeError if all parameters are not provided
try:
upgrade_scope = kwargs['upgrade_scope']
except KeyError:
raise TypeError("UpgradeDecision_approveRegistration takes exactly 1 arguments")
tag = "%s_requestUpgradeDecisionCreation_inProgress" % document.getUid()
activate_kw = {'tag': tag}
if portal.portal_activities.countMessageWithTag(tag) > 0:
# nothing to do
return
with upgrade_decision.defaultActivateParameterDict(activate_kw):
if upgrade_decision.getSimulationState() == "draft":
upgrade_decision.plan()
upgrade_decision.setStartDate(DateTime())
if upgrade_scope == "auto":
if upgrade_decision.getSimulationState() == "planned":
upgrade_decision.start()
# Prevent concurrent transaction to create 2 upgrade decision for the same instance_tree
document.serialize()
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Workflow Script" module="erp5.portal_type"/>
</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>state_change</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>script_UpgradeDecision_approveRegistration</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Workflow Script</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>UpgradeDecision_approveRegistration</string> </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