More work on the extend data dialog. The suggested properties are now populated by the gridworks-helper acre app. Constraints can be specified per column, in the free form of a MQL query. It's a temporary solution.
git-svn-id: http://google-refine.googlecode.com/svn/trunk@540 7d457c2a-affb-35e4-300a-418c747d4874
This commit is contained in:
parent
fd062bdc2b
commit
007da57c1e
@ -60,7 +60,8 @@ public class FreebaseDataExtensionJob {
|
||||
StringWriter writer = new StringWriter();
|
||||
formulateQuery(guids, extension, writer);
|
||||
|
||||
InputStream is = doMqlRead(writer.toString());
|
||||
String query = writer.toString();
|
||||
InputStream is = doMqlRead(query);
|
||||
try {
|
||||
String s = ParsingUtilities.inputStreamToString(is);
|
||||
JSONObject o = ParsingUtilities.evaluateJsonStringToObject(s);
|
||||
@ -160,16 +161,21 @@ public class FreebaseDataExtensionJob {
|
||||
|
||||
int l = a.length();
|
||||
for (int r = 0; r < l; r++) {
|
||||
JSONObject o = a.isNull(r) ? null : a.getJSONObject(r);
|
||||
Object v = a.isNull(r) ? null : a.get(r);
|
||||
JSONObject o = v != null && v instanceof JSONObject ? (JSONObject) v : null;
|
||||
|
||||
int startColumnIndex2 = startColumnIndex;
|
||||
int startRowIndex2 = startRowIndex;
|
||||
|
||||
if (isOwnColumn) {
|
||||
storeCell(rows, startRowIndex2++, startColumnIndex2++, o);
|
||||
if (o != null) {
|
||||
storeCell(rows, startRowIndex2++, startColumnIndex2++, o);
|
||||
} else {
|
||||
storeCell(rows, startRowIndex2++, startColumnIndex2++, v);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSubProperties) {
|
||||
if (hasSubProperties && o != null) {
|
||||
int[] rowcol = collectResult(
|
||||
rows,
|
||||
extNode.getJSONArray("properties"),
|
||||
@ -278,8 +284,41 @@ public class FreebaseDataExtensionJob {
|
||||
{
|
||||
if (!expectedTypeID.startsWith("/type/")) { // not literal
|
||||
writer.object();
|
||||
writer.key("limit"); writer.value(10);
|
||||
writer.key("optional"); writer.value(true);
|
||||
|
||||
boolean hasLimit = false;
|
||||
if (node.has("constraints") && !node.isNull("constraints")) {
|
||||
JSONObject constraints = node.getJSONObject("constraints");
|
||||
|
||||
String[] names = JSONObject.getNames(constraints);
|
||||
for (String name : names) {
|
||||
Object value = constraints.get(name);
|
||||
if (name.equals("limit")) {
|
||||
hasLimit = true;
|
||||
}
|
||||
|
||||
if (!name.contains(":") &&
|
||||
!name.equals("limit") &&
|
||||
!name.equals("optional") &&
|
||||
!name.equals("count") &&
|
||||
!name.equals("estimate-count") &&
|
||||
!name.equals("sort") &&
|
||||
!name.equals("return")) {
|
||||
|
||||
if (name.startsWith("!")) {
|
||||
name = "!c:" + name.substring(1);
|
||||
} else {
|
||||
name = "c:" + name;
|
||||
}
|
||||
}
|
||||
writer.key(name);
|
||||
writer.value(value);
|
||||
}
|
||||
}
|
||||
if (!hasLimit) {
|
||||
writer.key("limit"); writer.value(10);
|
||||
}
|
||||
|
||||
{
|
||||
boolean hasSubProperties = (node.has("properties") && !node.isNull("properties"));
|
||||
|
||||
|
@ -17,10 +17,11 @@ function ExtendDataPreviewDialog(column, columnIndex, rowIndices, onDone) {
|
||||
'<tr>' +
|
||||
'<td width="300" height="1">Add Property</td>' +
|
||||
'<td height="1">Preview</td>' +
|
||||
'<td height="1" width="1%"><button bind="resetButton">Reset</button></td>' +
|
||||
'</tr>' +
|
||||
'<tr>' +
|
||||
'<td style="vertical-align: top;" height="1"><div class="input-container"><input bind="addPropertyInput" /></div></td>' +
|
||||
'<td style="vertical-align: top;" rowspan="4"><div class="preview-container" bind="previewContainer"></div></td>' +
|
||||
'<td style="vertical-align: top;" rowspan="3" colspan="2"><div class="preview-container" bind="previewContainer"></div></td>' +
|
||||
'</tr>' +
|
||||
'<tr>' +
|
||||
'<td height="1">Suggested Properties</td>' +
|
||||
@ -33,6 +34,11 @@ function ExtendDataPreviewDialog(column, columnIndex, rowIndices, onDone) {
|
||||
|
||||
this._elmts = DOM.bind(html);
|
||||
|
||||
this._elmts.resetButton.click(function() {
|
||||
self._extension.properties = [];
|
||||
self._update();
|
||||
});
|
||||
|
||||
$('<button></button>').html(" OK ").click(function() {
|
||||
if (self._extension.properties.length === 0) {
|
||||
alert("Please add some properties first.");
|
||||
@ -55,146 +61,46 @@ function ExtendDataPreviewDialog(column, columnIndex, rowIndices, onDone) {
|
||||
}
|
||||
|
||||
ExtendDataPreviewDialog.getAllProperties = function(typeID, onDone) {
|
||||
var query = {
|
||||
"id" : typeID,
|
||||
"type" : "/type/type",
|
||||
"/freebase/type_hints/included_types" : [{
|
||||
"optional" : true,
|
||||
"properties" : [{
|
||||
"id" : null,
|
||||
"name" : null,
|
||||
"/type/property/expected_type" : {
|
||||
"id" : null,
|
||||
"name" : null,
|
||||
"/freebase/type_hints/mediator" : []
|
||||
},
|
||||
"sort" : "name"
|
||||
}]
|
||||
}],
|
||||
"properties" : [{
|
||||
"id" : null,
|
||||
"name" : null,
|
||||
"/type/property/expected_type" : {
|
||||
"id" : null,
|
||||
"name" : null,
|
||||
"/freebase/type_hints/mediator" : []
|
||||
},
|
||||
"sort" : "name"
|
||||
}]
|
||||
};
|
||||
|
||||
var allProperties = [];
|
||||
var cvtProperties = [];
|
||||
var processProperty = function(property, parent) {
|
||||
var expectedType = property["/type/property/expected_type"];
|
||||
if (expectedType["/freebase/type_hints/mediator"].length > 0 && expectedType["/freebase/type_hints/mediator"][0]) {
|
||||
cvtProperties.push(property);
|
||||
} else {
|
||||
allProperties.push({
|
||||
id : property.id,
|
||||
name : property.name,
|
||||
expected : {
|
||||
id : expectedType.id,
|
||||
name : expectedType.name
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
var processProperties = function(properties, parent) {
|
||||
$.each(properties, function() { processProperty(this, parent); });
|
||||
};
|
||||
var done = false;
|
||||
|
||||
$.getJSON(
|
||||
"http://api.freebase.com/api/service/mqlread?query=" + escape(JSON.stringify({ query : query })) + "&callback=?",
|
||||
"http://gridworks-helper.freebaseapps.com/get_properties_of_type?type=" + typeID + "&callback=?",
|
||||
null,
|
||||
function(o) {
|
||||
if (!("result" in o)) {
|
||||
onDone([]);
|
||||
return;
|
||||
}
|
||||
function(data) {
|
||||
if (done) return;
|
||||
done = true;
|
||||
|
||||
processProperties(o.result.properties);
|
||||
$.each(o.result["/freebase/type_hints/included_types"], function() {
|
||||
processProperties(this.properties, null);
|
||||
});
|
||||
|
||||
if (!cvtProperties.length) {
|
||||
onDone(allProperties);
|
||||
return;
|
||||
}
|
||||
|
||||
var expectedTypeToProperties = [];
|
||||
var expectedTypeIDs = [];
|
||||
$.each(cvtProperties, function() {
|
||||
var expected = this["/type/property/expected_type"];
|
||||
if (expected.id in expectedTypeToProperties) {
|
||||
expectedTypeToProperties[expected.id].push(this);
|
||||
var allProperties = [];
|
||||
for (var i = 0; i < data.properties.length; i++) {
|
||||
var property = data.properties[i];
|
||||
var property2 = {
|
||||
id: property.id,
|
||||
name: property.name
|
||||
};
|
||||
if ("id2" in property) {
|
||||
property2.expected = property.schema2;
|
||||
property2.properties = [{
|
||||
id: property.id2,
|
||||
name: property.name2,
|
||||
expected: property.expects
|
||||
}];
|
||||
} else {
|
||||
expectedTypeToProperties[expected.id] = [ this ];
|
||||
expectedTypeIDs.push(expected.id);
|
||||
property2.expected = property.expects;
|
||||
}
|
||||
});
|
||||
allProperties.push(property2);
|
||||
}
|
||||
allProperties.sort(function(a, b) { return a.name.localeCompare(b.name); });
|
||||
|
||||
var query2 = [{
|
||||
"id|=" : expectedTypeIDs,
|
||||
"id" : null,
|
||||
"type" : "/type/type",
|
||||
"properties" : [{
|
||||
"id" : null,
|
||||
"name" : null,
|
||||
"/type/property/expected_type" : {
|
||||
"id" : null,
|
||||
"name" : null,
|
||||
"/freebase/type_hints/mediator" : []
|
||||
},
|
||||
"sort" : "name"
|
||||
}]
|
||||
}];
|
||||
|
||||
$.getJSON(
|
||||
"http://api.freebase.com/api/service/mqlread?query=" + escape(JSON.stringify({ query : query2 })) + "&callback=?",
|
||||
null,
|
||||
function(o2) {
|
||||
if ("result" in o2) {
|
||||
var processCVTProperty = function(parentProperty, properties) {
|
||||
var parentExpected = parentProperty["/type/property/expected_type"];
|
||||
|
||||
$.each(properties, function() {
|
||||
var expected = this["/type/property/expected_type"];
|
||||
allProperties.push({
|
||||
id : parentProperty.id,
|
||||
name : parentProperty.name,
|
||||
expected : {
|
||||
id : parentExpected.id,
|
||||
name : parentExpected.name
|
||||
},
|
||||
properties: [{
|
||||
id : this.id,
|
||||
name : this.name,
|
||||
expected : {
|
||||
id : expected.id,
|
||||
name : expected.name
|
||||
}
|
||||
}]
|
||||
});
|
||||
});
|
||||
};
|
||||
var processCVTProperties = function(parentProperties, properties) {
|
||||
$.each(parentProperties, function() { processCVTProperty(this, properties); });
|
||||
};
|
||||
|
||||
$.each(o2.result, function() {
|
||||
processCVTProperties(expectedTypeToProperties[this.id], this.properties);
|
||||
});
|
||||
}
|
||||
|
||||
onDone(allProperties);
|
||||
},
|
||||
"jsonp"
|
||||
);
|
||||
},
|
||||
"jsonp"
|
||||
);
|
||||
onDone(allProperties);
|
||||
}
|
||||
);
|
||||
|
||||
window.setTimeout(function() {
|
||||
if (done) return;
|
||||
|
||||
done = true;
|
||||
onDone([]);
|
||||
}, 7000); // time to give up?
|
||||
};
|
||||
|
||||
ExtendDataPreviewDialog.prototype._show = function(properties) {
|
||||
@ -203,6 +109,7 @@ ExtendDataPreviewDialog.prototype._show = function(properties) {
|
||||
var n = this._elmts.suggestedPropertyContainer.offset().top +
|
||||
this._elmts.suggestedPropertyContainer.outerHeight(true) -
|
||||
this._elmts.addPropertyInput.offset().top;
|
||||
|
||||
this._elmts.previewContainer.height(Math.floor(n));
|
||||
|
||||
var self = this;
|
||||
@ -211,9 +118,13 @@ ExtendDataPreviewDialog.prototype._show = function(properties) {
|
||||
var label = ("properties" in property) ? (property.name + " » " + property.properties[0].name) : property.name;
|
||||
var div = $('<div>').addClass("suggested-property").appendTo(container);
|
||||
|
||||
$('<a>').attr("href", "javascript:{}").html(label).appendTo(div).click(function() {
|
||||
self._addProperty(property);
|
||||
});
|
||||
$('<a>')
|
||||
.attr("href", "javascript:{}")
|
||||
.html(label)
|
||||
.appendTo(div)
|
||||
.click(function() {
|
||||
self._addProperty(property);
|
||||
});
|
||||
};
|
||||
for (var i = 0; i < properties.length; i++) {
|
||||
renderSuggestedProperty(properties[i]);
|
||||
@ -294,6 +205,64 @@ ExtendDataPreviewDialog.prototype._addProperty = function(p) {
|
||||
this._update();
|
||||
};
|
||||
|
||||
ExtendDataPreviewDialog.prototype._renderPreview = function(data) {
|
||||
var self = this;
|
||||
var container = this._elmts.previewContainer.empty();
|
||||
if (data.code == "error") {
|
||||
container.text("Error.");
|
||||
return;
|
||||
}
|
||||
|
||||
var table = $('<table>')[0];
|
||||
var trHead = table.insertRow(table.rows.length);
|
||||
$('<th>').appendTo(trHead).text(this._column.name);
|
||||
|
||||
var renderColumnHeader = function(column) {
|
||||
var th = $('<th>').appendTo(trHead);
|
||||
|
||||
$('<span>').html(column.names.join(" » ")).appendTo(th);
|
||||
$('<br>').appendTo(th);
|
||||
|
||||
$('<a href="javascript:{}"></a>')
|
||||
.text("remove")
|
||||
.addClass("action")
|
||||
.attr("title", "Remove this column")
|
||||
.click(function() {
|
||||
self._removeProperty(column.path);
|
||||
}).appendTo(th);
|
||||
|
||||
$('<a href="javascript:{}"></a>')
|
||||
.text("constrain")
|
||||
.addClass("action")
|
||||
.attr("title", "Add constraints to this column")
|
||||
.click(function() {
|
||||
self._constrainProperty(column.path);
|
||||
}).appendTo(th);
|
||||
};
|
||||
for (var c = 0; c < data.columns.length; c++) {
|
||||
renderColumnHeader(data.columns[c]);
|
||||
}
|
||||
|
||||
for (var r = 0; r < data.rows.length; r++) {
|
||||
var tr = table.insertRow(table.rows.length);
|
||||
var row = data.rows[r];
|
||||
|
||||
for (var c = 0; c < row.length; c++) {
|
||||
var td = tr.insertCell(tr.cells.length);
|
||||
var cell = row[c];
|
||||
if (cell !== null) {
|
||||
if ($.isPlainObject(cell)) {
|
||||
$('<a>').attr("href", "http://www.freebase.com/view" + cell.id).text(cell.name).appendTo(td);
|
||||
} else {
|
||||
$('<span>').text(cell).appendTo(td);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
container.append(table);
|
||||
};
|
||||
|
||||
ExtendDataPreviewDialog.prototype._removeProperty = function(path) {
|
||||
var removeFromList = function(path, index, properties) {
|
||||
var id = path[index];
|
||||
@ -323,49 +292,95 @@ ExtendDataPreviewDialog.prototype._removeProperty = function(path) {
|
||||
this._update();
|
||||
};
|
||||
|
||||
ExtendDataPreviewDialog.prototype._renderPreview = function(data) {
|
||||
var self = this;
|
||||
var container = this._elmts.previewContainer.empty();
|
||||
if (data.code == "error") {
|
||||
container.text("Error.");
|
||||
return;
|
||||
}
|
||||
|
||||
var table = $('<table>')[0];
|
||||
var trHead = table.insertRow(table.rows.length);
|
||||
$('<th>').appendTo(trHead).text(this._column.name);
|
||||
|
||||
var renderColumnHeader = function(column) {
|
||||
var th = $('<th>').appendTo(trHead);
|
||||
ExtendDataPreviewDialog.prototype._findProperty = function(path) {
|
||||
var find = function(path, index, properties) {
|
||||
var id = path[index];
|
||||
|
||||
$('<span>').html(column.names.join(" » ")).appendTo(th);
|
||||
$('<img>')
|
||||
.attr("src", "images/close.png")
|
||||
.attr("title", "Remove this column")
|
||||
.click(function() {
|
||||
self._removeProperty(column.path);
|
||||
}).appendTo(th);
|
||||
};
|
||||
for (var c = 0; c < data.columns.length; c++) {
|
||||
renderColumnHeader(data.columns[c]);
|
||||
}
|
||||
|
||||
for (var r = 0; r < data.rows.length; r++) {
|
||||
var tr = table.insertRow(table.rows.length);
|
||||
var row = data.rows[r];
|
||||
|
||||
for (var c = 0; c < row.length; c++) {
|
||||
var td = tr.insertCell(tr.cells.length);
|
||||
var cell = row[c];
|
||||
if (cell !== null) {
|
||||
if ($.isPlainObject(cell)) {
|
||||
$('<a>').attr("href", "http://www.freebase.com/view" + cell.id).text(cell.name).appendTo(td);
|
||||
} else {
|
||||
$('<span>').text(cell).appendTo(td);
|
||||
for (var i = properties.length - 1; i >= 0; i--) {
|
||||
var property = properties[i];
|
||||
if (property.id == id) {
|
||||
if (index === path.length - 1) {
|
||||
return property;
|
||||
} else if ("properties" in property && property.properties.length > 0) {
|
||||
return find(path, index + 1, property.properties);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return find(path, 0, this._extension.properties);
|
||||
};
|
||||
|
||||
ExtendDataPreviewDialog.prototype._constrainProperty = function(path) {
|
||||
var self = this;
|
||||
var property = this._findProperty(path);
|
||||
|
||||
var frame = DialogSystem.createDialog();
|
||||
frame.width("500px");
|
||||
|
||||
var header = $('<div></div>').addClass("dialog-header").text("Constrain " + path.join(" > ")).appendTo(frame);
|
||||
var body = $('<div></div>').addClass("dialog-body").appendTo(frame);
|
||||
var footer = $('<div></div>').addClass("dialog-footer").appendTo(frame);
|
||||
|
||||
body.html(
|
||||
'<div class="grid-layout layout-normal layout-full"><table>' +
|
||||
'<tr><td>' +
|
||||
'Enter MQL query constraints as JSON' +
|
||||
'</td></tr>' +
|
||||
'<tr><td>' +
|
||||
'<textarea style="width: 100%; height: 300px; font-family: monospace;" bind="textarea"></textarea>' +
|
||||
'</td></tr>' +
|
||||
'</table></div>'
|
||||
);
|
||||
var bodyElmts = DOM.bind(body);
|
||||
|
||||
if ("constraints" in property) {
|
||||
bodyElmts.textarea[0].value = JSON.stringify(property.constraints, null, 2);
|
||||
} else {
|
||||
bodyElmts.textarea[0].value = JSON.stringify({ "limit" : 10 }, null, 2);
|
||||
}
|
||||
|
||||
container.append(table);
|
||||
};
|
||||
footer.html(
|
||||
'<button bind="okButton"> OK </button>' +
|
||||
'<button bind="cancelButton">Cancel</button>'
|
||||
);
|
||||
var footerElmts = DOM.bind(footer);
|
||||
|
||||
var level = DialogSystem.showDialog(frame);
|
||||
var dismiss = function() {
|
||||
DialogSystem.dismissUntil(level - 1);
|
||||
};
|
||||
|
||||
footerElmts.cancelButton.click(dismiss);
|
||||
footerElmts.okButton.click(function() {
|
||||
try {
|
||||
var o = JSON.parse(bodyElmts.textarea[0].value);
|
||||
if (o === undefined) {
|
||||
alert("Please ensure that the JSON you enter is valid.");
|
||||
return;
|
||||
}
|
||||
|
||||
if ($.isArray(o) && o.length == 1) {
|
||||
o = o[0];
|
||||
}
|
||||
if (!$.isPlainObject(o)) {
|
||||
alert("The JSON you enter must be an object, that is, it is of this form { ... }.")
|
||||
return;
|
||||
}
|
||||
|
||||
property.constraints = o;
|
||||
|
||||
dismiss();
|
||||
|
||||
self._update();
|
||||
} catch (e) {
|
||||
//console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
bodyElmts.textarea.focus();
|
||||
};
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
.extend-data-preview-dialog .preview-container td, .extend-data-preview-dialog .preview-container th {
|
||||
padding: 3px 5px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-right: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.extend-data-preview-dialog .preview-container th img {
|
||||
|
Loading…
Reference in New Issue
Block a user