SpreadsheetView.java revision 313ab769319f56b940a2784cc0cbdebd005c5799
1package autotest.tko;
2
3import autotest.common.JsonRpcCallback;
4import autotest.common.JsonRpcProxy;
5import autotest.common.Utils;
6import autotest.common.ui.ContextMenu;
7import autotest.common.ui.NotifyManager;
8import autotest.common.ui.RightClickTable;
9import autotest.common.ui.SimpleHyperlink;
10import autotest.common.ui.TableActionsPanel;
11import autotest.common.ui.TableActionsPanel.TableActionsListener;
12import autotest.common.ui.TableSelectionPanel.SelectionPanelListener;
13import autotest.tko.CommonPanel.CommonPanelListener;
14import autotest.tko.Spreadsheet.CellInfo;
15import autotest.tko.Spreadsheet.Header;
16import autotest.tko.Spreadsheet.SpreadsheetListener;
17import autotest.tko.TableView.TableSwitchListener;
18import autotest.tko.TableView.TableViewConfig;
19import autotest.tko.TkoUtils.FieldInfo;
20
21import com.google.gwt.json.client.JSONArray;
22import com.google.gwt.json.client.JSONObject;
23import com.google.gwt.json.client.JSONString;
24import com.google.gwt.json.client.JSONValue;
25import com.google.gwt.user.client.Command;
26import com.google.gwt.user.client.Event;
27import com.google.gwt.user.client.Window;
28import com.google.gwt.user.client.WindowResizeListener;
29import com.google.gwt.user.client.ui.Button;
30import com.google.gwt.user.client.ui.CheckBox;
31import com.google.gwt.user.client.ui.ClickListener;
32import com.google.gwt.user.client.ui.HTML;
33import com.google.gwt.user.client.ui.MenuBar;
34import com.google.gwt.user.client.ui.Panel;
35import com.google.gwt.user.client.ui.RootPanel;
36import com.google.gwt.user.client.ui.VerticalPanel;
37import com.google.gwt.user.client.ui.Widget;
38
39import java.util.ArrayList;
40import java.util.HashMap;
41import java.util.List;
42import java.util.Map;
43
44public class SpreadsheetView extends ConditionTabView
45                             implements SpreadsheetListener, TableActionsListener,
46                                        CommonPanelListener, SelectionPanelListener {
47    private static final String HISTORY_ONLY_LATEST = "show_only_latest";
48    public static final String DEFAULT_ROW = "kernel";
49    public static final String DEFAULT_COLUMN = "platform";
50
51    private static final String HISTORY_SHOW_INCOMPLETE = "show_incomplete";
52    private static final String HISTORY_COLUMN = "column";
53    private static final String HISTORY_ROW = "row";
54
55    private static enum DrilldownType {DRILLDOWN_ROW, DRILLDOWN_COLUMN, DRILLDOWN_BOTH}
56
57    private static JsonRpcProxy rpcProxy = JsonRpcProxy.getProxy();
58    private static JsonRpcProxy afeRpcProxy = JsonRpcProxy.getProxy(JsonRpcProxy.AFE_BASE_URL);
59    private TableSwitchListener listener;
60    protected List<HeaderField> currentRowFields;
61    protected List<HeaderField> currentColumnFields;
62    protected Map<String,String[]> drilldownMap = new HashMap<String,String[]>();
63    private Map<String, HeaderField> headerFieldMap = new HashMap<String, HeaderField>();
64
65    private HeaderSelect rowSelect = new HeaderSelect();
66    private HeaderSelect columnSelect = new HeaderSelect();
67    private CheckBox showIncomplete = new CheckBox("Show incomplete tests");
68    private CheckBox showOnlyLatest = new CheckBox("Show only latest test per cell");
69    private Button queryButton = new Button("Query");
70    private TestGroupDataSource normalDataSource = TestGroupDataSource.getStatusCountDataSource();
71    private TestGroupDataSource latestDataSource = TestGroupDataSource.getLatestTestsDataSource();
72    private Spreadsheet spreadsheet = new Spreadsheet();
73    private SpreadsheetDataProcessor spreadsheetProcessor =
74        new SpreadsheetDataProcessor(spreadsheet);
75    private SpreadsheetSelectionManager selectionManager =
76        new SpreadsheetSelectionManager(spreadsheet, null);
77    private TableActionsPanel actionsPanel = new TableActionsPanel(false);
78    private RootPanel jobCompletionPanel;
79    private boolean currentShowIncomplete, currentShowOnlyLatest;
80    private boolean notYetQueried = true;
81
82    public SpreadsheetView(TableSwitchListener listener) {
83        this.listener = listener;
84        commonPanel.addListener(this);
85    }
86
87    @Override
88    public String getElementId() {
89        return "spreadsheet_view";
90    }
91
92    @Override
93    public void initialize() {
94        normalDataSource.setSkipNumResults(true);
95        latestDataSource.setSkipNumResults(true);
96
97        actionsPanel.setActionsListener(this);
98        actionsPanel.setSelectionListener(this);
99
100        currentRowFields = new ArrayList<HeaderField>();
101        currentColumnFields = new ArrayList<HeaderField>();
102
103        for (FieldInfo fieldInfo : TkoUtils.getFieldList("group_fields")) {
104            HeaderField field = new SimpleHeaderField(fieldInfo.name, fieldInfo.field);
105            headerFieldMap.put(fieldInfo.field, field);
106            rowSelect.addItem(field);
107            columnSelect.addItem(field);
108        }
109        currentRowFields.add(headerFieldMap.get(DEFAULT_ROW));
110        currentColumnFields.add(headerFieldMap.get(DEFAULT_COLUMN));
111        updateWidgets();
112
113        queryButton.addClickListener(new ClickListener() {
114            public void onClick(Widget sender) {
115                doQuery();
116                updateHistory();
117            }
118        });
119
120        spreadsheet.setVisible(false);
121        spreadsheet.setListener(this);
122
123        SimpleHyperlink swapLink = new SimpleHyperlink("swap");
124        swapLink.addClickListener(new ClickListener() {
125            public void onClick(Widget sender) {
126                List<HeaderField> newRows = columnSelect.getSelectedItems();
127                setSelectedHeader(columnSelect, rowSelect.getSelectedItems());
128                setSelectedHeader(rowSelect, newRows);
129            }
130        });
131
132        Panel filterOptions = new VerticalPanel();
133        filterOptions.add(showIncomplete);
134        filterOptions.add(showOnlyLatest);
135
136        RootPanel.get("ss_filter_options").add(filterOptions);
137        RootPanel.get("ss_row_select").add(rowSelect);
138        RootPanel.get("ss_column_select").add(columnSelect);
139        RootPanel.get("ss_swap").add(swapLink);
140        RootPanel.get("ss_query_controls").add(queryButton);
141        RootPanel.get("ss_actions").add(actionsPanel);
142        RootPanel.get("ss_spreadsheet").add(spreadsheet);
143        jobCompletionPanel = RootPanel.get("ss_job_completion");
144
145        Window.addWindowResizeListener(new WindowResizeListener() {
146            public void onWindowResized(int width, int height) {
147                if(spreadsheet.isVisible())
148                    spreadsheet.fillWindow(true);
149            }
150        });
151
152        setupDrilldownMap();
153    }
154
155    protected TestSet getWholeTableTestSet() {
156        boolean isSingleTest = spreadsheetProcessor.getNumTotalTests() == 1;
157        if (isSingleTest) {
158            return getTestSet(spreadsheetProcessor.getLastCellInfo());
159        }
160        return new ConditionTestSet(getFullConditionArgs());
161    }
162
163    protected void setupDrilldownMap() {
164        drilldownMap.put("platform", new String[] {"hostname", "test_name"});
165        drilldownMap.put("hostname", new String[] {"job_tag", "status"});
166        drilldownMap.put("job_tag", new String[] {"job_tag"});
167
168        drilldownMap.put("kernel", new String[] {"test_name", "status"});
169        drilldownMap.put("test_name", new String[] {"job_name", "job_tag"});
170
171        drilldownMap.put("status", new String[] {"reason", "job_tag"});
172        drilldownMap.put("reason", new String[] {"job_tag"});
173
174        drilldownMap.put("job_owner", new String[] {"job_name", "job_tag"});
175        drilldownMap.put("job_name", new String[] {"job_tag"});
176
177        drilldownMap.put("test_finished_time", new String[] {"status", "job_tag"});
178        drilldownMap.put("DATE(test_finished_time)",
179                         new String[] {"test_finished_time", "job_tag"});
180    }
181
182    protected void setSelectedHeader(HeaderSelect list, List<HeaderField> fields) {
183        list.selectItems(fields);
184    }
185
186    @Override
187    public void refresh() {
188        notYetQueried = false;
189        spreadsheet.setVisible(false);
190        selectionManager.clearSelection();
191        spreadsheet.clear();
192        setJobCompletionHtml("&nbsp");
193
194        final JSONObject condition = getFullConditionArgs();
195        JSONObject queryParameters = getQueryParameters();
196
197        setLoading(true);
198        if (currentShowOnlyLatest) {
199            spreadsheetProcessor.setDataSource(latestDataSource);
200        } else {
201            spreadsheetProcessor.setDataSource(normalDataSource);
202        }
203        spreadsheetProcessor.setHeaders(currentRowFields, currentColumnFields,
204                                        getQueryParameters());
205        spreadsheetProcessor.refresh(condition, new Command() {
206            public void execute() {
207                if (isJobFilteringCondition(condition)) {
208                    showCompletionPercentage(condition);
209                } else {
210                    setLoading(false);
211                }
212            }
213        });
214    }
215
216    private JSONObject getQueryParameters() {
217        JSONObject parameters = new JSONObject();
218        rowSelect.addQueryParameters(parameters);
219        columnSelect.addQueryParameters(parameters);
220        return parameters;
221    }
222
223    private JSONObject getFullConditionArgs() {
224        JSONObject args = commonPanel.getSavedConditionArgs();
225        String condition = TkoUtils.getSqlCondition(args);
226        if (!condition.equals("")) {
227            condition = "(" + condition + ") AND ";
228        }
229        condition += "status != 'TEST_NA'";
230        if (!currentShowIncomplete) {
231            condition += " AND status != 'RUNNING'";
232        }
233        args.put("extra_where", new JSONString(condition));
234        return args;
235    }
236
237    public void doQuery() {
238        List<HeaderField> rows = rowSelect.getSelectedItems();
239        List<HeaderField> columns = columnSelect.getSelectedItems();
240        if (rows.isEmpty() || columns.isEmpty()) {
241            NotifyManager.getInstance().showError("You must select row and column fields");
242            return;
243        }
244        if (!checkMachineLabelHeaders(rowSelect) || !checkMachineLabelHeaders(columnSelect)) {
245            NotifyManager.getInstance().showError(
246                      "You must enter labels for all machine label fields");
247            return;
248        }
249        saveSelectedHeaders();
250        currentShowIncomplete = showIncomplete.isChecked();
251        currentShowOnlyLatest = showOnlyLatest.isChecked();
252        commonPanel.saveSqlCondition();
253        refresh();
254    }
255
256    private boolean checkMachineLabelHeaders(HeaderSelect headerSelect) {
257        for (MachineLabelField field : headerSelect.getMachineLabelHeaders()) {
258            if (field.getLabelList().isEmpty()) {
259                return false;
260            }
261        }
262        return true;
263    }
264
265    private void saveSelectedHeaders() {
266        currentRowFields = rowSelect.getSelectedItems();
267        currentColumnFields = columnSelect.getSelectedItems();
268    }
269
270    private void showCompletionPercentage(JSONObject condition) {
271        rpcProxy.rpcCall("get_job_ids", condition, new JsonRpcCallback() {
272            @Override
273            public void onSuccess(JSONValue result) {
274                finishShowCompletionPercentage(result.isArray());
275                setLoading(false);
276            }
277
278            @Override
279            public void onError(JSONObject errorObject) {
280                super.onError(errorObject);
281                setLoading(false);
282            }
283        });
284    }
285
286    private void finishShowCompletionPercentage(JSONArray jobIds) {
287        final int jobCount = jobIds.size();
288        if (jobCount == 0) {
289            return;
290        }
291
292        JSONObject args = new JSONObject();
293        args.put("job__id__in", jobIds);
294        afeRpcProxy.rpcCall("get_hqe_percentage_complete", args, new JsonRpcCallback() {
295            @Override
296            public void onSuccess(JSONValue result) {
297                int percentage = (int) (result.isNumber().doubleValue() * 100);
298                StringBuilder message = new StringBuilder("Matching ");
299                if (jobCount == 1) {
300                    message.append("job is ");
301                } else {
302                    message.append("jobs are ");
303                }
304                message.append(percentage);
305                message.append("% complete");
306                setJobCompletionHtml(message.toString());
307            }
308        });
309    }
310
311    private void setJobCompletionHtml(String html) {
312        jobCompletionPanel.clear();
313        jobCompletionPanel.add(new HTML(html));
314    }
315
316    private boolean isJobFilteringCondition(JSONObject condition) {
317        return TkoUtils.getSqlCondition(condition).indexOf("job_tag") != -1;
318    }
319
320    public void onCellClicked(CellInfo cellInfo) {
321        Event event = Event.getCurrentEvent();
322        TestSet testSet = getTestSet(cellInfo);
323        DrilldownType drilldownType = getDrilldownType(cellInfo);
324        if (RightClickTable.isRightClick(event)) {
325            if (!selectionManager.isEmpty()) {
326                testSet = getTestSet(selectionManager.getSelectedCells());
327                drilldownType = DrilldownType.DRILLDOWN_BOTH;
328            }
329            ContextMenu menu = getContextMenu(testSet, drilldownType);
330            menu.showAtWindow(event.getClientX(), event.getClientY());
331            return;
332        }
333
334        if (isSelectEvent(event)) {
335            selectionManager.toggleSelected(cellInfo);
336            return;
337        }
338
339        if (testSet.isSingleTest()) {
340            listener.onSelectTest(testSet.getTestIndex());
341            return;
342        }
343
344        doDrilldown(testSet,
345                    getDefaultDrilldownRow(drilldownType),
346                    getDefaultDrilldownColumn(drilldownType));
347    }
348
349    private DrilldownType getDrilldownType(CellInfo cellInfo) {
350        if (cellInfo.row == null) {
351            // column header
352            return DrilldownType.DRILLDOWN_COLUMN;
353        }
354        if (cellInfo.column == null) {
355            // row header
356            return DrilldownType.DRILLDOWN_ROW;
357        }
358        return DrilldownType.DRILLDOWN_BOTH;
359    }
360
361    private TestSet getTestSet(CellInfo cellInfo) {
362        boolean isSingleTest = cellInfo.testCount == 1;
363        if (isSingleTest) {
364            return new SingleTestSet(cellInfo.testIndex, getFullConditionArgs());
365        }
366
367        ConditionTestSet testSet = new ConditionTestSet(getFullConditionArgs());
368        if (cellInfo.row != null) {
369            setSomeFields(testSet, currentRowFields, cellInfo.row);
370        }
371        if (cellInfo.column != null) {
372            setSomeFields(testSet, currentColumnFields, cellInfo.column);
373        }
374        return testSet;
375    }
376
377    private void setSomeFields(ConditionTestSet testSet, List<HeaderField> allFields,
378                               Header values) {
379        for (int i = 0; i < values.size(); i++) {
380            HeaderField field = allFields.get(i);
381            String value = values.get(i);
382            testSet.addCondition(field.getSqlCondition(value));
383        }
384    }
385
386    private TestSet getTestSet(List<CellInfo> cells) {
387        CompositeTestSet tests = new CompositeTestSet();
388        for (CellInfo cell : cells) {
389            tests.add(getTestSet(cell));
390        }
391        return tests;
392    }
393
394    private void doDrilldown(TestSet tests, String newRowField, String newColumnField) {
395        commonPanel.refineCondition(tests);
396        currentRowFields = Utils.wrapObjectWithList(headerFieldMap.get(newRowField));
397        currentColumnFields = Utils.wrapObjectWithList(headerFieldMap.get(newColumnField));
398        rowSelect.resetFixedValues();
399        columnSelect.resetFixedValues();
400        updateWidgets();
401        doQuery();
402        updateHistory();
403    }
404
405    private String getDefaultDrilldownRow(DrilldownType type) {
406        return getDrilldownRows(type)[0];
407    }
408
409    private String getDefaultDrilldownColumn(DrilldownType type) {
410        return getDrilldownColumns(type)[0];
411    }
412
413    private ContextMenu getContextMenu(final TestSet tests, DrilldownType drilldownType) {
414        TestContextMenu menu = new TestContextMenu(tests, listener);
415
416        if (!menu.addViewDetailsIfSingleTest()) {
417            MenuBar drilldownMenu = menu.addSubMenuItem("Drill down");
418            fillDrilldownMenu(tests, drilldownType, drilldownMenu);
419        }
420
421        menu.addItem("View in table", new Command() {
422            public void execute() {
423                switchToTable(tests, false);
424            }
425        });
426        menu.addItem("Triage failures", new Command() {
427            public void execute() {
428                switchToTable(tests, true);
429            }
430        });
431
432        menu.addLabelItems();
433        return menu;
434    }
435
436    private void fillDrilldownMenu(final TestSet tests, DrilldownType drilldownType, MenuBar menu) {
437        for (final String rowField : getDrilldownRows(drilldownType)) {
438            for (final String columnField : getDrilldownColumns(drilldownType)) {
439                if (rowField.equals(columnField)) {
440                    continue;
441                }
442                menu.addItem(rowField + " vs. " + columnField, new Command() {
443                    public void execute() {
444                        doDrilldown(tests, rowField, columnField);
445                    }
446                });
447            }
448        }
449    }
450
451    private String[] getDrilldownFields(List<HeaderField> fields, DrilldownType type,
452                                        DrilldownType otherType) {
453        HeaderField lastField = fields.get(fields.size() - 1);
454        String lastFieldName = lastField.getSqlName();
455        if (type == otherType) {
456            return new String[] {lastFieldName};
457        } else {
458            if (lastField instanceof MachineLabelField) {
459                // treat machine label fields like platform, for the purpose of default drilldown
460                lastFieldName = "platform";
461            }
462            return drilldownMap.get(lastFieldName);
463        }
464    }
465
466    private String[] getDrilldownRows(DrilldownType type) {
467        return getDrilldownFields(currentRowFields, type, DrilldownType.DRILLDOWN_COLUMN);
468    }
469
470    private String[] getDrilldownColumns(DrilldownType type) {
471        return getDrilldownFields(currentColumnFields, type, DrilldownType.DRILLDOWN_ROW);
472    }
473
474    private void updateWidgets() {
475        setSelectedHeader(rowSelect, currentRowFields);
476        setSelectedHeader(columnSelect, currentColumnFields);
477        showIncomplete.setChecked(currentShowIncomplete);
478    }
479
480    @Override
481    protected Map<String, String> getHistoryArguments() {
482        Map<String, String> arguments = super.getHistoryArguments();
483        if (!notYetQueried) {
484            rowSelect.addHistoryArguments(arguments, HISTORY_ROW);
485            columnSelect.addHistoryArguments(arguments, HISTORY_COLUMN);
486            arguments.put(HISTORY_SHOW_INCOMPLETE, Boolean.toString(currentShowIncomplete));
487            arguments.put(HISTORY_ONLY_LATEST, Boolean.toString(showOnlyLatest.isChecked()));
488            commonPanel.addHistoryArguments(arguments);
489        }
490        return arguments;
491    }
492
493    @Override
494    public void handleHistoryArguments(Map<String, String> arguments) {
495        super.handleHistoryArguments(arguments);
496        commonPanel.handleHistoryArguments(arguments);
497        rowSelect.handleHistoryArguments(arguments, HISTORY_ROW);
498        columnSelect.handleHistoryArguments(arguments, HISTORY_COLUMN);
499        saveSelectedHeaders();
500
501        currentShowIncomplete = Boolean.valueOf(arguments.get(HISTORY_SHOW_INCOMPLETE));
502        showOnlyLatest.setChecked(Boolean.valueOf(arguments.get(HISTORY_ONLY_LATEST)));
503        updateWidgets();
504    }
505
506    @Override
507    protected void fillDefaultHistoryValues(Map<String, String> arguments) {
508        Utils.setDefaultValue(arguments, HISTORY_ROW, DEFAULT_ROW);
509        Utils.setDefaultValue(arguments, HISTORY_COLUMN, DEFAULT_COLUMN);
510        Utils.setDefaultValue(arguments, HISTORY_ROW + HeaderSelect.HISTORY_FIXED_VALUES, "");
511        Utils.setDefaultValue(arguments, HISTORY_COLUMN + HeaderSelect.HISTORY_FIXED_VALUES, "");
512        Utils.setDefaultValue(arguments, HISTORY_SHOW_INCOMPLETE, Boolean.toString(false));
513        Utils.setDefaultValue(arguments, HISTORY_ONLY_LATEST, Boolean.toString(false));
514    }
515
516    private void switchToTable(final TestSet tests, boolean isTriageView) {
517        commonPanel.refineCondition(tests);
518        TableViewConfig config;
519        if (isTriageView) {
520            config = TableViewConfig.TRIAGE;
521            refineConditionForTriage();
522        } else {
523            config = TableViewConfig.DEFAULT;
524        }
525        listener.onSwitchToTable(config);
526    }
527
528    private void refineConditionForTriage() {
529        commonPanel.refineCondition("status != 'GOOD'");
530        if (currentShowOnlyLatest) {
531            List<Integer> testIndices = spreadsheet.getAllTestIndices();
532            String filter = "test_idx IN (" + Utils.joinStrings(",", testIndices) + ")";
533            commonPanel.refineCondition(filter);
534        }
535    }
536
537    public ContextMenu getActionMenu() {
538        TestSet tests;
539        if (selectionManager.isEmpty()) {
540            tests = getWholeTableTestSet();
541        } else {
542            tests = getTestSet(selectionManager.getSelectedCells());
543        }
544        return getContextMenu(tests, DrilldownType.DRILLDOWN_BOTH);
545    }
546
547    public void onSelectAll(boolean ignored) {
548        selectionManager.selectAll();
549    }
550
551    public void onSelectNone() {
552        selectionManager.clearSelection();
553    }
554
555    @Override
556    protected boolean hasFirstQueryOccurred() {
557        return !notYetQueried;
558    }
559
560    private void setLoading(boolean loading) {
561        queryButton.setEnabled(!loading);
562        NotifyManager.getInstance().setLoading(loading);
563    }
564
565    public void onSetControlsVisible(boolean visible) {
566        TkoUtils.setElementVisible("ss_all_controls", visible);
567        if (isTabVisible()) {
568            spreadsheet.fillWindow(true);
569        }
570    }
571}
572