Commit ac116617 authored by Roque's avatar Roque

erp5_web_project_ui: new front page gadget (WIP)

parent 84bd681e
......@@ -111,12 +111,14 @@ CACHE:\n
\n
gadget_erp5_page_project_controller.html\n
gadget_erp5_page_project_controller.js\n
gadget_project_info.html\n
gadget_project_info.js\n
gadget_erp5_page_project_redirector.html\n
gadget_erp5_page_project_redirector.js\n
gadget_erp5_project_panel.html\n
gadget_erp5_project_panel.js\n
gadget_front_page_info.html\n
gadget_front_page_info.js\n
gadget_project_info.html\n
gadget_project_info.js\n
\n
favicon.ico\n
font-awesome/font-awesome-webfont.eot\n
......@@ -405,7 +407,7 @@ NETWORK:\n
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>979.50747.65222.58094</string> </value>
<value> <string>981.52234.40735.41557</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -423,7 +425,7 @@ NETWORK:\n
</tuple>
<state>
<tuple>
<float>1577216049.89</float>
<float>1581523847.97</float>
<string>UTC</string>
</tuple>
</state>
......
import json
from datetime import datetime
def getProjectId(id):
segments = id.split("/");
if (len(segments) == 2):
return id
return "/".join(segments[0:-1])
project_dict = {}
now_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
#TODO these should be parameters
limit_date = "2019-12-19 00:00:00"
valid_states = ['planned', 'ordered', 'confirmed', 'delivered', 'ready']
valid_states = "('" + "', '".join(valid_states) + "')"
valid_types = ['Task', 'Bug', 'Task Report']
valid_types = "('" + "', '".join(valid_types) + "')"
query = """SELECT DISTINCT
COUNT(*) AS `#number`,
`related_source_project__relative_url_1_catalog`.`relative_url` AS `source_project__relative_url`,
`catalog`.`portal_type` AS `portal_type`
FROM (catalog AS `catalog`
LEFT JOIN ( category AS `related_source_project__relative_url_category`
INNER JOIN catalog AS `related_source_project__relative_url_1_catalog`
ON related_source_project__relative_url_1_catalog.uid = related_source_project__relative_url_category.category_uid)
ON related_source_project__relative_url_category.uid = catalog.uid)
WHERE
1 = 1
AND (`catalog`.`modification_date` <= "%s")
AND `catalog`.`simulation_state` IN %s
AND ( `catalog`.`portal_type` IN %s)
AND ( `related_source_project__relative_url_1_catalog`.`relative_url` IS NOT NULL)
AND `related_source_project__relative_url_1_catalog`.`portal_type` IN ('Project', 'Project Line')
AND `related_source_project__relative_url_1_catalog`.`validation_state` = 'validated'
GROUP BY
`related_source_project__relative_url_1_catalog`.`relative_url`,
`catalog`.`portal_type`"""
total_query = query % (now_date, valid_states, valid_types)
for row in context.cmf_activity_sql_connection.manage_test(total_query):
key = getProjectId(row['source_project__relative_url'])
if key in project_dict:
project_dict[key][row['portal_type']] = { 'total' : row["#number"], 'outdated' : 0 }
else:
project_dict[key] = {row['portal_type'] : { 'total' : row["#number"], 'outdated' : 0 }}
outdated_query = query % (limit_date, valid_states, valid_types)
for row in context.cmf_activity_sql_connection.manage_test(outdated_query):
key = getProjectId(row['source_project__relative_url'])
project_dict[key][row['portal_type']]['outdated'] = row["#number"]
return json.dumps(project_dict, indent=2)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_getProjectInfo</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -62,6 +62,7 @@
<key> <string>bottom</string> </key>
<value>
<list>
<string>my_front_page_gadget_field</string>
<string>listbox</string>
</list>
</value>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="GadgetField" module="Products.ERP5Form.GadgetField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>my_front_page_gadget_field</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
<item>
<key> <string>no_validator</string> </key>
<value> <string>Does not support this operation.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>data_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>gadget_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>js_sandbox</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>renderjs_extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>validator_field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>validator_form_id</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>data_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>gadget_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>js_sandbox</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>renderjs_extra</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>validator_field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>validator_form_id</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>data_url</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>gadget_url</string> </key>
<value> <string>gadget_front_page_info.html</string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>js_sandbox</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>renderjs_extra</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>my_info_gadget_field</string> </value>
</item>
<item>
<key> <string>validator_field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>validator_form_id</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: {"project_info_dict": here.Base_getProjectInfo()}</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Project front page gadget</title>
<script src="rsvp.js"></script>
<script src="renderjs.js"></script>
<script src="gadget_front_page_info.js"></script>
<link rel="stylesheet" type="text/css" href="gadget_project_info.css">
</head>
<body>
<div class="front-project-list">
<ul id="js-project-list">
</ul>
</div>
</body>
</html>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string>gadget_front_page_info.html</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
/*jslint nomen: true, indent: 2 */
/*global window, rJS, RSVP, document, SimpleQuery, ComplexQuery, Query*/
(function (window, rJS, RSVP, document, SimpleQuery, ComplexQuery, Query) {
"use strict";
const STATUS_OK = "green";
const STATUS_NOT_OK = "red";
function getProjectElementList(gadget) {
function getComplexQuery(query_dict, operator, extra_query) {
var key,
query_list = [];
for (key in query_dict) {
if (query_dict.hasOwnProperty(key)) {
query_list.push(new SimpleQuery({
key: key,
operator: "=",
type: "simple",
value: query_dict[key]
}));
}
}
if (extra_query) {
query_list.push(extra_query);
}
return new ComplexQuery({
operator: operator,
query_list: query_list,
type: "complex"
});
}
var i,
project_query = getComplexQuery({"portal_type" : "Project",
"validation_state" : "validated"},
"AND"),
milestone_query = getComplexQuery({"portal_type" : "Project Milestone",
"parent__validation_state" : "validated"},
"AND"),
test_result_query = Query.objectToSearchText(
getComplexQuery({"portal_type" : "Test Result",
"source_project__validation_state" : "validated"},
"AND")),
//document_query,
//portal_type_list = ["Task", "Bug", "Task Report"],
//valid_state_list = ["planned", "ordered", "confirmed", "delivered", "ready"],
test_state_list = ["failed", "stopped"],
date_query,
one_year_old_date = new Date();
test_result_query += ' AND simulation_state: ("' + test_state_list.join('", "') + '")';
//TODO filter too old objects?
one_year_old_date.setFullYear(one_year_old_date.getFullYear() - 1);
one_year_old_date = one_year_old_date.toISOString();
one_year_old_date = one_year_old_date.substring(0, one_year_old_date.length - 5).replace("T", " ");
date_query = new SimpleQuery({
type: "simple",
key: "creation_date",
operator: ">",
value: one_year_old_date
});
/*document_query = Query.objectToSearchText(new SimpleQuery({
key: "source_project__validation_state",
operator: "=",
type: "simple",
value: "validated"
}));
// done with string queries directly because there is no way to do "key IN <list-of-values>" with Query
// unless appending simple queries with AND by iterating on list but it's inefficient
document_query += ' AND portal_type: ("' + portal_type_list.join('", "') + '")';
document_query += ' AND simulation_state: ("' + valid_state_list.join('", "') + '")';*/
return new RSVP.Queue()
.push(function () {
var promise_list = [],
limit = [0, 100000],
select_list = ['source_project', 'portal_type', 'modification_date'];
promise_list.push(gadget.jio_allDocs({
query: Query.objectToSearchText(project_query),
limit: limit,
select_list: ['title'],
sort_on: [["modification_date", "ascending"]]
}));
promise_list.push(gadget.jio_allDocs({
query: Query.objectToSearchText(milestone_query),
limit: limit,
select_list: select_list,
sort_on: [["modification_date", "descending"]]
}));
promise_list.push(gadget.jio_allDocs({
query: test_result_query,
limit: limit,
select_list: ['source_project__relative_url', 'modification_date'],
group_by: ['source_project__relative_url'],
sort_on: [["modification_date", "descending"]]
}));
/*promise_list.push(gadget.jio_allDocs({
query: document_query,
limit: limit,
select_list: select_list,
sort_on: [["modification_date", "descending"]]
}));*/
return RSVP.all(promise_list);
})
.push(function (result_list) {
return [result_list[0].data.rows, result_list[1].data.rows, result_list[2].data.rows/*, result_list[3].data.rows*/];
});
}
function renderProjectList(project_info_dict, project_list, milestone_list, test_result_list) {
var i,
item,
project_html,
left_div_html,
project_html_element_list,
left_line_html,
ul_list = document.getElementById("js-project-list"),
//XXX hardcoded for now (build a template?)
line_title_list = {"Task": "Tasks",
"Bug" : "Bugs",
"Task Report": "Task Reports"},
project_dict,
type;
function createProjectHtmlElement(project_id, project_title) {
var project_element = document.createElement('li'),
box_div = document.createElement('div'),
title_div = document.createElement('div'),
info_div = document.createElement('div'),
left_info_div = document.createElement('div'),
right_div = document.createElement('div'),
right_line_div = document.createElement('div'),
forum_link = document.createElement('a');
box_div.classList.add("project-box");
title_div.classList.add("project-title");
title_div.innerHTML = project_title;
info_div.classList.add("project-info");
left_info_div.classList.add("left");
right_line_div.classList.add("project-line");
forum_link.href = "todo";
forum_link.innerHTML = project_id + " forum link";
right_line_div.appendChild(forum_link);
right_div.appendChild(right_line_div);
info_div.appendChild(left_info_div);
info_div.appendChild(right_div);
box_div.appendChild(title_div);
box_div.appendChild(info_div);
project_element.appendChild(box_div);
return [project_element, left_info_div];
}
function createProjectLineHtmlElement(portal_type, total_count, out_count, status_color) {
var line_div = document.createElement('div'),
status = document.createElement('span'),
name = document.createElement('span'),
fail = document.createElement('span'),
total = document.createElement('span');
line_div.classList.add("project-line");
status.classList.add("status");
status.classList.add(status_color);
name.classList.add("name");
name.innerHTML = portal_type;
total.innerHTML = total_count;
fail.innerHTML = "(" + out_count + ")";
line_div.appendChild(status);
line_div.appendChild(name);
line_div.appendChild(total);
line_div.appendChild(fail);
return line_div;
}
for (i = 0; i < project_list.length; i += 1) {
project_html_element_list = createProjectHtmlElement(project_list[i].id, project_list[i].value.title);
project_html = project_html_element_list[0];
left_div_html = project_html_element_list[1];
project_dict = project_info_dict[project_list[i].id];
for (type in project_dict) {
if (project_dict.hasOwnProperty(type)) {
left_line_html = createProjectLineHtmlElement(((line_title_list.hasOwnProperty(type)) ? line_title_list[type] : type),
project_dict[type].total, project_dict[type].outdated,
((project_dict[type].outdated > 0) ? STATUS_NOT_OK : STATUS_OK));
left_div_html.appendChild(left_line_html);
}
}
ul_list.appendChild(project_html);
}
}
rJS(window)
.declareAcquiredMethod("getUrlForList", "getUrlForList")
.declareAcquiredMethod("getUrlFor", "getUrlFor")
.declareAcquiredMethod("jio_allDocs", "jio_allDocs")
.declareAcquiredMethod("jio_getAttachment", "jio_getAttachment")
.declareAcquiredMethod("getSetting", "getSetting")
.declareMethod('render', function (options) {
if (options.project_info_dict) {
options.project_info_dict = JSON.parse(options.project_info_dict);
}
return this.changeState(options);
})
.onStateChange(function (modification_dict) {
var gadget = this;
return getProjectElementList(gadget)
.push(function (element_list) {
renderProjectList(modification_dict.project_info_dict, element_list[0], element_list[1], element_list[2]);
});
})
.declareMethod('getContent', function () {
return {};
})
.declareMethod('checkValidity', function () {
return true;
});
}(window, rJS, RSVP, document, SimpleQuery, ComplexQuery, Query));
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string>gadget_front_page_info.js</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>application/javascript</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
#generate-rss {
padding: 5.5pt;
margin-right: 12pt;
background-color: #FF6600;
border-color: #FF6600;
color: #FFFFFF;
border-radius: 0.325em;
border-width: 0.5px;
border-style: solid;
min-width: 6em;
line-height: 1.5;
display: none;
text-align: center;
width: fit-content;
/* FRONT PAGE */
div[data-gadget-url$="gadget_front_page_info.html"] .front-project-list {
margin-top: 10px;
}
.restore-button {
padding: 6pt;
margin-right: 12pt;
background-color: #FF6600;
color: #FFFFFF;
border-radius: 0.325em;
border-width: 0.5px;
div[data-gadget-url$="gadget_front_page_info.html"] .front-project-list .project-box {
margin-bottom: 10px;
border-style: solid;
min-width: 8em;
line-height: 1.5;
border-width: 1px;
border-radius: 5px;
padding: 5px;
padding: 5px;
padding-left: 15px;
}
#wrap1 iframe {
height: 100%;
div[data-gadget-url$="gadget_front_page_info.html"] a {
color: #267B87;
text-decoration: none;
}
#wrap2 iframe {
height: 100%;
div[data-gadget-url$="gadget_front_page_info.html"] .front-project-list .project-box .project-info {
display: inline-flex;
}
.gadget-content .ui-field-contain .graph-spinner {
position: relative;
top: 100px;
width: 120px;
div[data-gadget-url$="gadget_front_page_info.html"] .front-project-list .project-box .project-info .left {
width: 35%;
}
.gadget-content .ui-field-contain .count-spinner {
top: 20px;
width: 120px;
div[data-gadget-url$="gadget_front_page_info.html"] .front-project-list .project-box .project-line {
margin-bottom: 5px;
font-family: "Roboto", Arial, sans-serif;
font-size: 1.25em;
line-height: 1.58em;
letter-spacing: -.003em;
}
div[data-gadget-url$="gadget_front_page_info.html"] .front-project-list .project-box .project-title {
color: #19535F;
font-weight: normal;
font-size: 24px;
line-height: 33px;
font-weight: bold;
margin-bottom: 5px;
}
div[data-gadget-url$="gadget_front_page_info.html"] .front-project-list .project-box .status {
padding-left: 21px;
}
div[data-gadget-url$="gadget_front_page_info.html"] .front-project-list .project-box span {
margin-right: 5px;
}
div[data-gadget-url$="gadget_front_page_info.html"] .front-project-list .project-box .status.orange {
background: #f0ad4e !important;
}
div[data-gadget-url$="gadget_front_page_info.html"] .front-project-list .project-box .status.red {
background: #de1e00 !important;
}
div[data-gadget-url$="gadget_front_page_info.html"] .front-project-list .project-box .status.green {
background: #00b80c !important;
}
/* PROJECT PAGE */
.gadget-content .ui-field-contain .bottom .first-line-buttons {
-webkit-appearance: none;
margin-top: 0;
......@@ -123,4 +145,38 @@ input[type="submit"] {
.worklist-title {
color: #777777;
margin-top: 10px;
}
/* PROJECT HOME PAGE */
body.cke_editable h1 {
margin-top: 1.5em;
font-family: "Roboto", Arial, sans-serif;
color: #19535F;
font-size: 2em;
margin: 0.67em 0;
}
body.cke_editable h2 {
font-family: "Roboto", Arial, sans-serif;
font-weight: normal;
font-size: 25px;
line-height: 33px;
color: #19535F;
}
body.cke_editable p, body.cke_editable li, body.cke_editable span {
font-family: 'Heuristica', 'Helvetica', Times, serif;
}
body.cke_editable ul li, body.cke_editable p {
font-size: 1.25em;
line-height: 1.58em;
letter-spacing: -.003em;
}
body.cke_editable a {
color: #267B87;
text-decoration: none;
}
\ No newline at end of file
......@@ -14,13 +14,15 @@
function parseHTMLLinks(html, url) {
var parser = new DOMParser(), i,
//TODO create head and link html elements and prepend to doc instead of using text
styles_header = '<head><link rel="stylesheet" type="text/css" href="gadget_project_info.css"></head>',
oSerializer = new XMLSerializer(),
doc = parser.parseFromString(html, "text/html"),
link_list = doc.getElementsByTagName("a");
for (i = 0; i < link_list.length; i += 1) {
link_list[i].setAttribute('href', addRedirectionToReference(link_list[i].getAttribute('href'), url));
}
return oSerializer.serializeToString(doc);
return styles_header + oSerializer.serializeToString(doc);
}
function enableLink(link_element, url) {
......@@ -220,7 +222,7 @@
gadget.getSetting("hateoas_url")
];
if (modification_dict.publication_section) {
promise_list.push(gadget.getDeclaredGadget("editor")),
promise_list.push(gadget.getDeclaredGadget("editor"));
promise_list.push(getWebPageInfo(gadget, modification_dict.jio_key, modification_dict.publication_section));
}
return RSVP.all(promise_list);
......
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