Commit 1fe39865 authored by Jérome Perrin's avatar Jérome Perrin

officejs_support_request_ui: Rework homepage dashboard

* Make it obvious that this script works with catalog brains and does
not want to call getObject
* Make two queries, one to get the "latest" support requests (by date)
and another one to get the "current" (by state), making one big query
will return too many results when the number of SR grow
* backend directly returns the data in the format expected by graph
library.
parent f0305cb7
...@@ -112,7 +112,7 @@ ...@@ -112,7 +112,7 @@
<item> <item>
<key> <string>text_content</string> </key> <key> <string>text_content</string> </key>
<value> <string>CACHE MANIFEST\n <value> <string>CACHE MANIFEST\n
# v1.0.5\n # v1.0.6\n
CACHE:\n CACHE:\n
font-awesome/font-awesome-webfont.woff2\n font-awesome/font-awesome-webfont.woff2\n
echarts-all.js\n echarts-all.js\n
...@@ -360,7 +360,7 @@ NETWORK:\n ...@@ -360,7 +360,7 @@ NETWORK:\n
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1540870337.1</float> <float>1542010989.1</float>
<string>GMT+9</string> <string>GMT+9</string>
</tuple> </tuple>
</state> </state>
......
...@@ -177,7 +177,10 @@ ...@@ -177,7 +177,10 @@
return result; return result;
}) })
.push(function (result_list) { .push(function (result_list) {
var sp_data = result_list[0], graph_gadget_1 = result_list[1], graph_gadget_2 = result_list[2]; var sp_data = result_list[0],
graph_gadget_1 = result_list[1],
graph_gadget_2 = result_list[2],
count_by_state_and_date_range = sp_data.count_by_state_and_date_range;
gadget.property_dict.graph_widget = graph_gadget_1; gadget.property_dict.graph_widget = graph_gadget_1;
return RSVP.all([graph_gadget_1.render( return RSVP.all([graph_gadget_1.render(
{ {
...@@ -185,45 +188,30 @@ ...@@ -185,45 +188,30 @@
data: [ data: [
{ {
value_dict: { value_dict: {
0: ["< 2", "2-7", "7-30", "> 30"], 0: count_by_state_and_date_range.validated.date_range_list,
1: [ 1: count_by_state_and_date_range.validated.count_list
sp_data.le2.validated,
sp_data['2to7'].validated,
sp_data['7to30'].validated,
sp_data.gt30.validated
]
}, },
colors: ['#d48265'], colors: ['#d48265'],
type: "bar", type: "bar",
title: "Opened" title: sp_data.state_title_by_state_id.validated
}, },
{ {
value_dict: { value_dict: {
0: ["< 2", "2-7", "7-30", "> 30"], 0: count_by_state_and_date_range.submitted.date_range_list,
1: [ 1: count_by_state_and_date_range.submitted.count_list
sp_data.le2.submitted,
sp_data['2to7'].submitted,
sp_data['7to30'].submitted,
sp_data.gt30.submitted
]
}, },
colors: ['#61a0a8'], colors: ['#61a0a8'],
type: "bar", type: "bar",
title: "Submitted" title: sp_data.state_title_by_state_id.submitted
}, },
{ {
value_dict: { value_dict: {
0: ["< 2", "2-7", "7-30", "> 30"], 0: count_by_state_and_date_range.suspended.date_range_list,
1: [ 1: count_by_state_and_date_range.suspended.count_list
sp_data.le2.suspended,
sp_data['2to7'].suspended,
sp_data['7to30'].suspended,
sp_data.gt30.suspended
]
}, },
colors: ['#c23531'], colors: ['#c23531'],
type: "bar", type: "bar",
title: "Suspended" title: sp_data.state_title_by_state_id.suspended
} }
], ],
layout: { layout: {
...@@ -249,8 +237,18 @@ ...@@ -249,8 +237,18 @@
data: [ data: [
{ {
value_dict: { value_dict: {
0: ["Opened", "Submitted", "Suspended", "Closed"], 0: [
1: [sp_data.validated, sp_data.submitted, sp_data.suspended, sp_data.invalidated] sp_data.state_title_by_state_id.validated,
sp_data.state_title_by_state_id.submitted,
sp_data.state_title_by_state_id.suspended,
sp_data.state_title_by_state_id.invalidated
],
1: [
sp_data.count_by_state.validated || 0,
sp_data.count_by_state.submitted || 0,
sp_data.count_by_state.suspended || 0,
sp_data.count_by_state.invalidated || 0
],
}, },
colors: ['#d48265', '#61a0a8', '#c23531', '#2f4554'], colors: ['#d48265', '#61a0a8', '#c23531', '#2f4554'],
type: "pie", type: "pie",
......
...@@ -258,8 +258,8 @@ ...@@ -258,8 +258,8 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1506606769.12</float> <float>1542008780.46</float>
<string>UTC</string> <string>GMT+9</string>
</tuple> </tuple>
</state> </state>
</object> </object>
......
"""Returns the workflow states titles for ticket workflow, keyed by state id.
This script has proxy role, as only manager can access workflow configuration.
"""
from Products.ERP5Type.Message import translateString
portal = context.getPortalObject()
info = {}
workflow = portal.portal_workflow.ticket_workflow
for state in workflow['states'].objectValues():
state_title = state.title_or_id()
if 0:
# We don't translate yet, it needs several other fixes
# see https://lab.nexedi.com/nexedi/erp5/merge_requests/778
state_title = unicode(translateString(
'%s [state in %s]' % (state_title, workflow.getId()),
default=unicode(translateString(state_title))))
info[state.getId()] = state_title
return info
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_getTicketWorkflowStateInfoDict</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
from datetime import timedelta from datetime import timedelta
from collections import defaultdict
from json import dumps from json import dumps
portal = context.getPortalObject() portal = context.getPortalObject()
...@@ -9,41 +10,59 @@ date_2_midnight = DateTime(now_date - timedelta(days=2)).earliestTime() ...@@ -9,41 +10,59 @@ date_2_midnight = DateTime(now_date - timedelta(days=2)).earliestTime()
date_7_midnight = DateTime(now_date - timedelta(days=7)).earliestTime() date_7_midnight = DateTime(now_date - timedelta(days=7)).earliestTime()
date_30_midnight = DateTime(now_date - timedelta(days=30)).earliestTime() date_30_midnight = DateTime(now_date - timedelta(days=30)).earliestTime()
support_request_list = portal.portal_catalog(
portal_type="Support Request",
select_list=['simulation_state', 'start_date'],
**{"delivery.start_date": {"query": DateTime(now_date), "range": "ngt"}}
)
count_by_state = {}
count_by_date = {"le2": {}, "2to7": {}, "7to30": {}, "gt30": {}}
for sr in support_request_list:
sr_date = sr.start_date
sr_state = sr.getProperty("simulation_state")
if sr_state not in count_by_state:
count_by_state[sr_state] = 0
if sr_state not in count_by_date["le2"]: # Count the "pipe" of current support requests by state and date
for date_category in count_by_date: # this catalog search is not limited in time, but it only selects the
count_by_date[date_category][sr_state] = 0 # currently active support requests by state. Unless CRM agents are late
# in the processing of these support requests, they should not be too many.
count_by_state_and_date_range = defaultdict(lambda:defaultdict(int))
for brain in portal.portal_catalog(
portal_type="Support Request",
simulation_state=("submitted", "suspended", "validated",),
select_dict={"simulation_state": None, "start_date": "delivery.start_date"},):
sr_date = brain.start_date
sr_state = brain.simulation_state
if sr_date >= date_2_midnight: if sr_date >= date_2_midnight:
count_by_date["le2"][sr_state] = count_by_date["le2"][sr_state] + 1 count_by_state_and_date_range[sr_state]["< 2"] = \
count_by_state_and_date_range[sr_state]["< 2"] + 1
elif sr_date >= date_7_midnight: elif sr_date >= date_7_midnight:
count_by_date["2to7"][sr_state] = count_by_date["2to7"][sr_state] + 1 count_by_state_and_date_range[sr_state]["2-7"] = \
count_by_state_and_date_range[sr_state]["2-7"] + 1
elif sr_date >= date_30_midnight: elif sr_date >= date_30_midnight:
count_by_date["7to30"][sr_state] = count_by_date["7to30"][sr_state] + 1 count_by_state_and_date_range[sr_state]["7-30"] = \
count_by_state_and_date_range[sr_state]["7-30"] + 1
else: else:
count_by_date["gt30"][sr_state] = count_by_date["gt30"][sr_state] + 1 count_by_state_and_date_range[sr_state]["> 30"] = \
count_by_state_and_date_range[sr_state]["> 30"] + 1
# We have
# { state: { date_range: count } }
# but we need to turn it into:
# { state : {date_range_list: [date_range], count_list: [count], }
# with the date range sorted as `date_step_list`
date_range_list = ("< 2", "2-7", "7-30", "> 30")
count_by_state_and_date_range = {
state: {
"date_range_list": date_range_list,
"count_list": [count_by_state_and_date_range[state][date_range]
for date_range in date_range_list ]
} for state in count_by_state_and_date_range }
if sr_date < date_30_midnight:
continue
# Count last month activity by state
# we only select support requests from last 30 days, so there should not be too many.
count_by_state = defaultdict(int)
for brain in portal.portal_catalog(
portal_type="Support Request",
select_dict={"simulation_state": None},
**{"delivery.start_date": {"query": date_30_midnight, "range": ">="}}):
sr_state = brain.simulation_state
count_by_state[sr_state] = count_by_state[sr_state] + 1 count_by_state[sr_state] = count_by_state[sr_state] + 1
result = {}
result.update(count_by_state) return dumps({
result.update(count_by_date) "count_by_state": count_by_state,
return dumps(result) "count_by_state_and_date_range": count_by_state_and_date_range,
"state_title_by_state_id": portal.ERP5Site_getTicketWorkflowStateInfoDict()
})
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