diff --git a/src/main/java/com/metaweb/gridworks/GridworksServlet.java b/src/main/java/com/metaweb/gridworks/GridworksServlet.java index 1f4db1962..e40edf63e 100644 --- a/src/main/java/com/metaweb/gridworks/GridworksServlet.java +++ b/src/main/java/com/metaweb/gridworks/GridworksServlet.java @@ -33,6 +33,7 @@ import com.metaweb.gridworks.commands.info.GetRowsCommand; import com.metaweb.gridworks.commands.recon.ApproveNewReconcileCommand; import com.metaweb.gridworks.commands.recon.ApproveReconcileCommand; import com.metaweb.gridworks.commands.recon.DiscardReconcileCommand; +import com.metaweb.gridworks.commands.recon.JudgeOneCellCommand; import com.metaweb.gridworks.commands.recon.ReconcileCommand; import com.metaweb.gridworks.commands.util.GetExpressionLanguageInfoCommand; import com.metaweb.gridworks.commands.util.PreviewExpressionCommand; @@ -67,6 +68,7 @@ public class GridworksServlet extends HttpServlet { _commands.put("approve-reconcile", new ApproveReconcileCommand()); _commands.put("approve-new-reconcile", new ApproveNewReconcileCommand()); _commands.put("discard-reconcile", new DiscardReconcileCommand()); + _commands.put("judge-one-cell", new JudgeOneCellCommand()); _commands.put("preview-expression", new PreviewExpressionCommand()); _commands.put("get-expression-language-info", new GetExpressionLanguageInfoCommand()); diff --git a/src/main/java/com/metaweb/gridworks/commands/recon/JudgeOneCellCommand.java b/src/main/java/com/metaweb/gridworks/commands/recon/JudgeOneCellCommand.java new file mode 100644 index 000000000..7ae07e528 --- /dev/null +++ b/src/main/java/com/metaweb/gridworks/commands/recon/JudgeOneCellCommand.java @@ -0,0 +1,116 @@ +package com.metaweb.gridworks.commands.recon; + +import java.io.IOException; +import java.util.Properties; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.json.JSONWriter; + +import com.metaweb.gridworks.commands.Command; +import com.metaweb.gridworks.history.Change; +import com.metaweb.gridworks.history.HistoryEntry; +import com.metaweb.gridworks.model.Cell; +import com.metaweb.gridworks.model.Column; +import com.metaweb.gridworks.model.Project; +import com.metaweb.gridworks.model.Recon; +import com.metaweb.gridworks.model.ReconCandidate; +import com.metaweb.gridworks.model.changes.CellChange; +import com.metaweb.gridworks.process.Process; +import com.metaweb.gridworks.process.QuickHistoryEntryProcess; + +public class JudgeOneCellCommand extends Command { + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + try { + Project project = getProject(request); + + int rowIndex = Integer.parseInt(request.getParameter("row")); + int cellIndex = Integer.parseInt(request.getParameter("cell")); + Cell cell = project.rows.get(rowIndex).getCell(cellIndex); + if (cell == null || cell.value == null) { + respond(response, "{ \"code\" : \"error\", \"message\" : \"Cell is blank\" }"); + return; + } + + Column column = project.columnModel.getColumnByCellIndex(cellIndex); + if (column == null) { + respond(response, "{ \"code\" : \"error\", \"message\" : \"No such column\" }"); + return; + } + + Cell newCell = new Cell( + cell.value, + cell.recon == null ? new Recon() : cell.recon.dup() + ); + + String cellDescription = + "single cell on row " + (rowIndex + 1) + + ", column " + column.getHeaderLabel() + + ", containing \"" + cell.value + "\""; + + String description = null; + + String judgment = request.getParameter("judgment"); + if ("match".equals(judgment)) { + ReconCandidate match = null; + + if (cell.recon != null) { + String candidateID = request.getParameter("candidate"); + + for (ReconCandidate c : cell.recon.candidates) { + if (candidateID.equals(c.topicID)) { + match = c; + break; + } + } + } + if (match == null) { + respond(response, "{ \"code\" : \"error\", \"message\" : \"No such recon candidate\" }"); + return; + } + + newCell.recon.judgment = Recon.Judgment.Matched; + newCell.recon.match = match; + + description = "Match " + match.topicName + + " (" + match.topicID + ") to " + + cellDescription; + + } else if ("new".equals(judgment)) { + newCell.recon.judgment = Recon.Judgment.New; + description = "Mark to create new topic for " + cellDescription; + } else if ("discard".equals(judgment)) { + newCell.recon.judgment = Recon.Judgment.None; + newCell.recon.match = null; + description = "Discard recon judgment for " + cellDescription; + } else { + respond(response, "{ \"code\" : \"error\", \"message\" : \"bad judgment\" }"); + return; + } + + Change change = new CellChange(rowIndex, cellIndex, cell, newCell); + HistoryEntry historyEntry = new HistoryEntry( + project, description, null, change); + + Process process = new QuickHistoryEntryProcess(project, historyEntry); + + boolean done = project.processManager.queueProcess(process); + if (done) { + JSONWriter writer = new JSONWriter(response.getWriter()); + writer.object(); + writer.key("code"); writer.value("ok"); + writer.key("cell"); newCell.write(writer, new Properties()); + writer.endObject(); + } else { + respond(response, "{ \"code\" : \"pending\" }"); + } + } catch (Exception e) { + respondException(response, e); + } + } +} diff --git a/src/main/java/com/metaweb/gridworks/model/Recon.java b/src/main/java/com/metaweb/gridworks/model/Recon.java index 9b86167df..c5850eaad 100644 --- a/src/main/java/com/metaweb/gridworks/model/Recon.java +++ b/src/main/java/com/metaweb/gridworks/model/Recon.java @@ -18,7 +18,7 @@ public class Recon implements Serializable, HasFields, Jsonizable { static public enum Judgment { None, - Approve, + Matched, New } @@ -41,8 +41,8 @@ public class Recon implements Serializable, HasFields, Jsonizable { return candidates.size() > 0 ? candidates.get(0) : null; } else if ("judgment".equals(name) || "judgement".equals(name)) { return judgmentToString(); - } else if ("approved".equals(name)) { - return judgment == Judgment.Approve; + } else if ("matched".equals(name)) { + return judgment == Judgment.Matched; } else if ("new".equals(name)) { return judgment == Judgment.New; } else if ("match".equals(name)) { @@ -58,8 +58,8 @@ public class Recon implements Serializable, HasFields, Jsonizable { } protected String judgmentToString() { - if (judgment == Judgment.Approve) { - return "approve"; + if (judgment == Judgment.Matched) { + return "matched"; } else if (judgment == Judgment.New) { return "new"; } else { diff --git a/src/main/java/com/metaweb/gridworks/model/changes/CellChange.java b/src/main/java/com/metaweb/gridworks/model/changes/CellChange.java index 3e831a264..75924fa9c 100644 --- a/src/main/java/com/metaweb/gridworks/model/changes/CellChange.java +++ b/src/main/java/com/metaweb/gridworks/model/changes/CellChange.java @@ -1,21 +1,29 @@ package com.metaweb.gridworks.model.changes; -import java.io.Serializable; - +import com.metaweb.gridworks.history.Change; import com.metaweb.gridworks.model.Cell; +import com.metaweb.gridworks.model.Project; -public class CellChange implements Serializable { +public class CellChange implements Change { private static final long serialVersionUID = -2637405780084390883L; final public int row; - final public int column; + final public int cellIndex; final public Cell oldCell; final public Cell newCell; public CellChange(int row, int column, Cell oldCell, Cell newCell) { this.row = row; - this.column = column; + this.cellIndex = column; this.oldCell = oldCell; this.newCell = newCell; } + + public void apply(Project project) { + project.rows.get(row).setCell(cellIndex, newCell); + } + + public void revert(Project project) { + project.rows.get(row).setCell(cellIndex, oldCell); + } } diff --git a/src/main/java/com/metaweb/gridworks/model/changes/MassCellChange.java b/src/main/java/com/metaweb/gridworks/model/changes/MassCellChange.java index 22717815f..8545428ef 100644 --- a/src/main/java/com/metaweb/gridworks/model/changes/MassCellChange.java +++ b/src/main/java/com/metaweb/gridworks/model/changes/MassCellChange.java @@ -26,7 +26,7 @@ public class MassCellChange implements Change { List rows = project.rows; for (CellChange cellChange : _cellChanges) { - rows.get(cellChange.row).cells.set(cellChange.column, cellChange.newCell); + rows.get(cellChange.row).setCell(cellChange.cellIndex, cellChange.newCell); } if (_commonCellIndex >= 0) { @@ -44,7 +44,7 @@ public class MassCellChange implements Change { List rows = project.rows; for (CellChange cellChange : _cellChanges) { - rows.get(cellChange.row).cells.set(cellChange.column, cellChange.oldCell); + rows.get(cellChange.row).setCell(cellChange.cellIndex, cellChange.oldCell); } if (_commonCellIndex >= 0) { diff --git a/src/main/java/com/metaweb/gridworks/model/operations/ApproveReconOperation.java b/src/main/java/com/metaweb/gridworks/model/operations/ApproveReconOperation.java index 67d04e9f1..84b207df2 100644 --- a/src/main/java/com/metaweb/gridworks/model/operations/ApproveReconOperation.java +++ b/src/main/java/com/metaweb/gridworks/model/operations/ApproveReconOperation.java @@ -56,7 +56,7 @@ public class ApproveReconOperation extends EngineDependentMassCellOperation { cell.recon.dup() ); newCell.recon.match = newCell.recon.candidates.get(0); - newCell.recon.judgment = Judgment.Approve; + newCell.recon.judgment = Judgment.Matched; CellChange cellChange = new CellChange(rowIndex, cellIndex, cell, newCell); cellChanges.add(cellChange); diff --git a/src/main/webapp/scripts/project/data-table-cell-ui.js b/src/main/webapp/scripts/project/data-table-cell-ui.js index 3e56e0338..2ee8e2c52 100644 --- a/src/main/webapp/scripts/project/data-table-cell-ui.js +++ b/src/main/webapp/scripts/project/data-table-cell-ui.js @@ -9,10 +9,14 @@ function DataTableCellUI(dataTableView, cell, rowIndex, cellIndex, td) { }; DataTableCellUI.prototype._render = function() { + var self = this; var cell = this._cell; var td = this._td; + $(td).empty(); + if (cell == null || cell.v == null) { + // TODO: content editing UI return; } @@ -21,31 +25,114 @@ DataTableCellUI.prototype._render = function() { } else { var r = cell.r; if (r.j == "new") { - $(td).html(cell.v + " (new)"); - } else if (r.j == "approve" && "m" in r && r.m != null) { + $(td).html(cell.v + " (new topic)"); + + $(' ').appendTo(td); + $('re-match') + .addClass("data-table-recon-action") + .appendTo(td).click(function(evt) { + self._doRematch(); + }); + } else if (r.j == "matched" && "m" in r && r.m != null) { var match = cell.r.m; $('') .attr("href", "http://www.freebase.com/view" + match.id) .attr("target", "_blank") .text(match.name) .appendTo(td); + + $(' ').appendTo(td); + $('re-match') + .addClass("data-table-recon-action") + .appendTo(td).click(function(evt) { + self._doRematch(); + }); } else { $(td).html(cell.v); + $(' ').appendTo(td); + $('mark as new') + .addClass("data-table-recon-action") + .appendTo(td).click(function(evt) { + self._doMarkAsNew(); + }); + if (this._dataTableView._showRecon && "c" in r && r.c.length > 0) { var candidates = r.c; - var ul = $('').appendTo(td); - - for (var i = 0; i < candidates.length; i++) { - var candidate = candidates[i]; + var ul = $('').addClass("data-table-recon-candidates").appendTo(td); + var renderCandidate = function(candidate, index) { var li = $('
  • ').appendTo(ul); $('') + .addClass("data-table-recon-topic") .attr("href", "http://www.freebase.com/view" + candidate.id) .attr("target", "_blank") .text(candidate.name) .appendTo(li); - $('').addClass("recon-score").text("(" + Math.round(candidate.score) + ")").appendTo(li); + + $('').addClass("data-table-recon-score").text("(" + Math.round(candidate.score) + ")").appendTo(li); + $('match') + .addClass("data-table-recon-action") + .appendTo(li).click(function(evt) { + self._doSetAsMatch(candidate.id); + }); + }; + + for (var i = 0; i < candidates.length; i++) { + renderCandidate(candidates[i], i); } } } } -}; \ No newline at end of file +}; + +DataTableCellUI.prototype._doRematch = function() { + this._doJudgment("discard"); +}; + +DataTableCellUI.prototype._doMarkAsNew = function() { + this._doJudgment("new"); +}; + +DataTableCellUI.prototype._doSetAsMatch = function(candidateID) { + this._doJudgment("match", { candidate : candidateID }); +}; + +DataTableCellUI.prototype._doJudgment = function(judgment, params) { + params = params || {}; + params.row = this._rowIndex; + params.cell = this._cellIndex; + params.judgment = judgment; + this.doPostThenUpdate("judge-one-cell", params); +}; + +DataTableCellUI.prototype.createUpdateFunction = function(onBefore) { + var self = this; + return function(data) { + if (data.code == "ok") { + var onDone = function() { + self._cell = data.cell; + self._render(); + ui.historyWidget.update(); + }; + } else { + var onDone = function() { + ui.processWidget.update(); + } + } + + if (onBefore) { + onBefore(onDone); + } else { + onDone(); + } + }; +}; + +DataTableCellUI.prototype.doPostThenUpdate = function(command, params) { + params.project = theProject.id; + $.post( + "/command/" + command + "?" + $.param(params), + null, + this.createUpdateFunction(), + "json" + ); +}; diff --git a/src/main/webapp/styles/browsing.css b/src/main/webapp/styles/browsing.css index e9ecd49aa..3a41b48af 100644 --- a/src/main/webapp/styles/browsing.css +++ b/src/main/webapp/styles/browsing.css @@ -94,8 +94,3 @@ a.facet-choice-link:hover { .facet-text-body input { width: 98%; } - -.recon-score { - color: #aaa; - margin-left: 0.5em; -} \ No newline at end of file diff --git a/src/main/webapp/styles/data-table-view.css b/src/main/webapp/styles/data-table-view.css index f2902a844..5191808e2 100644 --- a/src/main/webapp/styles/data-table-view.css +++ b/src/main/webapp/styles/data-table-view.css @@ -41,3 +41,35 @@ img.column-header-menu { text-align: center; margin: 1em 0; } + + +ul.data-table-recon-candidates { + margin: 0.75em 1.5em; + padding: 0; + list-style: circle; + color: #88a; +} + +a.data-table-recon-topic { + text-decoration: none; + color: #88a; +} +a.data-table-recon-topic:hover { + text-decoration: underline; + color: #008; +} + +.data-table-recon-score { + color: #aaa; + margin: 0 0.5em; +} + +a.data-table-recon-action { + font-size: 80%; + text-decoration: none; + color: #aaf; +} +a.data-table-recon-action:hover { + text-decoration: underline; + color: #008; +}