package org.sonar.wsclient.gwt.unmarshallers;
-import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONValue;
import org.sonar.gwt.JsonUtils;
import org.sonar.wsclient.services.TimeMachineData;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
-public class TimeMachineUnmarshaller implements Unmarshaller<TimeMachineData> {
+public class TimeMachineUnmarshaller extends AbstractUnmarshaller<TimeMachineData> {
- public TimeMachineData toModel(JavaScriptObject json) {
- JSONObject map = new JSONObject(json);
- Map<Date, List<String>> data = new HashMap<Date, List<String>>();
- for (String dateTimeStr : map.keySet()) {
- JSONArray array = map.get(dateTimeStr).isArray();
- List<String> measures = new ArrayList<String>();
- for (int i = 0; i < JsonUtils.getArraySize(array); i++) {
- // We can't use JsonUtils.getArray here, because it returns JSONObject instead of JSONValue
- JSONValue elem = array.get(i);
- measures.add(JsonUtils.getAsString(elem));
- }
- data.put(JsonUtils.parseDateTime(dateTimeStr), measures);
+ protected TimeMachineData parse(JSONObject json) {
+ String dateTimeStr = (String) json.keySet().iterator().next();
+ JSONArray array = json.get(dateTimeStr).isArray();
+ List<String> measures = new ArrayList<String>();
+ for (int i = 0; i < JsonUtils.getArraySize(array); i++) {
+ // We can't use JsonUtils.getArray here, because it returns JSONObject instead of JSONValue
+ JSONValue elem = array.get(i);
+ measures.add(JsonUtils.getAsString(elem));
}
- return new TimeMachineData().setData(data);
- }
-
- public List<TimeMachineData> toModels(JavaScriptObject json) {
- return Arrays.asList(toModel(json));
+ return new TimeMachineData()
+ .setDate(JsonUtils.parseDateTime(dateTimeStr))
+ .setValues(measures);
}
}
#
class Api::TimemachineController < Api::ApiController
MAX_IN_ELEMENTS=990
+
#
# GET /api/timemachine
#
+ # Required parameters :
+ # - resource (id or key)
+ # - metrics
+ #
+ # Optional parameters :
+ # - fromDateTime
+ # - toDateTime
+ # - model
+ # - characteristics
+ #
def index
begin
resource_id = params[:resource]
- metric_keys = params[:metrics].split(',')
- metrics = Metric.by_keys(metric_keys)
- first_date = parse_datetime(params[:fromDateTime])
- last_date = parse_datetime(params[:toDateTime])
@resource=Project.by_key(resource_id)
if @resource.nil?
raise ApiException.new 404, "Resource not found: #{resource_id}"
end
- snapshots = Snapshot.find(:all,
- :conditions => ['created_at>=? AND created_at<=? AND project_id=? AND status=?',
- first_date, last_date, @resource.id, Snapshot::STATUS_PROCESSED],
- :order => 'created_at')
+ # ---------- PARAMETERS
+ measures_conditions = []
+ measures_values = {}
+ snapshots_conditions = []
+ snapshots_values = {}
+
+ if params[:fromDateTime]
+ from = parse_datetime(params[:fromDateTime])
+ end
+ if from
+ snapshots_conditions << 'created_at>=:from'
+ snapshots_values[:from] = from
+ end
+
+ if params[:toDateTime]
+ to = parse_datetime(params[:toDateTime])
+ end
+ if to
+ snapshots_conditions << 'created_at<=:to'
+ snapshots_values[:to] = to
+ end
+
+ snapshots_conditions << 'project_id=:rid AND status=:status'
+ snapshots_values[:rid] = @resource.id
+ snapshots_values[:status] = Snapshot::STATUS_PROCESSED
+ snapshots = Snapshot.find(:all,
+ :conditions => [ snapshots_conditions.join(' AND '), snapshots_values],
+ :order => 'created_at')
# Oracle limitation : no more than 1000 elements in IN clause
if snapshots.length > MAX_IN_ELEMENTS
size=snapshots.size
snapshots=snapshots[size-MAX_IN_ELEMENTS .. size-1]
end
- measures = find_measures(metrics, snapshots)
-
- result = {}
- if !measures.empty?
- measures_by_sid = {}
- measures.each do |measure|
- measures_by_sid[measure.snapshot_id]||=[]
- measures_by_sid[measure.snapshot_id]<<measure
- end
-
- snapshots.each do |snapshot|
- snapshot_measures = measures_by_sid[snapshot.id] || []
- values_by_key = {}
- snapshot_measures.each do |measure|
- values_by_key[measure.metric.name] = measure.value.to_f if measure.value
- end
-
- values = []
- metric_keys.each do |metric|
- values<<values_by_key[metric]
- end
- result[format_datetime(snapshot.created_at)] = values
- end
+ measures_conditions << 'project_measures.rules_category_id IS NULL AND project_measures.rule_id IS NULL AND project_measures.rule_priority IS NULL'
+
+ measures_conditions << 'project_measures.snapshot_id IN (:snapshots)'
+ measures_values[:snapshots] = snapshots.map{|s| s.id}
+
+ metric_keys = params[:metrics].split(',')
+ metrics = Metric.by_keys(metric_keys)
+ measures_conditions << 'project_measures.metric_id IN (:metrics)'
+ measures_values[:metrics] = metrics.select{|m| m.id}
+
+ add_characteristic_filters(measures_conditions, measures_values)
+
+ measures = ProjectMeasure.find(:all,
+ :select => 'project_measures.id,project_measures.value,project_measures.metric_id,project_measures.snapshot_id',
+ :conditions => [ measures_conditions.join(' AND '), measures_values])
+
+ # ---------- PREPARE RESPONSE
+ measures_by_sid = {}
+ measures.each do |measure|
+ measures_by_sid[measure.snapshot_id]||=[]
+ measures_by_sid[measure.snapshot_id]<<measure
end
# ---------- FORMAT RESPONSE
+ objects = { :snapshots => snapshots, :measures_by_sid => measures_by_sid, :metric_keys => metric_keys }
respond_to do |format|
- format.json { render :json => jsonp(result) }
- format.xml { render :xml => xml_not_supported }
+ format.json { render :json => jsonp(to_json(objects)) }
+ format.xml { render :xml => to_xml(objects) }
format.text { render :text => text_not_supported }
end
rescue ApiException => e
private
- def find_measures(metrics, snapshots)
- ProjectMeasure.find(:all,
- :select => 'project_measures.id,project_measures.value,project_measures.metric_id,project_measures.snapshot_id',
- :conditions => ['rules_category_id IS NULL AND rule_id IS NULL AND rule_priority IS NULL AND metric_id IN (?) AND snapshot_id IN (?)',
- metrics.select{|m| m.id}, snapshots.map{|s| s.id}])
+ def to_json(objects)
+ snapshots = objects[:snapshots]
+ measures_by_sid = objects[:measures_by_sid]
+ metric_keys = objects[:metric_keys]
+
+ result = []
+ snapshots.each do |snapshot|
+ result << snapshot_to_json(snapshot, measures_by_sid[snapshot.id] || [], metric_keys)
+ end
+ result
+ end
+
+ def snapshot_to_json(snapshot, measures, metric_keys)
+ values_by_key = {}
+ measures.each do |measure|
+ values_by_key[measure.metric.name] = measure.value.to_f if measure.value
+ end
+ values = []
+ metric_keys.each do |metric|
+ values << values_by_key[metric]
+ end
+ json = { format_datetime(snapshot.created_at) => values }
+ json
+ end
+
+ def to_xml(objects)
+ snapshots = objects[:snapshots]
+ measures_by_sid = objects[:measures_by_sid]
+ metric_keys = objects[:metric_keys]
+
+ xml = Builder::XmlMarkup.new(:indent => 0)
+ xml.instruct!
+
+ xml.snapshots do
+ snapshots.each do |snapshot|
+ snapshot_to_xml(xml, snapshot, measures_by_sid[snapshot.id])
+ end
+ end
+ end
+
+ def snapshot_to_xml(xml, snapshot, measures)
+ xml.snapshot do
+ xml.date(format_datetime(snapshot.created_at))
+ # TODO measures
+ end
+ end
+
+ def add_characteristic_filters(measures_conditions, measures_values)
+ @characteristics=[]
+ @characteristic_by_id={}
+ if params[:model].present? && params[:characteristics].present?
+ @characteristics=Characteristic.find(:all,
+ :select => 'characteristics.id,characteristics.kee,characteristics.name',
+ :joins => :quality_model,
+ :conditions => ['quality_models.name=? AND characteristics.kee IN (?)', params[:model], params[:characteristics].split(',')])
+ if @characteristics.empty?
+ measures_conditions<<'project_measures.characteristic_id=-1'
+ else
+ @characteristics.each { |c| @characteristic_by_id[c.id]=c }
+ measures_conditions<<'project_measures.characteristic_id IN (:characteristics)'
+ measures_values[:characteristics]=@characteristic_by_id.keys
+ end
+ else
+ measures_conditions<<'project_measures.characteristic_id IS NULL'
+ end
end
end
package org.sonar.wsclient.services;
+import java.util.ArrayList;
import java.util.Date;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
public class TimeMachineData extends Model {
+ private Date date;
/**
* We use strings here in order to support measures with string value.
*/
- private Map<Date, List<String>> data = new HashMap<Date, List<String>>();
+ private List<String> values = new ArrayList<String>();
- public Map<Date, List<String>> getData() {
- return data;
+ public Date getDate() {
+ return date;
}
- public TimeMachineData setData(Map<Date, List<String>> data) {
- this.data = data;
+ public TimeMachineData setDate(Date date) {
+ this.date = date;
return this;
}
- public Double getValueAsDouble(Date date, int index) {
- if (data.containsKey(date)) {
- String valueStr = data.get(date).get(index);
- try {
- return valueStr == null ? null : Double.valueOf(valueStr);
- } catch (NumberFormatException e) {
- return null;
- }
+ public List<String> getValues() {
+ return values;
+ }
+
+ public TimeMachineData setValues(List<String> values) {
+ this.values = values;
+ return this;
+ }
+
+ public Double getValueAsDouble(int index) {
+ String valueStr = values.get(index);
+ try {
+ return valueStr == null ? null : Double.valueOf(valueStr);
+ } catch (NumberFormatException e) {
+ return null;
}
- return null;
}
}
private Date from;
private Date to;
+ private String model;
+ private String[] characteristicKeys;
+
public TimeMachineQuery(String resourceKeyOrId) {
this.resourceKeyOrId = resourceKeyOrId;
}
return this;
}
+ public TimeMachineQuery setCharacteristicKeys(String model, String... keys) {
+ this.model = model;
+ this.characteristicKeys = keys;
+ return this;
+ }
+
@Override
public String getUrl() {
StringBuilder url = new StringBuilder(BASE_URL);
appendUrlParameter(url, "metrics", metrics);
appendUrlParameter(url, "fromDateTime", from, true);
appendUrlParameter(url, "toDateTime", to, true);
+ appendUrlParameter(url, "model", model);
+ appendUrlParameter(url, "characteristics", characteristicKeys);
return url.toString();
}
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
-import org.json.simple.JSONValue;
import org.sonar.wsclient.services.TimeMachineData;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
-public class TimeMachineUnmarshaller implements Unmarshaller<TimeMachineData> {
+public class TimeMachineUnmarshaller extends AbstractUnmarshaller<TimeMachineData> {
- public TimeMachineData toModel(String json) {
- JSONObject map = (JSONObject) JSONValue.parse(json);
- Map<Date, List<String>> data = new HashMap<Date, List<String>>();
- for (Object key : map.keySet()) {
- JSONArray array = (JSONArray) map.get(key);
- List<String> measures = new ArrayList<String>();
- for (int i = 0; i < array.size(); i++) {
- Object elem = array.get(i);
- String value = elem == null ? null : elem.toString();
- measures.add(value);
- }
- data.put(JsonUtils.parseDateTime((String) key), measures);
+ protected TimeMachineData parse(JSONObject json) {
+ String dateTimeStr = (String) json.keySet().iterator().next();
+ JSONArray array = (JSONArray) json.get(dateTimeStr);
+ List<String> measures = new ArrayList<String>();
+ for (int i = 0; i < array.size(); i++) {
+ Object elem = array.get(i);
+ String value = elem == null ? null : elem.toString();
+ measures.add(value);
}
- return new TimeMachineData().setData(data);
- }
-
- public List<TimeMachineData> toModels(String json) {
- throw new UnsupportedOperationException();
+ return new TimeMachineData()
+ .setDate(JsonUtils.parseDateTime(dateTimeStr))
+ .setValues(measures);
}
}
import org.junit.Test;
-import java.util.*;
+import java.util.Arrays;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
@Test
public void valueAsDouble() {
- Map<Date, List<String>> map = new HashMap<Date, List<String>>();
- Date date = new Date();
- map.put(date, Arrays.asList(null, "20.3", "hello"));
- TimeMachineData data = new TimeMachineData().setData(map);
+ TimeMachineData data = new TimeMachineData().setValues(Arrays.asList(null, "20.3", "hello"));
- assertThat(data.getValueAsDouble(date, 0), nullValue());
- assertThat(data.getValueAsDouble(date, 1), is(20.3));
- assertThat(data.getValueAsDouble(date, 2), nullValue());
+ assertThat(data.getValueAsDouble(0), nullValue());
+ assertThat(data.getValueAsDouble(1), is(20.3));
+ assertThat(data.getValueAsDouble(2), nullValue());
}
}
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
-import java.util.Map;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
public void toModel() throws Exception {
TimeMachineData data = new TimeMachineUnmarshaller().toModel(WSTestUtils.loadFile("/timemachine/timemachine.json"));
- Map<Date, List<String>> map = data.getData();
- assertThat(map.size(), is(1));
- Date date = map.keySet().iterator().next();
+ Date date = data.getDate();
final Date expectedDate = new SimpleDateFormat("yyyy-MM-dd'T'kk:mm:ssZZZZ").parse("2010-12-04T15:59:23+0000");
assertThat(date, is(expectedDate));
- List<String> values = map.values().iterator().next();
+ List<String> values = data.getValues();
assertThat(values.size(), is(3));
assertThat(values.get(0), is("20.0"));
assertThat(values.get(1), nullValue());
@Test
public void many() throws Exception {
- TimeMachineData data = new TimeMachineUnmarshaller().toModel(WSTestUtils.loadFile("/timemachine/many.json"));
+ List<TimeMachineData> data = new TimeMachineUnmarshaller().toModels(WSTestUtils.loadFile("/timemachine/many.json"));
- Map<Date, List<String>> map = data.getData();
- assertThat(map.size(), is(3));
+ assertThat(data.size(), is(3));
}
}
-{
- "2010-10-10T00:00:00+0000": [25.0, null, 14.6],
- "2010-11-15T00:00:00+0000": [23.0, null, 10.3],
- "2010-12-04T00:00:00+0000": [20.0, null, 12.8]
-}
+[
+ {"2010-10-10T00:00:00+0000": [25.0, null, 14.6]},
+ {"2010-11-15T00:00:00+0000": [23.0, null, 10.3]},
+ {"2010-12-04T00:00:00+0000": [20.0, null, 12.8]}
+]
-{
- "2010-12-04T15:59:23+0000": [20.0, null, 12.8]
-}
+[
+ {"2010-12-04T15:59:23+0000": [20.0, null, 12.8]}
+]