Client-side UI widget for long running processes.

git-svn-id: http://google-refine.googlecode.com/svn/trunk@16 7d457c2a-affb-35e4-300a-418c747d4874
This commit is contained in:
David Huynh 2010-02-01 03:22:35 +00:00
parent 86f8c630ad
commit 06b5373151
14 changed files with 264 additions and 12 deletions

View File

@ -20,6 +20,7 @@ import com.metaweb.gridlock.commands.CreateProjectFromUploadCommand;
import com.metaweb.gridlock.commands.DoTextTransformCommand; import com.metaweb.gridlock.commands.DoTextTransformCommand;
import com.metaweb.gridlock.commands.GetColumnModelCommand; import com.metaweb.gridlock.commands.GetColumnModelCommand;
import com.metaweb.gridlock.commands.GetHistoryCommand; import com.metaweb.gridlock.commands.GetHistoryCommand;
import com.metaweb.gridlock.commands.GetProcessesCommand;
import com.metaweb.gridlock.commands.GetProjectMetadataCommand; import com.metaweb.gridlock.commands.GetProjectMetadataCommand;
import com.metaweb.gridlock.commands.GetRowsCommand; import com.metaweb.gridlock.commands.GetRowsCommand;
import com.metaweb.gridlock.commands.UndoRedoCommand; import com.metaweb.gridlock.commands.UndoRedoCommand;
@ -31,12 +32,15 @@ public class GridlockServlet extends HttpServlet {
static { static {
_commands.put("create-project-from-upload", new CreateProjectFromUploadCommand()); _commands.put("create-project-from-upload", new CreateProjectFromUploadCommand());
_commands.put("get-project-metadata", new GetProjectMetadataCommand()); _commands.put("get-project-metadata", new GetProjectMetadataCommand());
_commands.put("get-column-model", new GetColumnModelCommand()); _commands.put("get-column-model", new GetColumnModelCommand());
_commands.put("get-rows", new GetRowsCommand()); _commands.put("get-rows", new GetRowsCommand());
_commands.put("get-processes", new GetProcessesCommand());
_commands.put("get-history", new GetHistoryCommand()); _commands.put("get-history", new GetHistoryCommand());
_commands.put("compute-facets", new ComputeFacetsCommand());
_commands.put("undo-redo", new UndoRedoCommand()); _commands.put("undo-redo", new UndoRedoCommand());
_commands.put("compute-facets", new ComputeFacetsCommand());
_commands.put("do-text-transform", new DoTextTransformCommand()); _commands.put("do-text-transform", new DoTextTransformCommand());
} }

View File

@ -30,14 +30,13 @@ public class DoTextTransformCommand extends Command {
Project project = getProject(request); Project project = getProject(request);
int cellIndex = Integer.parseInt(request.getParameter("cell")); int cellIndex = Integer.parseInt(request.getParameter("cell"));
String columnName = null; Column column = project.columnModel.getColumnByCellIndex(cellIndex);
for (Column column : project.columnModel.columns) { if (column == null) {
if (column.cellIndex == cellIndex) { respond(response, "{ \"code\" : \"error\", \"message\" : \"No such column\" }");
columnName = column.headerLabel; return;
break;
}
} }
String columnName = column.headerLabel;
String expression = request.getParameter("expression"); String expression = request.getParameter("expression");
try { try {

View File

@ -0,0 +1,29 @@
package com.metaweb.gridlock.commands;
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.JSONException;
import com.metaweb.gridlock.model.Project;
public class GetProcessesCommand extends Command {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Project project = getProject(request);
try {
Properties options = new Properties();
respondJSON(response, project.processManager.getJSON(options));
} catch (JSONException e) {
respondException(response, e);
}
}
}

View File

@ -2,6 +2,7 @@ package com.metaweb.gridlock.history;
import com.metaweb.gridlock.model.Project; import com.metaweb.gridlock.model.Project;
import com.metaweb.gridlock.process.Process; import com.metaweb.gridlock.process.Process;
import com.metaweb.gridlock.process.ProcessManager;
public class HistoryProcess extends Process { public class HistoryProcess extends Process {
final protected Project _project; final protected Project _project;
@ -27,6 +28,6 @@ public class HistoryProcess extends Process {
} }
@Override @Override
public void startPerforming() { public void startPerforming(ProcessManager manager) {
} }
} }

View File

@ -37,4 +37,13 @@ public class ColumnModel implements Serializable {
} }
return _nameToColumn.get(name); return _nameToColumn.get(name);
} }
public Column getColumnByCellIndex(int cellIndex) {
for (Column column : columns) {
if (column.cellIndex == cellIndex) {
return column;
}
}
return null;
}
} }

View File

@ -0,0 +1,57 @@
package com.metaweb.gridlock.process;
import java.util.Properties;
import org.json.JSONException;
import org.json.JSONObject;
abstract public class LongRunningProcess extends Process {
final protected String _description;
protected ProcessManager _manager;
protected Thread _thread;
protected int _progress; // out of 100
protected LongRunningProcess(String description) {
_description = description;
}
@Override
public void cancel() {
// TODO Auto-generated method stub
}
@Override
JSONObject getJSON(Properties options) throws JSONException {
JSONObject o = new JSONObject();
o.put("description", _description);
o.put("immediate", false);
o.put("status", _thread == null ? "pending" : (_thread.isAlive() ? "running" : "done"));
o.put("progress", _progress);
return o;
}
@Override
public boolean isImmediate() {
return false;
}
@Override
public void performImmediate() {
throw new RuntimeException("Not an immediate process");
}
@Override
public void startPerforming(ProcessManager manager) {
if (_thread == null) {
_manager = manager;
_thread = new Thread(getRunnable());
_thread.start();
}
}
abstract protected Runnable getRunnable();
}

View File

@ -1,10 +1,20 @@
package com.metaweb.gridlock.process; package com.metaweb.gridlock.process;
import java.util.Properties;
import org.json.JSONException;
import org.json.JSONObject;
public abstract class Process { public abstract class Process {
abstract public boolean isImmediate(); abstract public boolean isImmediate();
abstract public boolean isRunning();
abstract public boolean isDone();
abstract public void performImmediate(); abstract public void performImmediate();
abstract public void startPerforming(); abstract public void startPerforming(ProcessManager manager);
abstract public void cancel(); abstract public void cancel();
abstract JSONObject getJSON(Properties options) throws JSONException;
} }

View File

@ -1,7 +1,12 @@
package com.metaweb.gridlock.process; package com.metaweb.gridlock.process;
import java.util.ArrayList;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Properties;
import org.json.JSONException;
import org.json.JSONObject;
public class ProcessManager { public class ProcessManager {
protected List<Process> _processes = new LinkedList<Process>(); protected List<Process> _processes = new LinkedList<Process>();
@ -10,6 +15,18 @@ public class ProcessManager {
} }
public JSONObject getJSON(Properties options) throws JSONException {
JSONObject o = new JSONObject();
List<JSONObject> a = new ArrayList<JSONObject>(_processes.size());
for (Process p : _processes) {
a.add(p.getJSON(options));
}
o.put("processes", a);
return o;
}
public boolean queueProcess(Process process) { public boolean queueProcess(Process process) {
if (process.isImmediate() && _processes.size() == 0) { if (process.isImmediate() && _processes.size() == 0) {
process.performImmediate(); process.performImmediate();
@ -19,4 +36,25 @@ public class ProcessManager {
return false; return false;
} }
} }
public void onDoneProcess(Process p) {
update();
}
protected void update() {
while (_processes.size() > 0) {
Process p = _processes.get(0);
if (p.isImmediate()) {
p.performImmediate();
_processes.remove(0);
} else if (p.isDone()) {
_processes.remove(0);
} else {
if (!p.isRunning()) {
p.startPerforming(this);
}
break;
}
}
}
} }

View File

@ -1,11 +1,17 @@
package com.metaweb.gridlock.process; package com.metaweb.gridlock.process;
import java.util.Properties;
import org.json.JSONException;
import org.json.JSONObject;
import com.metaweb.gridlock.history.HistoryEntry; import com.metaweb.gridlock.history.HistoryEntry;
import com.metaweb.gridlock.model.Project; import com.metaweb.gridlock.model.Project;
public class QuickHistoryEntryProcess extends Process { public class QuickHistoryEntryProcess extends Process {
final protected Project _project; final protected Project _project;
final protected HistoryEntry _historyEntry; final protected HistoryEntry _historyEntry;
boolean _done = false;
public QuickHistoryEntryProcess(Project project, HistoryEntry historyEntry) { public QuickHistoryEntryProcess(Project project, HistoryEntry historyEntry) {
_project = project; _project = project;
@ -14,19 +20,43 @@ public class QuickHistoryEntryProcess extends Process {
@Override @Override
public void cancel() { public void cancel() {
throw new RuntimeException("Not a long-running process");
} }
@Override @Override
public boolean isImmediate() { public boolean isImmediate() {
return true; return true;
} }
@Override
public boolean isRunning() {
throw new RuntimeException("Not a long-running process");
}
@Override @Override
public void performImmediate() { public void performImmediate() {
_project.history.addEntry(_historyEntry); _project.history.addEntry(_historyEntry);
_done = true;
} }
@Override @Override
public void startPerforming() { public void startPerforming(ProcessManager manager) {
throw new RuntimeException("Not a long-running process");
}
@Override
JSONObject getJSON(Properties options) throws JSONException {
JSONObject o = new JSONObject();
o.put("description", _historyEntry.description);
o.put("immediate", true);
o.put("status", _done ? "done" : "pending");
return o;
}
@Override
public boolean isDone() {
return _done;
} }
} }

View File

@ -1 +1 @@
<html> <head> <title>Gridlock</title> <link rel="stylesheet" href="/styles/common.css" /> <link rel="stylesheet" href="/styles/project.css" /> <link rel="stylesheet" href="/styles/history.css" /> <link rel="stylesheet" href="/styles/browsing.css" /> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script> <script type="text/javascript" src="scripts/util/misc.js"></script> <script type="text/javascript" src="scripts/util/url.js"></script> <script type="text/javascript" src="scripts/util/string.js"></script> <script type="text/javascript" src="scripts/util/ajax.js"></script> <script type="text/javascript" src="scripts/util/menu.js"></script> <script type="text/javascript" src="scripts/project.js"></script> <script type="text/javascript" src="scripts/project/list-facet.js"></script> <script type="text/javascript" src="scripts/project/browsing-engine.js"></script> <script type="text/javascript" src="scripts/project/data-table-view.js"></script> <script type="text/javascript" src="scripts/project/history-widget.js"></script> </head> <body> <div id="header"> <h1 id="title">Gridlock</h1> </div> <div id="body"> Loading ... </div> </body> </html> <html> <head> <title>Gridlock</title> <link rel="stylesheet" href="/styles/common.css" /> <link rel="stylesheet" href="/styles/project.css" /> <link rel="stylesheet" href="/styles/history.css" /> <link rel="stylesheet" href="/styles/browsing.css" /> <link rel="stylesheet" href="/styles/process.css" /> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script> <script type="text/javascript" src="scripts/util/misc.js"></script> <script type="text/javascript" src="scripts/util/url.js"></script> <script type="text/javascript" src="scripts/util/string.js"></script> <script type="text/javascript" src="scripts/util/ajax.js"></script> <script type="text/javascript" src="scripts/util/menu.js"></script> <script type="text/javascript" src="scripts/project.js"></script> <script type="text/javascript" src="scripts/project/list-facet.js"></script> <script type="text/javascript" src="scripts/project/browsing-engine.js"></script> <script type="text/javascript" src="scripts/project/data-table-view.js"></script> <script type="text/javascript" src="scripts/project/history-widget.js"></script> <script type="text/javascript" src="scripts/project/process-widget.js"></script> </head> <body> <div id="header"> <h1 id="title">Gridlock</h1> </div> <div id="body"> Loading ... </div> </body> </html>

View File

@ -48,8 +48,10 @@ function initializeUI() {
ui.viewPanel = $('<div></div>').appendTo(tdLeft).css("width", tdLeft.offsetWidth + "px").css("overflow-x", "auto"); ui.viewPanel = $('<div></div>').appendTo(tdLeft).css("width", tdLeft.offsetWidth + "px").css("overflow-x", "auto");
ui.facetPanel = $('<div></div>').appendTo(tdRight); ui.facetPanel = $('<div></div>').appendTo(tdRight);
ui.historyPanel = $('<div></div>').addClass("history-panel").appendTo(document.body); ui.historyPanel = $('<div></div>').addClass("history-panel").appendTo(document.body);
ui.processPanel = $('<div></div>').addClass("process-panel").appendTo(document.body);
ui.browsingEngine = new BrowsingEngine(ui.facetPanel); ui.browsingEngine = new BrowsingEngine(ui.facetPanel);
ui.processWidget = new ProcessWidget(ui.processPanel);
ui.historyWidget = new HistoryWidget(ui.historyPanel); ui.historyWidget = new HistoryWidget(ui.historyPanel);
ui.dataTableView = new DataTableView(ui.viewPanel); ui.dataTableView = new DataTableView(ui.viewPanel);
} }

View File

@ -289,7 +289,7 @@ DataTableView.prototype._doTextTransform = function(column, expression) {
self.update(); self.update();
ui.historyWidget.update(); ui.historyWidget.update();
} else { } else {
// update process UI ui.processWidget.update();
} }
}, },
"json" "json"

View File

@ -0,0 +1,58 @@
function ProcessWidget(div) {
this._div = div;
this._timerID = null;
this.update();
}
ProcessWidget.prototype.update = function() {
if (this._timerID != null) {
return;
}
var self = this;
Ajax.chainGetJSON(
"/command/get-processes?" + $.param({ project: theProject.id }), null,
function(data) {
self._data = data;
self._render();
}
);
};
ProcessWidget.prototype._render = function() {
var self = this;
this._div.empty();
var bodyDiv = $('<div></div>').addClass("process-panel-inner").text("Testing").appendTo(this._div);
if (this._data.processes.length == 0) {
this._div.hide();
return;
}
this._div.show();
var hasPending = false;
var renderProcess = function(process) {
var div = $('<div></div>').addClass("process-panel-entry").appendTo(bodyDiv);
if (process.status == "pending") {
div.text(process.description + " (pending)");
} else {
div.text(process.description + " (" + process.progress + "%)");
hasPending = true;
}
};
var processes = this._data.processes;
for (var i = 0; i < processes.length; i++) {
renderProcess(processes[i]);
}
if (hasPending && this._timerID == null) {
this._timerID = window.setTimeout(function() {
self._timerID = null;
self.update();
}, 500);
}
};

View File

@ -0,0 +1,15 @@
.process-panel {
position: absolute;
top: -1px;
width: 100%;
text-align: center;
}
.process-panel-inner {
border: 1px solid #ccc;
width: 500px;
padding: 10px;
background: #fffee0;
margin: 0 auto;
text-align: left;
}