Client-side search in drop-down boxes

1. Drop-down contents are pre-loaded during page load, search is made local
2. Search support is moved out of API
3. Removed API methods:
   * /releases/<release>
   * /project_types/<project_type>
   * /metrics/<metric>
4. Response of API methods used in drop-downs is unified. All of them
   return content under "data" key, default value under "default"
5. Rename JS functions from c_style to camelCase
6. Adapt DriverLog report to new drop-downs behavior

Part of blueprint ui-performance

Change-Id: I7bd9e9d1176d8419aa1e4bc1ccbb7ab4afdf7583
This commit is contained in:
Ilya Shakhat 2014-05-22 14:18:26 +04:00
parent f36fd65ab2
commit 85e2f99134
23 changed files with 313 additions and 526 deletions

View File

@ -397,7 +397,14 @@ def jsonify(root='data'):
def decorator(func): def decorator(func):
@functools.wraps(func) @functools.wraps(func)
def jsonify_decorated_function(*args, **kwargs): def jsonify_decorated_function(*args, **kwargs):
return json.dumps({root: func(*args, **kwargs)}) value = func(*args, **kwargs)
if isinstance(value, tuple):
result = dict([(root[i], value[i])
for i in six.moves.range(min(len(value),
len(root)))])
else:
result = {root: value}
return json.dumps(result)
return jsonify_decorated_function return jsonify_decorated_function

View File

@ -470,3 +470,13 @@ div.stackamenu li.current-menu-item a span {
color: white; color: white;
/*text-shadow: 0 -1px 0 #436281;*/ /*text-shadow: 0 -1px 0 #436281;*/
} }
.select2-loading {
font-style: italic;
color: dimgray;
background: url('../images/select2-spinner.gif') no-repeat 100% !important;
}
.ui-widget-overlay {
opacity: 0.6;
}

View File

@ -36,132 +36,66 @@ function make_uri(uri, options) {
return (str == "") ? uri : uri + "?" + str; return (str == "") ? uri : uri + "?" + str;
} }
function make_std_options() { function getPageState() {
var options = {}; return {
options['project_id'] = $('#project_selector').val(); project_id: $('#project_id').val(),
options['vendor'] = $('#vendor_selector').val(); vendor: $('#vendor').val(),
options['release_id'] = $('#release_selector').val(); release_id: $('#release_id').val()
};
return options;
} }
function reload() { function reload(extra) {
var ops = {}; window.location.search = $.map($.extend(getPageState(), extra), function (val, index) {
$.extend(ops, getUrlVars()); return val? (index + "=" + encodeURIComponent(val)) : null;
$.extend(ops, make_std_options());
window.location.search = $.map(ops,function (val, index) {
return index + "=" + encodeURIComponent(val);
}).join("&") }).join("&")
} }
function init_selectors(base_url) { function initSelectors(base_url) {
$(document).tooltip();
var project_id = getUrlVars()["project_id"]; function initSingleSelector(name, data_container, api_url, select2_extra_options, change_handler) {
$("#" + name).val(0).select2({
data: [{id: 0, text: "Loading..." }],
formatSelection: function(item) { return "<div class=\"select2-loading\">" + item.text + "</div>"}
}).select2("enable", false);
$("#project_selector").val(project_id).select2({ $.ajax({
allowClear: true, url: api_url,
placeholder: "Select Project", dataType: "jsonp",
ajax: { success: function (data) {
url: make_uri(base_url + "api/1.0/list/project_ids"), var initial_value = getUrlVars()[name];
dataType: 'jsonp', if (!initial_value && data["default"]) {
data: function (term, page) { initial_value = data["default"];
return { }
query: term $("#" + name).
}; val(initial_value).
}, select2($.extend({
results: function (data, page) { data: data[data_container]
return {results: data["project_ids"]}; }, select2_extra_options)).
on("select2-selecting",function (e) { /* don't use 'change' event, because it changes value and then refreshes the page */
var options = {};
options[name] = e.val;
if (change_handler) {
change_handler(options);
console.log(options);
}
reload(options);
}).
on("select2-removed",function (e) {
var options = {};
options[name] = '';
reload(options);
}).
select2("enable", true);
} }
},
initSelection: function (element, callback) {
var id = $(element).val();
if (id !== "") {
$.ajax(make_uri(base_url + "api/1.0/list/project_ids/" + id), {
dataType: "jsonp"
}).done(function (data) {
callback(data["project_id"]);
});
}
}
});
$('#project_selector')
.on("change", function (e) {
reload();
});
var vendor = getUrlVars()["vendor"];
$("#vendor_selector").val(vendor).select2({
allowClear: true,
placeholder: "Select Vendor",
ajax: {
url: make_uri(base_url + "api/1.0/list/vendors"),
dataType: 'jsonp',
data: function (term, page) {
return {
query: term
};
},
results: function (data, page) {
return {results: data["vendors"]};
}
},
initSelection: function (element, callback) {
var id = $(element).val();
if (id !== "") {
$.ajax(make_uri(base_url + "api/1.0/list/vendors/" + id), {
dataType: "jsonp"
}).done(function (data) {
callback(data["vendor"]);
});
}
}
});
$('#vendor_selector')
.on("change", function (e) {
reload();
});
var release_id = getUrlVars()["release_id"];
$("#release_selector").val(release_id).select2({
allowClear: true,
placeholder: "Select Release",
ajax: {
url: make_uri(base_url + "api/1.0/list/releases"),
dataType: 'jsonp',
data: function (term, page) {
return {
query: term
};
},
results: function (data, page) {
return {results: data["releases"]};
}
},
initSelection: function (element, callback) {
var id = $(element).val();
if (id !== "") {
$.ajax(make_uri(base_url + "api/1.0/list/releases/" + id), {
dataType: "jsonp"
}).done(function (data) {
callback(data["release"]);
});
}
}
});
$('#release_selector')
.on("change", function (e) {
reload();
}); });
}
initSingleSelector("project_id", "project_ids", make_uri(base_url + "api/1.0/list/project_ids"), {allowClear: true});
initSingleSelector("vendor", "vendors", make_uri(base_url + "api/1.0/list/vendors"), {allowClear: true});
initSingleSelector("release", "releases", make_uri(base_url + "api/1.0/list/releases"), {allowClear: true});
} }
function show_driver_info(driver) { function showDriverInfo(driver) {
$("#driver_info_container").empty(); $("#driver_info_container").empty();
$("#driver_info_template").tmpl(driver).appendTo("#driver_info_container"); $("#driver_info_template").tmpl(driver).appendTo("#driver_info_container");
@ -194,7 +128,7 @@ function show_driver_info(driver) {
$("#driver_info_dialog").dialog("open"); $("#driver_info_dialog").dialog("open");
} }
function setup_driver_info_handler(table_id, element_id, driver) { function setupDriverInfoHandler(table_id, element_id, driver) {
$("#driver_info_dialog").dialog({ $("#driver_info_dialog").dialog({
autoOpen: false, autoOpen: false,
width: "70%", width: "70%",
@ -212,11 +146,11 @@ function setup_driver_info_handler(table_id, element_id, driver) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
show_driver_info(driver); showDriverInfo(driver);
}); });
} }
function show_summary(base_url) { function showSummary(base_url) {
var table_column_names = ["project_name", "vendor", "driver_info", "in_trunk", "ci_tested", "maintainers_info"]; var table_column_names = ["project_name", "vendor", "driver_info", "in_trunk", "ci_tested", "maintainers_info"];
var table_id = "data_table"; var table_id = "data_table";
@ -240,7 +174,7 @@ function show_summary(base_url) {
tableData[i].driver_info += "<div>" + tableData[i].description + "</div>"; tableData[i].driver_info += "<div>" + tableData[i].description + "</div>";
} }
setup_driver_info_handler(table_id, "driver_" + i, tableData[i]); setupDriverInfoHandler(table_id, "driver_" + i, tableData[i]);
var releases_list = []; var releases_list = [];
for (var j = 0; j < tableData[i].releases_info.length; j++) { for (var j = 0; j < tableData[i].releases_info.length; j++) {
@ -300,6 +234,7 @@ function show_summary(base_url) {
], ],
"iDisplayLength": -1, "iDisplayLength": -1,
"bAutoWidth": false, "bAutoWidth": false,
"bPaginate": false,
"aaData": tableData, "aaData": tableData,
"aoColumns": tableColumns "aoColumns": tableColumns
}); });

View File

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
*/ */
function createTimeline(data) { function _createTimeline(data) {
var plot = $.jqplot('timeline', data, { var plot = $.jqplot('timeline', data, {
gridPadding: { gridPadding: {
right: 35 right: 35
@ -74,10 +74,10 @@ function createTimeline(data) {
function renderTimeline(options) { function renderTimeline(options) {
$(document).ready(function () { $(document).ready(function () {
$.ajax({ $.ajax({
url: make_uri("/api/1.0/stats/timeline", options), url: makeURI("/api/1.0/stats/timeline", options),
dataType: "json", dataType: "json",
success: function (data) { success: function (data) {
createTimeline(data["timeline"]); _createTimeline(data["timeline"]);
} }
}); });
}); });
@ -88,7 +88,7 @@ function renderTableAndChart(url, container_id, table_id, chart_id, link_param,
$(document).ready(function () { $(document).ready(function () {
$.ajax({ $.ajax({
url: make_uri(url), url: makeURI(url),
dataType: "jsonp", dataType: "jsonp",
success: function (data) { success: function (data) {
@ -115,7 +115,7 @@ function renderTableAndChart(url, container_id, table_id, chart_id, link_param,
if (!data[i].link) { if (!data[i].link) {
if (data[i].id) { if (data[i].id) {
data[i].link = make_link(data[i].id, data[i].name, link_param); data[i].link = makeLink(data[i].id, data[i].name, link_param);
} else { } else {
data[i].link = data[i].name data[i].link = data[i].name
} }
@ -180,7 +180,7 @@ function renderTableAndChart(url, container_id, table_id, chart_id, link_param,
}); });
} }
function render_bar_chart(chart_id, chart_data) { function renderBarChart(chart_id, chart_data) {
$.jqplot(chart_id, chart_data, { $.jqplot(chart_id, chart_data, {
seriesDefaults: { seriesDefaults: {
renderer: $.jqplot.BarRenderer, renderer: $.jqplot.BarRenderer,
@ -202,7 +202,7 @@ function render_bar_chart(chart_id, chart_data) {
}); });
} }
function render_punch_card(chart_id, chart_data) { function renderPunchCard(chart_id, chart_data) {
$.jqplot(chart_id, chart_data, { $.jqplot(chart_id, chart_data, {
seriesDefaults:{ seriesDefaults:{
renderer: $.jqplot.BubbleRenderer, renderer: $.jqplot.BubbleRenderer,
@ -262,20 +262,20 @@ function extendWithGravatar(record, image_size) {
function getUrlVars() { function getUrlVars() {
var vars = {}; var vars = {};
var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (m, key, value) { window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (m, key, value) {
vars[key] = decodeURIComponent(value); vars[key] = decodeURIComponent(value);
}); });
return vars; return vars;
} }
function make_link(id, title, param_name) { function makeLink(id, title, param_name) {
var options = {}; var options = {};
options[param_name] = encodeURIComponent(id).toLowerCase(); options[param_name] = encodeURIComponent(id).toLowerCase();
var link = make_uri("/", options); var link = makeURI("/", options);
return "<a href=\"" + link + "\">" + title + "</a>" return "<a href=\"" + link + "\">" + title + "</a>"
} }
function make_uri(uri, options) { function makeURI(uri, options) {
var ops = {}; var ops = {};
$.extend(ops, getUrlVars()); $.extend(ops, getUrlVars());
if (options != null) { if (options != null) {
@ -288,234 +288,82 @@ function make_uri(uri, options) {
return (str == "") ? uri : uri + "?" + str; return (str == "") ? uri : uri + "?" + str;
} }
function make_std_options() { function getPageState() {
var options = {}; return {
options['release'] = $('#release').val(); release: $('#release').val(),
options['metric'] = $('#metric').val(); project_type: $('#project_type').val(),
options['project_type'] = $('#project_type').val(); module: $('#module').val(),
options['module'] = $('#module').val() || ''; company: $('#company').val(),
options['company'] = $('#company').val() || ''; user_id: $('#user').val(),
options['user_id'] = $('#user').val() || ''; metric: $('#metric').val()
};
return options;
} }
function reload() { function reload(extra) {
window.location.search = $.map(make_std_options(),function (val, index) { window.location.search = $.map($.extend(getUrlVars(), extra), function (val, index) {
return index + "=" + encodeURIComponent(val); return val? (index + "=" + encodeURIComponent(val)) : null;
}).join("&") }).join("&")
} }
function init_selectors(base_url) { function initSingleSelector(name, api_url, select2_extra_options, change_handler) {
var release = getUrlVars()["release"]; var selectorId = "#" + name + "_selector";
if (!release) {
release = "_default";
}
$("#release").val(release).select2({
ajax: {
url: make_uri(base_url + "/api/1.0/releases"),
dataType: 'jsonp',
data: function (term, page) {
return {
query: term
};
},
results: function (data, page) {
return {results: data["releases"]};
}
},
initSelection: function (element, callback) {
var id = $(element).val();
$.ajax(make_uri(base_url + "/api/1.0/releases/" + id), {
dataType: "jsonp"
}).done(function (data) {
callback(data["release"]);
$("#release").val(data["release"].id)
});
}
});
$('#release')
.on("change", function (e) {
reload();
});
var metric = getUrlVars()["metric"]; $(selectorId).val(0).select2({
if (!metric) { data: [
metric = "_default"; {id: 0, text: "Loading..." }
} ],
$("#metric").val(metric).select2({ formatSelection: function (item) {
ajax: { return "<div class=\"select2-loading\">" + item.text + "</div>"
url: make_uri(base_url + "/api/1.0/metrics"),
dataType: 'jsonp',
data: function (term, page) {
return {
query: term
};
},
results: function (data, page) {
return {results: data["metrics"]};
}
},
initSelection: function (element, callback) {
var id = $(element).val();
$.ajax(make_uri(base_url + "/api/1.0/metrics/" + id), {
dataType: "jsonp"
}).done(function (data) {
callback(data["metric"]);
$("#metric").val(data["metric"].id);
});
} }
}); }).select2("enable", false);
$('#metric')
.on("change", function (e) {
reload();
});
var project_type = getUrlVars()["project_type"]; $.ajax({
if (!project_type) { url: api_url,
project_type = "_default"; dataType: "jsonp",
} success: function (data) {
$("#project_type").val(project_type).select2({ var initial_value = getUrlVars()[name];
ajax: { if (!initial_value && data["default"]) {
url: make_uri(base_url + "/api/1.0/project_types"), initial_value = data["default"];
dataType: 'jsonp', }
data: function (term, page) { $(selectorId).
return { val(initial_value).
query: term select2($.extend({
}; data: data["data"]
}, }, select2_extra_options)).
results: function (data, page) { on("select2-selecting",function (e) { /* don't use 'change' event, because it changes value and only after refreshes the page */
const project_types = data["project_types"]; var options = {};
var result = []; options[name] = e.val;
for (var key in project_types) { if (change_handler) {
result.push({id: project_types[key].id, text: project_types[key].text, group: true}); change_handler(options);
for (var i in project_types[key].items) {
var item = project_types[key].items[i];
result.push({id: item.id, text: item.text});
} }
} reload(options);
return {results: result}; }).
} on("select2-removed",function (e) {
}, var options = {};
initSelection: function (element, callback) { options[name] = '';
var id = $(element).val(); reload(options);
$.ajax(make_uri(base_url + "/api/1.0/project_types/" + id), { }).
dataType: "jsonp" select2("enable", true);
}).done(function (data) {
callback(data["project_type"]);
$("#project_type").val(data["project_type"].id);
});
},
formatResultCssClass: function (item) {
if (item.group) {
return "project_group"
} else {
return "project_group_item";
}
} }
}); });
$('#project_type') }
.on("change", function (e) {
$('#module').val(''); function initSelectors(base_url) {
reload(); initSingleSelector("release", makeURI(base_url + "/api/1.0/releases"));
}); initSingleSelector("project_type", makeURI(base_url + "/api/1.0/project_types"), {
formatResultCssClass: function (item) {
$("#company").select2({ return (item.child) ? "project_group_item" : "project_group";
allowClear: true, }
ajax: { }, function (options) {
url: make_uri(base_url + "/api/1.0/companies"), options['module'] = null;
dataType: 'jsonp', });
data: function (term, page) { initSingleSelector("module", makeURI(base_url + "/api/1.0/modules", {tags: "module,program,group"}), {
return { formatResultCssClass: function (item) {
company_name: term return (item.tag)? ("select_module_" + item.tag): "";
}; },
}, allowClear: true
results: function (data, page) { });
return {results: data["companies"]}; initSingleSelector("company", makeURI(base_url + "/api/1.0/companies"), {allowClear: true});
} initSingleSelector("user_id", makeURI(base_url + "/api/1.0/users"), {allowClear: true});
}, initSingleSelector("metric", makeURI(base_url + "/api/1.0/metrics"));
initSelection: function (element, callback) {
var id = $(element).val();
if (id !== "") {
$.ajax(make_uri(base_url + "/api/1.0/companies/" + id), {
dataType: "jsonp"
}).done(function (data) {
callback(data["company"]);
});
}
}
});
$('#company')
.on("change", function (e) {
reload();
});
$("#module").select2({
allowClear: true,
ajax: {
url: make_uri(base_url + "/api/1.0/modules", {tags: "module,program,group"}),
dataType: 'jsonp',
data: function (term, page) {
return {
query: term
};
},
results: function (data, page) {
return {results: data["modules"]};
}
},
initSelection: function (element, callback) {
var id = $(element).val();
if (id !== "") {
$.ajax(make_uri(base_url + "/api/1.0/modules/" + id), {
dataType: "jsonp"
}).done(function (data) {
callback(data["module"]);
});
}
},
formatResultCssClass: function (item) {
if (item.tag) {
return "select_module_" + item.tag;
}
return "";
}
});
$('#module')
.on("change", function (e) {
reload();
});
$("#user").select2({
allowClear: true,
ajax: {
url: make_uri(base_url + "/api/1.0/users"),
dataType: 'jsonp',
data: function (term, page) {
return {
user_name: term
};
},
results: function (data, page) {
return {results: data["users"]};
}
},
initSelection: function (element, callback) {
var id = $(element).val();
if (id !== "") {
$.ajax(make_uri(base_url + "/api/1.0/users/" + id), {
dataType: "json"
}).done(function (data) {
callback(data["user"]);
});
}
}
});
$('#user')
.on("change", function (e) {
reload();
});
} }

View File

@ -23,7 +23,7 @@ show_record_type=True, show_user_gravatar=True, gravatar_size=32, show_all=True)
$.extend(options, extra_options); $.extend(options, extra_options);
$.ajax({ $.ajax({
url: make_uri("/api/1.0/activity", options), url: makeURI("/api/1.0/activity", options),
dataType: "json", dataType: "json",
success: function (data) { success: function (data) {
if (data["activity"].length < page_size) { if (data["activity"].length < page_size) {
@ -33,7 +33,6 @@ show_record_type=True, show_user_gravatar=True, gravatar_size=32, show_all=True)
$('#activity_header').hide(); $('#activity_header').hide();
} }
$.each(data["activity"], function() { $.each(data["activity"], function() {
console.log(this);
extendWithGravatar(this, {{ gravatar_size }}); extendWithGravatar(this, {{ gravatar_size }});
}); });
$("#activity_template").tmpl(data["activity"]).appendTo("#activity_container"); $("#activity_template").tmpl(data["activity"]).appendTo("#activity_container");

View File

@ -3,7 +3,7 @@
<script type="text/javascript"> <script type="text/javascript">
function load_contribution_summary(extra_options) { function load_contribution_summary(extra_options) {
$.ajax({ $.ajax({
url: make_uri("/api/1.0/contribution", extra_options), url: makeURI("/api/1.0/contribution", extra_options),
dataType: "json", dataType: "json",
success: function (data) { success: function (data) {
$("#contribution_template").tmpl(data["contribution"]).appendTo("#contribution_container"); $("#contribution_template").tmpl(data["contribution"]).appendTo("#contribution_container");

View File

@ -3,7 +3,7 @@
<script type="text/javascript"> <script type="text/javascript">
function load_user_profile(extra_options) { function load_user_profile(extra_options) {
$.ajax({ $.ajax({
url: make_uri("/api/1.0/users/{{ user_id }}", extra_options), url: makeURI("/api/1.0/users/{{ user_id }}", extra_options),
dataType: "json", dataType: "json",
success: function (data) { success: function (data) {
var user = data["user"]; var user = data["user"];

View File

@ -41,7 +41,7 @@
'use strict'; 'use strict';
function process_stats(container_id, url, query_options, item_id, metric, text_goal, comparator, data_filter) { function process_stats(container_id, url, query_options, item_id, metric, text_goal, comparator, data_filter) {
$.ajax({ $.ajax({
url: make_uri(url, query_options), url: makeURI(url, query_options),
dataType: "jsonp", dataType: "jsonp",
success: function (data) { success: function (data) {
data = data["stats"]; data = data["stats"];
@ -148,7 +148,7 @@
function goal_core_engineer_in_project(container_id, user_id, project, text_goal) { function goal_core_engineer_in_project(container_id, user_id, project, text_goal) {
$(document).ready(function () { $(document).ready(function () {
$.ajax({ $.ajax({
url: make_uri("/api/1.0/users/" + user_id), url: makeURI("/api/1.0/users/" + user_id),
dataType: "jsonp", dataType: "jsonp",
success: function (data) { success: function (data) {
var user = data.user; var user = data.user;

View File

@ -48,7 +48,7 @@
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function () { $(document).ready(function () {
init_selectors(""); initSelectors("");
}); });
$(function () { $(function () {
$(document).tooltip(); $(document).tooltip();
@ -69,7 +69,7 @@
</div> </div>
<div id="analytics_header"> <div id="analytics_header">
<div style="float: left;"> <div style="float: left;">
<span id="logo"><a href="/?metric={{ metric }}&release={{ release }}&project_type={{ project_type }}">Stackalytics</a></span> <span id="logo"><a href="{{ url_for('overview') }}">Stackalytics</a></span>
</div> </div>
<div class="stackamenu"> <div class="stackamenu">
<ul id="menu-stackamenu"> <ul id="menu-stackamenu">
@ -83,33 +83,33 @@
<div class="drops"> <div class="drops">
<div class="drop"> <div class="drop">
<label for="release" title="Official releases of OpenStack">Release</label> <label for="release_selector" title="Official releases of OpenStack">Release</label>
<input type="hidden" id="release" style="width: 140px" data-placeholder="Select release"/> <input type="hidden" id="release_selector" style="width: 140px" data-placeholder="Select release"/>
</div> </div>
<div class="drop"> <div class="drop">
<label for="project_type" title="Project type groups modules of same kind: official (integrated, incubated, other) or belonging to same organization (stackforge, infra)">Project Type</label> <label for="project_type_selector" title="Project type groups modules of same kind: official (integrated, incubated, other) or belonging to same organization (stackforge, infra)">Project Type</label>
<input type="hidden" id="project_type" style="width: 140px" data-placeholder="Select project type"/> <input type="hidden" id="project_type_selector" style="width: 140px" data-placeholder="Select project type"/>
</div> </div>
<div class="drop"> <div class="drop">
<label for="module" title="Module represents a repo (black), official program (violet) or pre-configured group of modules (cyan)">Module</label> <label for="module_selector" title="Module represents a repo (black), official program (violet) or pre-configured group of modules (cyan)">Module</label>
<input type="hidden" id="module" style="width: 140px" data-placeholder="Any module" value="{{ module }}"/> <input type="hidden" id="module_selector" style="width: 140px" data-placeholder="Any module"/>
</div> </div>
<div class="drop"> <div class="drop">
<label for="company" title="Company name">Company</label> <label for="company_selector" title="Company name">Company</label>
<input type="hidden" id="company" style="width: 140px" data-placeholder="Any company" value="{{ company }}"/> <input type="hidden" id="company_selector" style="width: 140px" data-placeholder="Any company"/>
</div> </div>
<div class="drop"> <div class="drop">
<label for="user" title="Name of engineer as configured in Launchpad or default_data.json">Engineer</label> <label for="user_id_selector" title="Name of engineer as configured in Launchpad or default_data.json">Engineer</label>
<input type="hidden" id="user" style="width: 140px" data-placeholder="Any engineer" value="{{ user_id }}"/> <input type="hidden" id="user_id_selector" style="width: 140px" data-placeholder="Any engineer"/>
</div> </div>
<div class="drop"> <div class="drop">
<label for="metric" title="One of available metrics">Metric</label> <label for="metric_selector" title="One of available metrics">Metric</label>
<input type="hidden" id="metric" style="width: 140px" data-placeholder="Select metric"/> <input type="hidden" id="metric_selector" style="width: 140px" data-placeholder="Select metric"/>
</div> </div>
</div> </div>

View File

@ -67,7 +67,7 @@
}); });
function make_options() { function make_options() {
var options = make_std_options(); var options = getUrlVars();
options['review_nth'] = $('#review_nth').val(); options['review_nth'] = $('#review_nth').val();
return options; return options;
} }

View File

@ -9,7 +9,7 @@
{% block scripts %} {% block scripts %}
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function () { $(document).ready(function () {
render_punch_card("punch_card", [{{ punch_card_data }}]); renderPunchCard("punch_card", [{{ punch_card_data }}]);
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -30,7 +30,7 @@ Contribution into {{ module }} for the last {{ days }} days
var table_id = "review_stats_table"; var table_id = "review_stats_table";
$.ajax({ $.ajax({
url: make_uri("/api/1.0/stats/engineers_extended?project_type=all&metric=marks&module={{ module }}&release=all&start_date={{ start_date }}"), url: makeURI("/api/1.0/stats/engineers_extended?project_type=all&metric=marks&module={{ module }}&release=all&start_date={{ start_date }}"),
dataType: "json", dataType: "json",
success: function (data) { success: function (data) {
var tableData = data["stats"]; var tableData = data["stats"];
@ -57,8 +57,8 @@ Contribution into {{ module }} for the last {{ days }} days
for (i = 0; i < tableData.length; i++) { for (i = 0; i < tableData.length; i++) {
if (tableData[i].id) { if (tableData[i].id) {
var user_link = make_uri("/", {user_id: tableData[i].id, metric: "marks"}); var user_link = makeURI("/", {user_id: tableData[i].id, metric: "marks"});
var company_link = make_uri("/", {company: tableData[i].company, metric: "marks"}); var company_link = makeURI("/", {company: tableData[i].company, metric: "marks"});
tableData[i].link = "<a href=\"" + user_link + "\">" + tableData[i].name + "</a>" + tableData[i].link = "<a href=\"" + user_link + "\">" + tableData[i].name + "</a>" +
" <a href=\"" + company_link + "\">" + "(" + tableData[i].company + ")</a>" " <a href=\"" + company_link + "\">" + "(" + tableData[i].company + ")</a>"
} else { } else {

View File

@ -29,7 +29,14 @@
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery.timeago.js') }}"></script> <script type="text/javascript" src="{{ url_for('static', filename='js/jquery.timeago.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/driverlog-ui.js') }}"></script> <script type="text/javascript" src="{{ url_for('static', filename='js/driverlog-ui.js') }}"></script>
{% block scripts %}{% endblock %} <script type="text/javascript">
var base_url = "http://stackalytics.com/driverlog/";
$(document).ready(function () {
initSelectors(base_url);
showSummary(base_url);
});
</script>
{% endblock %} {% endblock %}
@ -53,31 +60,21 @@
</div> </div>
</div> </div>
<script type="text/javascript">
var base_url = "http://stackalytics.com/driverlog/";
$(document).ready(function () {
init_selectors(base_url);
show_summary(base_url);
});
</script>
<div class="drops"> <div class="drops">
<div class="drop"> <div class="drop">
<label for="release_selector" title="OpenStack Release">Release</label> <label for="release" title="OpenStack Release">Release</label>
<input type="hidden" id="release_selector" style="width:240px"/> <input type="hidden" id="release" style="width:240px" data-placeholder="Any release"/>
</div> </div>
<div class="drop"> <div class="drop">
<label for="project_selector" title="OpenStack Project">Project</label> <label for="project_id" title="OpenStack Project">Project</label>
<input type="hidden" id="project_selector" style="width:240px"/> <input type="hidden" id="project_id" style="width:240px" data-placeholder="Any project"/>
</div> </div>
<div class="drop"> <div class="drop">
<label for="vendor_selector" title="Vendor">Vendor</label> <label for="vendor" title="Vendor">Vendor</label>
<input type="hidden" id="vendor_selector" style="width:240px"/> <input type="hidden" id="vendor" style="width:240px" data-placeholder="Any vendor"/>
</div> </div>
</div> </div>

View File

@ -1,17 +1,52 @@
{% extends "reports/base_report.html" %} {% extends "base.html" %}
{% block title %} {% block head %}
OpenStack foundation members <title>Members of OpenStack Foundation</title>
{% endblock %}
{% block scripts %} <meta name="keywords" content="openstack, contribution, statistics, community, review, commit, report, havana, grizzly, icehouse, members"/>
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/style.css') }}">
<link href='http://fonts.googleapis.com/css?family=PT+Sans:400,700,400italic&subset=latin,cyrillic' rel='stylesheet' type='text/css' />
<link href='http://fonts.googleapis.com/css?family=PT+Sans+Caption&subset=latin,cyrillic' rel='stylesheet' type='text/css' />
<link href='http://fonts.googleapis.com/css?family=PT+Sans+Narrow:400,700&subset=latin,cyrillic' rel='stylesheet' type='text/css' />
<link rel="icon" href="{{ url_for('static', filename='images/favicon.png') }}" type="image/png"/>
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/jquery.dataTables.css') }}">
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/jquery.jqplot.min.css') }}">
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/select2.css') }}">
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/style.css') }}">
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/moonfonts.css') }}">
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery-1.9.1.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery-ui.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery.dataTables.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery.jqplot.min.js') }}"></script>
<!--[if lt IE 9]><script type="text/javascript" src="{{ url_for('static', filename='js/excanvas.min.js') }}"></script><![endif]-->
<script type="text/javascript" src="{{ url_for('static', filename='js/jqplot.json2.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/jqplot.pieRenderer.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/jqplot.barRenderer.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/jqplot.bubbleRenderer.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/jqplot.categoryAxisRenderer.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/jqplot.dateAxisRenderer.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/jqplot.canvasTextRenderer.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/jqplot.canvasAxisLabelRenderer.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/jqplot.canvasAxisTickRenderer.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/jqplot.cursor.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/jqplot.highlighter.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/select2.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery.tmpl.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/md5.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery.gravatar.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery.timeago.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/stackalytics-ui.js') }}"></script>
<script type="text/javascript"> <script type="text/javascript">
function get_start_date() { function get_start_date() {
var days = {{ days }}; var days = {{ days }};
var start_date = Math.round(new Date().getTime() / 1000) - days * 24 * 60 * 60; return Math.round(new Date().getTime() / 1000) - days * 24 * 60 * 60;
return start_date;
} }
function show_engineers_table(options) { function show_engineers_table(options) {
@ -20,7 +55,7 @@
var company = $('#company_selector').val(); var company = $('#company_selector').val();
$.ajax({ $.ajax({
url: make_uri("/api/1.0/members", options), url: makeURI("/api/1.0/members", options),
dataType: "json", dataType: "json",
success: function (data) { success: function (data) {
var tableData = data["members"]; var tableData = data["members"];
@ -65,7 +100,7 @@
var table_id = "new_companies_table"; var table_id = "new_companies_table";
$.ajax({ $.ajax({
url: make_uri("/api/1.0/new_companies", options), url: makeURI("/api/1.0/new_companies", options),
dataType: "json", dataType: "json",
success: function (data) { success: function (data) {
var tableData = data["stats"]; var tableData = data["stats"];
@ -77,7 +112,7 @@
} }
for (i = 0; i < tableData.length; i++) { for (i = 0; i < tableData.length; i++) {
var company_link = make_uri('/report/members', {company:tableData[i].name}); var company_link = makeURI('/report/members', {company:tableData[i].name});
tableData[i].link = "<a href=\"" + company_link + "\">" + tableData[i].name + "</a>"; tableData[i].link = "<a href=\"" + company_link + "\">" + tableData[i].name + "</a>";
tableData[i].date = tableData[i].date_str; tableData[i].date = tableData[i].date_str;
} }
@ -108,7 +143,7 @@
var table_id = "companies_table"; var table_id = "companies_table";
$.ajax({ $.ajax({
url: make_uri("/api/1.0/stats/companies", options), url: makeURI("/api/1.0/stats/companies", options),
dataType: "json", dataType: "json",
success: function (data) { success: function (data) {
var tableData = data["stats"]; var tableData = data["stats"];
@ -120,7 +155,7 @@
} }
for (i = 0; i < tableData.length; i++) { for (i = 0; i < tableData.length; i++) {
var company_link = make_uri('/report/members', {company:tableData[i].name}); var company_link = makeURI('/report/members', {company:tableData[i].name});
tableData[i].link = "<a href=\"" + company_link + "\">" + tableData[i].name + "</a>"; tableData[i].link = "<a href=\"" + company_link + "\">" + tableData[i].name + "</a>";
tableData[i].count = tableData[i].metric; tableData[i].count = tableData[i].metric;
} }
@ -146,12 +181,12 @@
}); });
} }
function renderChart(url, container_id, chart_id, options) { function renderChart(url, chart_id, options) {
$(document).ready(function () { $(document).ready(function () {
$.ajax({ $.ajax({
url: make_uri(url, options), url: makeURI(url, options),
dataType: "jsonp", dataType: "jsonp",
success: function (data) { success: function (data) {
@ -222,7 +257,7 @@
{% if company == '' %} {% if company == '' %}
show_companies_table(base_options); show_companies_table(base_options);
show_new_companies_table(base_options); show_new_companies_table(base_options);
renderChart("/api/1.0/stats/companies", "members_container", "members_chart", base_options); renderChart("/api/1.0/stats/companies", "members_chart", base_options);
{% else %} {% else %}
$('#companies_block').hide(); $('#companies_block').hide();
$('#new_companies_table_header').hide(); $('#new_companies_table_header').hide();
@ -235,31 +270,13 @@
var start_date = get_start_date(); var start_date = get_start_date();
var base_options = { metric: 'members', project_type: 'all', release: 'all', start_date: start_date }; var base_options = { metric: 'members', project_type: 'all', release: 'all', start_date: start_date };
$("#company_selector").select2({ initSingleSelector("company", makeURI("/api/1.0/companies", base_options), {allowClear: true});
allowClear: true,
ajax: { $("#days_selector").val({{ days }}).select2().on('change', function (evt) {
url: make_uri("/api/1.0/companies", base_options), reload();
dataType: 'jsonp',
data: function (term, page) {
return {
company_name: term
};
},
results: function (data, page) {
return {results: data["companies"]};
}
},
initSelection: function (element, callback) {
var id = $(element).val();
if (id !== "") {
$.ajax(make_uri("/api/1.0/companies/" + id, base_options), {
dataType: "jsonp"
}).done(function (data) {
callback(data["company"]);
});
}
}
}); });
show_page();
}); });
</script> </script>
@ -278,16 +295,6 @@
} }
</style> </style>
{% endblock %}
{% block content %}
<div class="navigation">
<div id="timeline"
style="width: 100%; height: 120px; margin-top: 15px;"></div>
</div>
<script type='text/javascript'> <script type='text/javascript'>
$(document).ready(function () { $(document).ready(function () {
$('#days_selector').val({{ days }}); $('#days_selector').val({{ days }});
@ -304,12 +311,39 @@
}); });
</script> </script>
{% endblock %}
{% block body %}
<div class="page">
<div class="aheader">
<div style="float: right; margin-top: 10px; margin-right: 20px;">
<a href="https://wiki.openstack.org/wiki/Stackalytics" target="_blank">About ↗</a>
</div>
<div id="analytics_header">
<div style="float: left;">
<span id="logo"><a href="/?metric={{ metric }}&release={{ release }}&project_type={{ project_type }}">Stackalytics</a></span>
</div>
<div class="stackamenu" style="margin-left: 240px;">
<ul id="menu-stackamenu">
<li class="menu-item"><a href="{{ url_for('overview') }}"><span class="icon-pie"></span>Code Contribution</a></li>
<li class="menu-item current-menu-item"><a href="/report/members"><span class="icon-users"></span>Member Directory</a></li>
<li class="menu-item"><a href="/report/driverlog"><span class="icon-cogs"></span>Vendor Drivers</a></li>
</ul>
</div>
</div>
<div class="navigation">
<div id="timeline"
style="width: 100%; height: 120px; margin-top: 15px;"></div>
</div>
<div class="drops"> <div class="drops">
<div class="drop" style="margin-top: 1em;"> <div class="drop" style="margin-top: 1em;">
<label for="days_selector">Joined during period</label> <label for="days_selector">Joined during period</label>
<select id="days_selector" name="days_selector" <select id="days_selector" name="days_selector"
style="min-width: 140px;" style="min-width: 140px;"
data-placeholder="Select date_period #"> data-placeholder="Select period">
<option value="7">week</option> <option value="7">week</option>
<option value="14">two weeks</option> <option value="14">two weeks</option>
<option value="31">month</option> <option value="31">month</option>
@ -323,7 +357,7 @@
<div class="drop" style="margin-top: 1em;"> <div class="drop" style="margin-top: 1em;">
<label for="company_selector">Company</label> <label for="company_selector">Company</label>
<input id="company_selector" style="width: 140px" <input id="company_selector" style="width: 140px"
data-placeholder="Any company" value="{{ company }}"/> data-placeholder="Any company"/>
</div> </div>
</div> </div>
@ -403,4 +437,7 @@
</tr> </tr>
</table> </table>
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -7,8 +7,8 @@ Open reviews report for {{ module }}
{% block scripts %} {% block scripts %}
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function () { $(document).ready(function () {
render_bar_chart("latest_revision_chart", [{{ latest_revision.chart_data }}]); renderBarChart("latest_revision_chart", [{{ latest_revision.chart_data }}]);
render_bar_chart("first_revision_chart", [{{ first_revision.chart_data }}]); renderBarChart("first_revision_chart", [{{ first_revision.chart_data }}]);
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -10,7 +10,7 @@
{% block scripts %} {% block scripts %}
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function () { $(document).ready(function () {
render_punch_card("punch_card", [{{ punch_card_data }}]); renderPunchCard("punch_card", [{{ punch_card_data }}]);
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -42,7 +42,7 @@
var base_url = "http://stackalytics.com"; var base_url = "http://stackalytics.com";
$(document).ready(function () { $(document).ready(function () {
init_selectors(base_url); initSelectors(base_url);
renderTableAndChart(base_url + "/api/1.0/stats/companies", null, null, "company_chart", null); renderTableAndChart(base_url + "/api/1.0/stats/companies", null, null, "company_chart", null);
}); });

View File

@ -118,21 +118,7 @@ def _init_project_types(vault):
runtime_storage_inst = vault['runtime_storage'] runtime_storage_inst = vault['runtime_storage']
project_types = runtime_storage_inst.get_by_key('project_types') or {} project_types = runtime_storage_inst.get_by_key('project_types') or {}
result = [] vault['project_types'] = project_types
parent = None
for pt in project_types:
is_child = pt.get('child', False)
if parent and is_child:
item = {'id': pt['id'], 'text': pt['title']}
if 'items' in parent:
parent['items'].append(item)
else:
parent['items'] = [item]
else:
parent = pt
result.append(parent)
vault['project_types'] = result
vault['project_types_index'] = dict([(pt['id'], pt) vault['project_types_index'] = dict([(pt['id'], pt)
for pt in project_types]) for pt in project_types])

View File

@ -275,12 +275,14 @@ def get_contribution_json(records, **kwargs):
@decorators.exception_handler() @decorators.exception_handler()
@decorators.response() @decorators.response()
@decorators.cached(ignore=['company']) @decorators.cached(ignore=['company'])
@decorators.jsonify('companies') @decorators.jsonify()
@decorators.record_filter(ignore=['company']) @decorators.record_filter(ignore=['company'])
def get_companies_json(record_ids, **kwargs): def get_companies_json(record_ids, **kwargs):
memory_storage = vault.get_memory_storage() memory_storage = vault.get_memory_storage()
companies = memory_storage.get_index_keys_by_record_ids( companies = memory_storage.get_index_keys_by_record_ids(
'company_name', record_ids) 'company_name', record_ids)
if kwargs['_params']['company']:
companies.add(kwargs['_params']['company'][0])
result = [memory_storage.get_original_company_name(company) result = [memory_storage.get_original_company_name(company)
for company in companies] for company in companies]
@ -293,7 +295,7 @@ def get_companies_json(record_ids, **kwargs):
@decorators.exception_handler() @decorators.exception_handler()
@decorators.response() @decorators.response()
@decorators.cached(ignore=['module']) @decorators.cached(ignore=['module'])
@decorators.jsonify('modules') @decorators.jsonify()
@decorators.record_filter(ignore=['module']) @decorators.record_filter(ignore=['module'])
def get_modules_json(record_ids, **kwargs): def get_modules_json(record_ids, **kwargs):
module_id_index = vault.get_vault()['module_id_index'] module_id_index = vault.get_vault()['module_id_index']
@ -313,7 +315,8 @@ def get_modules_json(record_ids, **kwargs):
# keep only modules with specified tags # keep only modules with specified tags
if tags: if tags:
module_ids = set(module_id for module_id in module_ids module_ids = set(module_id for module_id in module_ids
if module_id_index[module_id].get('tag') in tags) if ((module_id in module_id_index) and
(module_id_index[module_id].get('tag') in tags)))
result = [] result = []
for module_id in module_ids: for module_id in module_ids:
@ -327,8 +330,9 @@ def get_modules_json(record_ids, **kwargs):
@app.route('/api/1.0/companies/<company_name>') @app.route('/api/1.0/companies/<company_name>')
@decorators.response() @decorators.response()
@decorators.cached()
@decorators.jsonify('company') @decorators.jsonify('company')
def get_company(company_name): def get_company(company_name, **kwargs):
memory_storage_inst = vault.get_memory_storage() memory_storage_inst = vault.get_memory_storage()
for company in memory_storage_inst.get_companies(): for company in memory_storage_inst.get_companies():
if company.lower() == company_name.lower(): if company.lower() == company_name.lower():
@ -342,8 +346,9 @@ def get_company(company_name):
@app.route('/api/1.0/modules/<module>') @app.route('/api/1.0/modules/<module>')
@decorators.response() @decorators.response()
@decorators.cached()
@decorators.jsonify('module') @decorators.jsonify('module')
def get_module(module): def get_module(module, **kwargs):
module_id_index = vault.get_vault()['module_id_index'] module_id_index = vault.get_vault()['module_id_index']
module = module.lower() module = module.lower()
if module in module_id_index: if module in module_id_index:
@ -410,11 +415,13 @@ def get_bpd(records, **kwargs):
@decorators.exception_handler() @decorators.exception_handler()
@decorators.response() @decorators.response()
@decorators.cached(ignore=['user_id']) @decorators.cached(ignore=['user_id'])
@decorators.jsonify('users') @decorators.jsonify()
@decorators.record_filter(ignore=['user_id']) @decorators.record_filter(ignore=['user_id'])
def get_users_json(record_ids, **kwargs): def get_users_json(record_ids, **kwargs):
user_ids = vault.get_memory_storage().get_index_keys_by_record_ids( user_ids = vault.get_memory_storage().get_index_keys_by_record_ids(
'user_id', record_ids) 'user_id', record_ids)
if kwargs['_params']['user_id']:
user_ids.add(kwargs['_params']['user_id'][0])
result = [{'id': user_id, result = [{'id': user_id,
'text': (vault.get_user_from_runtime_storage(user_id) 'text': (vault.get_user_from_runtime_storage(user_id)
@ -440,64 +447,35 @@ def get_user(user_id):
@decorators.exception_handler() @decorators.exception_handler()
@decorators.response() @decorators.response()
@decorators.cached(ignore=parameters.FILTER_PARAMETERS) @decorators.cached(ignore=parameters.FILTER_PARAMETERS)
@decorators.jsonify('releases') @decorators.jsonify(root=('data', 'default'))
def get_releases_json(**kwargs): def get_releases_json(**kwargs):
return [{'id': r['release_name'], 'text': r['release_name'].capitalize()} return ([{'id': r['release_name'], 'text': r['release_name'].capitalize()}
for r in vault.get_release_options()] for r in vault.get_release_options()],
parameters.get_default('release'))
@app.route('/api/1.0/releases/<release>')
@decorators.response()
@decorators.jsonify('release')
def get_release_json(release):
if release != 'all':
if release not in vault.get_vault()['releases']:
release = parameters.get_default('release')
return {'id': release, 'text': release.capitalize()}
@app.route('/api/1.0/metrics') @app.route('/api/1.0/metrics')
@decorators.exception_handler() @decorators.exception_handler()
@decorators.response() @decorators.response()
@decorators.cached(ignore=parameters.FILTER_PARAMETERS) @decorators.cached(ignore=parameters.FILTER_PARAMETERS)
@decorators.jsonify('metrics') @decorators.jsonify(root=('data', 'default'))
def get_metrics_json(**kwargs): def get_metrics_json(**kwargs):
return sorted([{'id': m, 'text': t} return (sorted([{'id': m, 'text': t} for m, t in
for m, t in six.iteritems(parameters.METRIC_LABELS)], six.iteritems(parameters.METRIC_LABELS)],
key=operator.itemgetter('text')) key=operator.itemgetter('text')),
parameters.get_default('metric'))
@app.route('/api/1.0/metrics/<metric>')
@decorators.response()
@decorators.jsonify('metric')
@decorators.exception_handler()
def get_metric_json(metric):
if metric not in parameters.METRIC_LABELS:
metric = parameters.get_default('metric')
return {'id': metric, 'text': parameters.METRIC_LABELS[metric]}
@app.route('/api/1.0/project_types') @app.route('/api/1.0/project_types')
@decorators.response() @decorators.response()
@decorators.exception_handler() @decorators.exception_handler()
@decorators.cached(ignore=parameters.FILTER_PARAMETERS) @decorators.cached(ignore=parameters.FILTER_PARAMETERS)
@decorators.jsonify('project_types') @decorators.jsonify(root=('data', 'default'))
def get_project_types_json(**kwargs): def get_project_types_json(**kwargs):
return [{'id': pt['id'], 'text': pt['title'], 'items': pt.get('items', [])} return ([{'id': pt['id'], 'text': pt['title'],
for pt in vault.get_project_types()] 'child': pt.get('child', False)}
for pt in vault.get_project_types()],
parameters.get_default('project_type'))
@app.route('/api/1.0/project_types/<project_type>')
@decorators.response()
@decorators.jsonify('project_type')
@decorators.exception_handler()
def get_project_type_json(project_type):
if not vault.is_project_type_valid(project_type):
project_type = parameters.get_default('project_type')
pt = vault.get_project_type(project_type)
return {'id': pt['id'], 'text': pt['title']}
def _get_week(kwargs, param_name): def _get_week(kwargs, param_name):

View File

@ -59,14 +59,14 @@ class TestAPICompanies(test_api.TestAPI):
response = self.app.get('/api/1.0/companies?metric=commits&' response = self.app.get('/api/1.0/companies?metric=commits&'
'module=glance') 'module=glance')
companies = json.loads(response.data)['companies'] companies = json.loads(response.data)['data']
self.assertEqual([{'id': 'ibm', 'text': 'IBM'}, self.assertEqual([{'id': 'ibm', 'text': 'IBM'},
{'id': 'nec', 'text': 'NEC'}, {'id': 'nec', 'text': 'NEC'},
{'id': 'ntt', 'text': 'NTT'}], companies) {'id': 'ntt', 'text': 'NTT'}], companies)
response = self.app.get('/api/1.0/companies?metric=marks&' response = self.app.get('/api/1.0/companies?metric=marks&'
'module=glance') 'module=glance')
companies = json.loads(response.data)['companies'] companies = json.loads(response.data)['data']
self.assertEqual([{'id': 'ibm', 'text': 'IBM'}, self.assertEqual([{'id': 'ibm', 'text': 'IBM'},
{'id': 'nec', 'text': 'NEC'}], companies) {'id': 'nec', 'text': 'NEC'}], companies)

View File

@ -45,7 +45,7 @@ class TestAPIModules(test_api.TestAPI):
response = self.app.get('/api/1.0/modules?' response = self.app.get('/api/1.0/modules?'
'project_type=all&metric=commits') 'project_type=all&metric=commits')
modules = json.loads(response.data)['modules'] modules = json.loads(response.data)['data']
self.assertEqual( self.assertEqual(
[{'id': 'glance', 'text': 'glance', 'tag': 'module'}, [{'id': 'glance', 'text': 'glance', 'tag': 'module'},
{'id': 'nova', 'text': 'nova', 'tag': 'module'}, {'id': 'nova', 'text': 'nova', 'tag': 'module'},
@ -58,7 +58,7 @@ class TestAPIModules(test_api.TestAPI):
response = self.app.get('/api/1.0/modules?module=nova-group&' response = self.app.get('/api/1.0/modules?module=nova-group&'
'project_type=integrated&metric=commits') 'project_type=integrated&metric=commits')
modules = json.loads(response.data)['modules'] modules = json.loads(response.data)['data']
self.assertEqual( self.assertEqual(
[{'id': 'glance', 'text': 'glance', 'tag': 'module'}, [{'id': 'glance', 'text': 'glance', 'tag': 'module'},
{'id': 'nova', 'text': 'nova', 'tag': 'module'}, {'id': 'nova', 'text': 'nova', 'tag': 'module'},

View File

@ -28,17 +28,7 @@ class TestAPIReleases(test_api.TestAPI):
{'release_name': 'icehouse', 'end_date': 1397692800}]}, {'release_name': 'icehouse', 'end_date': 1397692800}]},
test_api.make_records(record_type=['commit'])): test_api.make_records(record_type=['commit'])):
response = self.app.get('/api/1.0/releases') response = self.app.get('/api/1.0/releases')
releases = json.loads(response.data)['releases'] releases = json.loads(response.data)['data']
self.assertEqual(3, len(releases)) self.assertEqual(3, len(releases))
self.assertIn({'id': 'all', 'text': 'All'}, releases) self.assertIn({'id': 'all', 'text': 'All'}, releases)
self.assertIn({'id': 'icehouse', 'text': 'Icehouse'}, releases) self.assertIn({'id': 'icehouse', 'text': 'Icehouse'}, releases)
def test_release_details(self):
with test_api.make_runtime_storage(
{'releases': [
{'release_name': 'prehistory', 'end_date': 1365033600},
{'release_name': 'icehouse', 'end_date': 1397692800}]},
test_api.make_records(record_type=['commit'])):
response = self.app.get('/api/1.0/releases/icehouse')
release = json.loads(response.data)['release']
self.assertEqual({'id': 'icehouse', 'text': 'Icehouse'}, release)

View File

@ -36,7 +36,7 @@ class TestAPIUsers(test_api.TestAPI):
user_id=['john_doe', 'bill_smith'])): user_id=['john_doe', 'bill_smith'])):
response = self.app.get('/api/1.0/users?' response = self.app.get('/api/1.0/users?'
'module=nova&metric=commits') 'module=nova&metric=commits')
users = json.loads(response.data)['users'] users = json.loads(response.data)['data']
self.assertEqual(2, len(users)) self.assertEqual(2, len(users))
self.assertIn({'id': 'john_doe', 'text': 'John Doe'}, users) self.assertIn({'id': 'john_doe', 'text': 'John Doe'}, users)
self.assertIn({'id': 'bill_smith', 'text': 'Bill Smith'}, users) self.assertIn({'id': 'bill_smith', 'text': 'Bill Smith'}, users)