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):
@functools.wraps(func)
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

View File

@ -470,3 +470,13 @@ div.stackamenu li.current-menu-item a span {
color: white;
/*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;
}
function make_std_options() {
var options = {};
options['project_id'] = $('#project_selector').val();
options['vendor'] = $('#vendor_selector').val();
options['release_id'] = $('#release_selector').val();
return options;
function getPageState() {
return {
project_id: $('#project_id').val(),
vendor: $('#vendor').val(),
release_id: $('#release_id').val()
};
}
function reload() {
var ops = {};
$.extend(ops, getUrlVars());
$.extend(ops, make_std_options());
window.location.search = $.map(ops,function (val, index) {
return index + "=" + encodeURIComponent(val);
function reload(extra) {
window.location.search = $.map($.extend(getPageState(), extra), function (val, index) {
return val? (index + "=" + encodeURIComponent(val)) : null;
}).join("&")
}
function init_selectors(base_url) {
$(document).tooltip();
function initSelectors(base_url) {
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({
allowClear: true,
placeholder: "Select Project",
ajax: {
url: make_uri(base_url + "api/1.0/list/project_ids"),
dataType: 'jsonp',
data: function (term, page) {
return {
query: term
};
},
results: function (data, page) {
return {results: data["project_ids"]};
$.ajax({
url: api_url,
dataType: "jsonp",
success: function (data) {
var initial_value = getUrlVars()[name];
if (!initial_value && data["default"]) {
initial_value = data["default"];
}
$("#" + name).
val(initial_value).
select2($.extend({
data: data[data_container]
}, 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_template").tmpl(driver).appendTo("#driver_info_container");
@ -194,7 +128,7 @@ function show_driver_info(driver) {
$("#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({
autoOpen: false,
width: "70%",
@ -212,11 +146,11 @@ function setup_driver_info_handler(table_id, element_id, driver) {
event.preventDefault();
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_id = "data_table";
@ -240,7 +174,7 @@ function show_summary(base_url) {
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 = [];
for (var j = 0; j < tableData[i].releases_info.length; j++) {
@ -300,6 +234,7 @@ function show_summary(base_url) {
],
"iDisplayLength": -1,
"bAutoWidth": false,
"bPaginate": false,
"aaData": tableData,
"aoColumns": tableColumns
});

View File

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

View File

@ -23,7 +23,7 @@ show_record_type=True, show_user_gravatar=True, gravatar_size=32, show_all=True)
$.extend(options, extra_options);
$.ajax({
url: make_uri("/api/1.0/activity", options),
url: makeURI("/api/1.0/activity", options),
dataType: "json",
success: function (data) {
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();
}
$.each(data["activity"], function() {
console.log(this);
extendWithGravatar(this, {{ gravatar_size }});
});
$("#activity_template").tmpl(data["activity"]).appendTo("#activity_container");

View File

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

View File

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

View File

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

View File

@ -48,7 +48,7 @@
<script type="text/javascript">
$(document).ready(function () {
init_selectors("");
initSelectors("");
});
$(function () {
$(document).tooltip();
@ -69,7 +69,7 @@
</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>
<span id="logo"><a href="{{ url_for('overview') }}">Stackalytics</a></span>
</div>
<div class="stackamenu">
<ul id="menu-stackamenu">
@ -83,33 +83,33 @@
<div class="drops">
<div class="drop">
<label for="release" title="Official releases of OpenStack">Release</label>
<input type="hidden" id="release" style="width: 140px" data-placeholder="Select release"/>
<label for="release_selector" title="Official releases of OpenStack">Release</label>
<input type="hidden" id="release_selector" style="width: 140px" data-placeholder="Select release"/>
</div>
<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>
<input type="hidden" id="project_type" style="width: 140px" data-placeholder="Select project type"/>
<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_selector" style="width: 140px" data-placeholder="Select project type"/>
</div>
<div class="drop">
<label for="module" 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 }}"/>
<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_selector" style="width: 140px" data-placeholder="Any module"/>
</div>
<div class="drop">
<label for="company" title="Company name">Company</label>
<input type="hidden" id="company" style="width: 140px" data-placeholder="Any company" value="{{ company }}"/>
<label for="company_selector" title="Company name">Company</label>
<input type="hidden" id="company_selector" style="width: 140px" data-placeholder="Any company"/>
</div>
<div class="drop">
<label for="user" 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 }}"/>
<label for="user_id_selector" title="Name of engineer as configured in Launchpad or default_data.json">Engineer</label>
<input type="hidden" id="user_id_selector" style="width: 140px" data-placeholder="Any engineer"/>
</div>
<div class="drop">
<label for="metric" title="One of available metrics">Metric</label>
<input type="hidden" id="metric" style="width: 140px" data-placeholder="Select metric"/>
<label for="metric_selector" title="One of available metrics">Metric</label>
<input type="hidden" id="metric_selector" style="width: 140px" data-placeholder="Select metric"/>
</div>
</div>

View File

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

View File

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

View File

@ -30,7 +30,7 @@ Contribution into {{ module }} for the last {{ days }} days
var table_id = "review_stats_table";
$.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",
success: function (data) {
var tableData = data["stats"];
@ -57,8 +57,8 @@ Contribution into {{ module }} for the last {{ days }} days
for (i = 0; i < tableData.length; i++) {
if (tableData[i].id) {
var user_link = make_uri("/", {user_id: tableData[i].id, metric: "marks"});
var company_link = make_uri("/", {company: tableData[i].company, metric: "marks"});
var user_link = makeURI("/", {user_id: tableData[i].id, metric: "marks"});
var company_link = makeURI("/", {company: tableData[i].company, metric: "marks"});
tableData[i].link = "<a href=\"" + user_link + "\">" + tableData[i].name + "</a>" +
" <a href=\"" + company_link + "\">" + "(" + tableData[i].company + ")</a>"
} 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/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 %}
@ -53,31 +60,21 @@
</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="drop">
<label for="release_selector" title="OpenStack Release">Release</label>
<input type="hidden" id="release_selector" style="width:240px"/>
<label for="release" title="OpenStack Release">Release</label>
<input type="hidden" id="release" style="width:240px" data-placeholder="Any release"/>
</div>
<div class="drop">
<label for="project_selector" title="OpenStack Project">Project</label>
<input type="hidden" id="project_selector" style="width:240px"/>
<label for="project_id" title="OpenStack Project">Project</label>
<input type="hidden" id="project_id" style="width:240px" data-placeholder="Any project"/>
</div>
<div class="drop">
<label for="vendor_selector" title="Vendor">Vendor</label>
<input type="hidden" id="vendor_selector" style="width:240px"/>
<label for="vendor" title="Vendor">Vendor</label>
<input type="hidden" id="vendor" style="width:240px" data-placeholder="Any vendor"/>
</div>
</div>

View File

@ -1,17 +1,52 @@
{% extends "reports/base_report.html" %}
{% extends "base.html" %}
{% block title %}
OpenStack foundation members
{% endblock %}
{% block head %}
<title>Members of OpenStack Foundation</title>
{% 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">
function get_start_date() {
var days = {{ days }};
var start_date = Math.round(new Date().getTime() / 1000) - days * 24 * 60 * 60;
return start_date;
return Math.round(new Date().getTime() / 1000) - days * 24 * 60 * 60;
}
function show_engineers_table(options) {
@ -20,7 +55,7 @@
var company = $('#company_selector').val();
$.ajax({
url: make_uri("/api/1.0/members", options),
url: makeURI("/api/1.0/members", options),
dataType: "json",
success: function (data) {
var tableData = data["members"];
@ -65,7 +100,7 @@
var table_id = "new_companies_table";
$.ajax({
url: make_uri("/api/1.0/new_companies", options),
url: makeURI("/api/1.0/new_companies", options),
dataType: "json",
success: function (data) {
var tableData = data["stats"];
@ -77,7 +112,7 @@
}
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].date = tableData[i].date_str;
}
@ -108,7 +143,7 @@
var table_id = "companies_table";
$.ajax({
url: make_uri("/api/1.0/stats/companies", options),
url: makeURI("/api/1.0/stats/companies", options),
dataType: "json",
success: function (data) {
var tableData = data["stats"];
@ -120,7 +155,7 @@
}
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].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 () {
$.ajax({
url: make_uri(url, options),
url: makeURI(url, options),
dataType: "jsonp",
success: function (data) {
@ -222,7 +257,7 @@
{% if company == '' %}
show_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 %}
$('#companies_block').hide();
$('#new_companies_table_header').hide();
@ -235,31 +270,13 @@
var start_date = get_start_date();
var base_options = { metric: 'members', project_type: 'all', release: 'all', start_date: start_date };
$("#company_selector").select2({
allowClear: true,
ajax: {
url: make_uri("/api/1.0/companies", base_options),
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"]);
});
}
}
initSingleSelector("company", makeURI("/api/1.0/companies", base_options), {allowClear: true});
$("#days_selector").val({{ days }}).select2().on('change', function (evt) {
reload();
});
show_page();
});
</script>
@ -278,16 +295,6 @@
}
</style>
{% endblock %}
{% block content %}
<div class="navigation">
<div id="timeline"
style="width: 100%; height: 120px; margin-top: 15px;"></div>
</div>
<script type='text/javascript'>
$(document).ready(function () {
$('#days_selector').val({{ days }});
@ -304,12 +311,39 @@
});
</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="drop" style="margin-top: 1em;">
<label for="days_selector">Joined during period</label>
<select id="days_selector" name="days_selector"
style="min-width: 140px;"
data-placeholder="Select date_period #">
data-placeholder="Select period">
<option value="7">week</option>
<option value="14">two weeks</option>
<option value="31">month</option>
@ -323,7 +357,7 @@
<div class="drop" style="margin-top: 1em;">
<label for="company_selector">Company</label>
<input id="company_selector" style="width: 140px"
data-placeholder="Any company" value="{{ company }}"/>
data-placeholder="Any company"/>
</div>
</div>
@ -403,4 +437,7 @@
</tr>
</table>
</div>
</div>
{% endblock %}

View File

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

View File

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

View File

@ -42,7 +42,7 @@
var base_url = "http://stackalytics.com";
$(document).ready(function () {
init_selectors(base_url);
initSelectors(base_url);
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']
project_types = runtime_storage_inst.get_by_key('project_types') or {}
result = []
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'] = project_types
vault['project_types_index'] = dict([(pt['id'], pt)
for pt in project_types])

View File

@ -275,12 +275,14 @@ def get_contribution_json(records, **kwargs):
@decorators.exception_handler()
@decorators.response()
@decorators.cached(ignore=['company'])
@decorators.jsonify('companies')
@decorators.jsonify()
@decorators.record_filter(ignore=['company'])
def get_companies_json(record_ids, **kwargs):
memory_storage = vault.get_memory_storage()
companies = memory_storage.get_index_keys_by_record_ids(
'company_name', record_ids)
if kwargs['_params']['company']:
companies.add(kwargs['_params']['company'][0])
result = [memory_storage.get_original_company_name(company)
for company in companies]
@ -293,7 +295,7 @@ def get_companies_json(record_ids, **kwargs):
@decorators.exception_handler()
@decorators.response()
@decorators.cached(ignore=['module'])
@decorators.jsonify('modules')
@decorators.jsonify()
@decorators.record_filter(ignore=['module'])
def get_modules_json(record_ids, **kwargs):
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
if tags:
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 = []
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>')
@decorators.response()
@decorators.cached()
@decorators.jsonify('company')
def get_company(company_name):
def get_company(company_name, **kwargs):
memory_storage_inst = vault.get_memory_storage()
for company in memory_storage_inst.get_companies():
if company.lower() == company_name.lower():
@ -342,8 +346,9 @@ def get_company(company_name):
@app.route('/api/1.0/modules/<module>')
@decorators.response()
@decorators.cached()
@decorators.jsonify('module')
def get_module(module):
def get_module(module, **kwargs):
module_id_index = vault.get_vault()['module_id_index']
module = module.lower()
if module in module_id_index:
@ -410,11 +415,13 @@ def get_bpd(records, **kwargs):
@decorators.exception_handler()
@decorators.response()
@decorators.cached(ignore=['user_id'])
@decorators.jsonify('users')
@decorators.jsonify()
@decorators.record_filter(ignore=['user_id'])
def get_users_json(record_ids, **kwargs):
user_ids = vault.get_memory_storage().get_index_keys_by_record_ids(
'user_id', record_ids)
if kwargs['_params']['user_id']:
user_ids.add(kwargs['_params']['user_id'][0])
result = [{'id': user_id,
'text': (vault.get_user_from_runtime_storage(user_id)
@ -440,64 +447,35 @@ def get_user(user_id):
@decorators.exception_handler()
@decorators.response()
@decorators.cached(ignore=parameters.FILTER_PARAMETERS)
@decorators.jsonify('releases')
@decorators.jsonify(root=('data', 'default'))
def get_releases_json(**kwargs):
return [{'id': r['release_name'], 'text': r['release_name'].capitalize()}
for r in vault.get_release_options()]
@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()}
return ([{'id': r['release_name'], 'text': r['release_name'].capitalize()}
for r in vault.get_release_options()],
parameters.get_default('release'))
@app.route('/api/1.0/metrics')
@decorators.exception_handler()
@decorators.response()
@decorators.cached(ignore=parameters.FILTER_PARAMETERS)
@decorators.jsonify('metrics')
@decorators.jsonify(root=('data', 'default'))
def get_metrics_json(**kwargs):
return sorted([{'id': m, 'text': t}
for m, t in six.iteritems(parameters.METRIC_LABELS)],
key=operator.itemgetter('text'))
@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]}
return (sorted([{'id': m, 'text': t} for m, t in
six.iteritems(parameters.METRIC_LABELS)],
key=operator.itemgetter('text')),
parameters.get_default('metric'))
@app.route('/api/1.0/project_types')
@decorators.response()
@decorators.exception_handler()
@decorators.cached(ignore=parameters.FILTER_PARAMETERS)
@decorators.jsonify('project_types')
@decorators.jsonify(root=('data', 'default'))
def get_project_types_json(**kwargs):
return [{'id': pt['id'], 'text': pt['title'], 'items': pt.get('items', [])}
for pt in vault.get_project_types()]
@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']}
return ([{'id': pt['id'], 'text': pt['title'],
'child': pt.get('child', False)}
for pt in vault.get_project_types()],
parameters.get_default('project_type'))
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&'
'module=glance')
companies = json.loads(response.data)['companies']
companies = json.loads(response.data)['data']
self.assertEqual([{'id': 'ibm', 'text': 'IBM'},
{'id': 'nec', 'text': 'NEC'},
{'id': 'ntt', 'text': 'NTT'}], companies)
response = self.app.get('/api/1.0/companies?metric=marks&'
'module=glance')
companies = json.loads(response.data)['companies']
companies = json.loads(response.data)['data']
self.assertEqual([{'id': 'ibm', 'text': 'IBM'},
{'id': 'nec', 'text': 'NEC'}], companies)

View File

@ -45,7 +45,7 @@ class TestAPIModules(test_api.TestAPI):
response = self.app.get('/api/1.0/modules?'
'project_type=all&metric=commits')
modules = json.loads(response.data)['modules']
modules = json.loads(response.data)['data']
self.assertEqual(
[{'id': 'glance', 'text': 'glance', '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&'
'project_type=integrated&metric=commits')
modules = json.loads(response.data)['modules']
modules = json.loads(response.data)['data']
self.assertEqual(
[{'id': 'glance', 'text': 'glance', '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}]},
test_api.make_records(record_type=['commit'])):
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.assertIn({'id': 'all', 'text': 'All'}, 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'])):
response = self.app.get('/api/1.0/users?'
'module=nova&metric=commits')
users = json.loads(response.data)['users']
users = json.loads(response.data)['data']
self.assertEqual(2, len(users))
self.assertIn({'id': 'john_doe', 'text': 'John Doe'}, users)
self.assertIn({'id': 'bill_smith', 'text': 'Bill Smith'}, users)