1package autotest.tko;
2
3import autotest.common.StatusSummary;
4import autotest.common.Utils;
5import autotest.common.CustomHistory.HistoryToken;
6import autotest.common.table.DataTable;
7import autotest.common.table.DynamicTable;
8import autotest.common.table.RpcDataSource;
9import autotest.common.table.SelectionManager;
10import autotest.common.table.SimpleFilter;
11import autotest.common.table.TableDecorator;
12import autotest.common.table.DataSource.SortDirection;
13import autotest.common.table.DataSource.SortSpec;
14import autotest.common.table.DataTable.TableWidgetFactory;
15import autotest.common.table.DynamicTable.DynamicTableListener;
16import autotest.common.ui.ContextMenu;
17import autotest.common.ui.DoubleListSelector;
18import autotest.common.ui.MultiListSelectPresenter;
19import autotest.common.ui.NotifyManager;
20import autotest.common.ui.MultiListSelectPresenter.Item;
21import autotest.common.ui.TableActionsPanel.TableActionsWithExportCsvListener;
22import autotest.tko.CommonPanel.CommonPanelListener;
23
24import com.google.gwt.event.dom.client.ClickEvent;
25import com.google.gwt.event.dom.client.ClickHandler;
26import com.google.gwt.json.client.JSONArray;
27import com.google.gwt.json.client.JSONObject;
28import com.google.gwt.user.client.Command;
29import com.google.gwt.user.client.Event;
30import com.google.gwt.user.client.History;
31import com.google.gwt.user.client.ui.Button;
32import com.google.gwt.user.client.ui.CheckBox;
33import com.google.gwt.user.client.ui.HTML;
34import com.google.gwt.user.client.ui.Panel;
35import com.google.gwt.user.client.ui.SimplePanel;
36import com.google.gwt.user.client.ui.VerticalPanel;
37import com.google.gwt.user.client.ui.Widget;
38
39import java.util.ArrayList;
40import java.util.Arrays;
41import java.util.Collection;
42import java.util.Iterator;
43import java.util.List;
44import java.util.ListIterator;
45import java.util.Map;
46
47public class TableView extends ConditionTabView
48                       implements DynamicTableListener, TableActionsWithExportCsvListener,
49                                  ClickHandler, TableWidgetFactory, CommonPanelListener,
50                                  MultiListSelectPresenter.GeneratorHandler {
51    private static final int ROWS_PER_PAGE = 30;
52    private static final String COUNT_NAME = "Count in group";
53    private static final String STATUS_COUNTS_NAME = "Test pass rate";
54    private static final String[] DEFAULT_COLUMNS =
55        {"Test index", "Test name", "Job tag", "Hostname", "Status"};
56    private static final String[] TRIAGE_GROUP_COLUMNS =
57        {"Test name", "Status", COUNT_NAME, "Reason"};
58    private static final String[] PASS_RATE_GROUP_COLUMNS =
59        {"Hostname", STATUS_COUNTS_NAME};
60    private static final SortSpec[] TRIAGE_SORT_SPECS = {
61        new SortSpec("test_name", SortDirection.ASCENDING),
62        new SortSpec("status", SortDirection.ASCENDING),
63        new SortSpec("reason", SortDirection.ASCENDING),
64    };
65
66    private static enum GroupingType {NO_GROUPING, TEST_GROUPING, STATUS_COUNTS}
67
68    /**
69     * HeaderField representing a grouped count of some kind.
70     */
71    private static class GroupCountField extends HeaderField {
72        public GroupCountField(String name, String sqlName) {
73            super(name, sqlName);
74        }
75
76        @Override
77        public Item getItem() {
78            return Item.createGeneratedItem(getName(), getSqlName());
79        }
80
81        @Override
82        public String getSqlCondition(String value) {
83            throw new UnsupportedOperationException();
84        }
85
86        @Override
87        public boolean isUserSelectable() {
88            return false;
89        }
90    }
91
92    private GroupCountField groupCountField =
93        new GroupCountField(COUNT_NAME, TestGroupDataSource.GROUP_COUNT_FIELD);
94    private GroupCountField statusCountsField =
95        new GroupCountField(STATUS_COUNTS_NAME, DataTable.WIDGET_COLUMN);
96
97    private TestSelectionListener listener;
98
99    private DynamicTable table;
100    private TableDecorator tableDecorator;
101    private SelectionManager selectionManager;
102    private SimpleFilter sqlConditionFilter = new SimpleFilter();
103    private RpcDataSource testDataSource = new TestViewDataSource();
104    private TestGroupDataSource groupDataSource = TestGroupDataSource.getTestGroupDataSource();
105
106    private HeaderFieldCollection headerFields = commonPanel.getHeaderFields();
107    private HeaderSelect columnSelect = new HeaderSelect(headerFields, new HeaderSelect.State());
108
109    private DoubleListSelector columnSelectDisplay = new DoubleListSelector();
110    private CheckBox groupCheckbox = new CheckBox("Group by these columns and show counts");
111    private CheckBox statusGroupCheckbox =
112        new CheckBox("Group by these columns and show pass rates");
113    private Button queryButton = new Button("Query");
114    private Panel tablePanel = new SimplePanel();
115
116    private List<SortSpec> tableSorts = new ArrayList<SortSpec>();
117
118    public enum TableViewConfig {
119        DEFAULT, PASS_RATE, TRIAGE
120    }
121
122    public static interface TableSwitchListener extends TestSelectionListener {
123        public void onSwitchToTable(TableViewConfig config);
124    }
125
126    public TableView(TestSelectionListener listener) {
127        this.listener = listener;
128        commonPanel.addListener(this);
129        columnSelect.setGeneratorHandler(this);
130        columnSelect.bindDisplay(columnSelectDisplay);
131    }
132
133    @Override
134    public String getElementId() {
135        return "table_view";
136    }
137
138    @Override
139    public void initialize() {
140        super.initialize();
141
142        headerFields.add(groupCountField);
143        headerFields.add(statusCountsField);
144
145        selectColumnsByName(DEFAULT_COLUMNS);
146        updateViewFromState();
147
148        queryButton.addClickHandler(this);
149        groupCheckbox.addClickHandler(this);
150        statusGroupCheckbox.addClickHandler(this);
151
152        Panel columnPanel = new VerticalPanel();
153        columnPanel.add(columnSelectDisplay);
154        columnPanel.add(groupCheckbox);
155        columnPanel.add(statusGroupCheckbox);
156
157        addWidget(columnPanel, "table_column_select");
158        addWidget(queryButton, "table_query_controls");
159        addWidget(tablePanel, "table_table");
160    }
161
162    private void selectColumnsByName(String[] columnNames) {
163        List<HeaderField> fields = new ArrayList<HeaderField>();
164        for (String name : columnNames) {
165            fields.add(headerFields.getFieldByName(name));
166        }
167        columnSelect.setSelectedItems(fields);
168        cleanupSortsForNewColumns();
169    }
170
171    public void setupDefaultView() {
172        tableSorts.clear();
173        selectColumnsByName(DEFAULT_COLUMNS);
174        updateViewFromState();
175    }
176
177    public void setupJobTriage() {
178        selectColumnsByName(TRIAGE_GROUP_COLUMNS);
179        // need to copy it so we can mutate it
180        tableSorts = new ArrayList<SortSpec>(Arrays.asList(TRIAGE_SORT_SPECS));
181        updateViewFromState();
182    }
183
184    public void setupPassRate() {
185        tableSorts.clear();
186        selectColumnsByName(PASS_RATE_GROUP_COLUMNS);
187        updateViewFromState();
188    }
189
190    private void createTable() {
191        String[][] columns = buildColumnSpecs();
192
193        table = new DynamicTable(columns, getDataSource());
194        table.addFilter(sqlConditionFilter);
195        table.setRowsPerPage(ROWS_PER_PAGE);
196        table.makeClientSortable();
197        table.setClickable(true);
198        table.sinkRightClickEvents();
199        table.addListener(this);
200        table.setWidgetFactory(this);
201        restoreTableSorting();
202
203        tableDecorator = new TableDecorator(table);
204        tableDecorator.addPaginators();
205        selectionManager = tableDecorator.addSelectionManager(false);
206        tableDecorator.addTableActionsWithExportCsvListener(this);
207        tablePanel.clear();
208        tablePanel.add(tableDecorator);
209
210        selectionManager = new SelectionManager(table, false);
211    }
212
213    private String[][] buildColumnSpecs() {
214        int numColumns = savedColumns().size();
215        String[][] columns = new String[numColumns][2];
216        int i = 0;
217        for (HeaderField field : savedColumns()) {
218            columns[i][0] = field.getSqlName();
219            columns[i][1] = field.getName();
220            i++;
221        }
222        return columns;
223    }
224
225    private List<HeaderField> savedColumns() {
226        return columnSelect.getSelectedItems();
227    }
228
229    private RpcDataSource getDataSource() {
230        GroupingType groupingType = getActiveGrouping();
231        if (groupingType == GroupingType.NO_GROUPING) {
232            return testDataSource;
233        } else if (groupingType == GroupingType.TEST_GROUPING) {
234            groupDataSource = TestGroupDataSource.getTestGroupDataSource();
235        } else {
236            groupDataSource = TestGroupDataSource.getStatusCountDataSource();
237        }
238
239        updateGroupColumns();
240        return groupDataSource;
241    }
242
243    private void updateStateFromView() {
244        commonPanel.updateStateFromView();
245        columnSelect.updateStateFromView();
246    }
247
248    private void updateViewFromState() {
249        commonPanel.updateViewFromState();
250        columnSelect.updateViewFromState();
251    }
252
253    private void updateGroupColumns() {
254        List<String> groupFields = new ArrayList<String>();
255        for (HeaderField field : savedColumns()) {
256            if (!isGroupField(field)) {
257                groupFields.add(field.getSqlName());
258            }
259        }
260
261        groupDataSource.setGroupColumns(groupFields.toArray(new String[0]));
262    }
263
264    private boolean isGroupField(HeaderField field) {
265        return field instanceof GroupCountField;
266    }
267
268    private void saveTableSorting() {
269        if (table != null) {
270            // we need our own copy so we can modify it later
271            tableSorts = new ArrayList<SortSpec>(table.getSortSpecs());
272        }
273    }
274
275    private void restoreTableSorting() {
276        for (ListIterator<SortSpec> i = tableSorts.listIterator(tableSorts.size());
277             i.hasPrevious();) {
278            SortSpec sortSpec = i.previous();
279            table.sortOnColumn(sortSpec.getField(), sortSpec.getDirection());
280        }
281    }
282
283    private void cleanupSortsForNewColumns() {
284        // remove sorts on columns that we no longer have
285        for (Iterator<SortSpec> i = tableSorts.iterator(); i.hasNext();) {
286            String attribute = i.next().getField();
287            if (!isAttributeSelected(attribute)) {
288                i.remove();
289            }
290        }
291
292        if (tableSorts.isEmpty()) {
293            // default to sorting on the first column
294            HeaderField field = savedColumns().iterator().next();
295            SortSpec sortSpec = new SortSpec(field.getSqlName(), SortDirection.ASCENDING);
296            tableSorts = new ArrayList<SortSpec>();
297            tableSorts.add(sortSpec);
298        }
299    }
300
301    private boolean isAttributeSelected(String attribute) {
302        for (HeaderField field : savedColumns()) {
303            if (field.getSqlName().equals(attribute)) {
304                return true;
305            }
306        }
307        return false;
308    }
309
310    @Override
311    public void refresh() {
312        createTable();
313        JSONObject condition = commonPanel.getConditionArgs();
314        sqlConditionFilter.setAllParameters(condition);
315        table.refresh();
316    }
317
318    @Override
319    public void doQuery() {
320        if (savedColumns().isEmpty()) {
321            NotifyManager.getInstance().showError("You must select columns");
322            return;
323        }
324        updateStateFromView();
325        refresh();
326    }
327
328    @Override
329    public void onRowClicked(int rowIndex, JSONObject row, boolean isRightClick) {
330        Event event = Event.getCurrentEvent();
331        TestSet testSet = getTestSet(row);
332        if (isRightClick) {
333            if (selectionManager.getSelectedObjects().size() > 0) {
334                testSet = getTestSet(selectionManager.getSelectedObjects());
335            }
336            ContextMenu menu = getContextMenu(testSet);
337            menu.showAtWindow(event.getClientX(), event.getClientY());
338            return;
339        }
340
341        if (isSelectEvent(event)) {
342            selectionManager.toggleSelected(row);
343            return;
344        }
345
346        HistoryToken historyToken;
347        if (isAnyGroupingEnabled()) {
348            historyToken = getDrilldownHistoryToken(testSet);
349        } else {
350            historyToken = listener.getSelectTestHistoryToken(testSet.getTestIndex());
351        }
352        openHistoryToken(historyToken);
353    }
354
355    private ContextMenu getContextMenu(final TestSet testSet) {
356        TestContextMenu menu = new TestContextMenu(testSet, listener);
357
358        if (!menu.addViewDetailsIfSingleTest() && isAnyGroupingEnabled()) {
359            menu.addItem("Drill down", new Command() {
360                public void execute() {
361                    doDrilldown(testSet);
362                }
363            });
364        }
365
366        menu.addLabelItems();
367        return menu;
368    }
369
370    private HistoryToken getDrilldownHistoryToken(TestSet testSet) {
371        saveHistoryState();
372        commonPanel.refineCondition(testSet);
373        selectColumnsByName(DEFAULT_COLUMNS);
374        HistoryToken historyToken = getHistoryArguments();
375        restoreHistoryState();
376        return historyToken;
377    }
378
379    private void doDrilldown(TestSet testSet) {
380        History.newItem(getDrilldownHistoryToken(testSet).toString());
381    }
382
383    private TestSet getTestSet(JSONObject row) {
384        if (!isAnyGroupingEnabled()) {
385            return new SingleTestSet((int) row.get("test_idx").isNumber().doubleValue());
386        }
387
388        ConditionTestSet testSet = new ConditionTestSet(commonPanel.getConditionArgs());
389        for (HeaderField field : savedColumns()) {
390            if (isGroupField(field)) {
391                continue;
392            }
393
394            String value = Utils.jsonToString(row.get(field.getSqlName()));
395            testSet.addCondition(field.getSqlCondition(value));
396        }
397        return testSet;
398    }
399
400    private TestSet getTestSet(Collection<JSONObject> selectedObjects) {
401        CompositeTestSet compositeSet = new CompositeTestSet();
402        for (JSONObject row : selectedObjects) {
403            compositeSet.add(getTestSet(row));
404        }
405        return compositeSet;
406    }
407
408    public void onTableRefreshed() {
409        selectionManager.refreshSelection();
410        saveTableSorting();
411        updateHistory();
412    }
413
414    private void setCheckboxesEnabled() {
415        assert !(groupCheckbox.getValue() && statusGroupCheckbox.getValue());
416
417        groupCheckbox.setEnabled(true);
418        statusGroupCheckbox.setEnabled(true);
419        if (groupCheckbox.getValue()) {
420            statusGroupCheckbox.setEnabled(false);
421        } else if (statusGroupCheckbox.getValue()) {
422            groupCheckbox.setEnabled(false);
423        }
424    }
425
426    private void updateFieldsFromCheckboxes() {
427        columnSelect.deselectItemInView(groupCountField);
428        columnSelect.deselectItemInView(statusCountsField);
429
430        if (groupCheckbox.getValue()) {
431            columnSelect.selectItemInView(groupCountField);
432        } else if (statusGroupCheckbox.getValue()) {
433            columnSelect.selectItemInView(statusCountsField);
434        }
435    }
436
437    private void updateCheckboxesFromFields() {
438        groupCheckbox.setValue(false);
439        statusGroupCheckbox.setValue(false);
440
441        GroupingType grouping = getGroupingFromFields(
442            columnSelect.getStateFromView().getSelectedFields());
443        if (grouping == GroupingType.TEST_GROUPING) {
444            groupCheckbox.setValue(true);
445        } else if (grouping == GroupingType.STATUS_COUNTS) {
446            statusGroupCheckbox.setValue(true);
447        }
448
449        setCheckboxesEnabled();
450    }
451
452    public ContextMenu getActionMenu() {
453        TestSet tests;
454        if (selectionManager.isEmpty()) {
455            tests = getWholeTableSet();
456        } else {
457            tests = getTestSet(selectionManager.getSelectedObjects());
458        }
459        return getContextMenu(tests);
460    }
461
462    private ConditionTestSet getWholeTableSet() {
463        return new ConditionTestSet(commonPanel.getConditionArgs());
464    }
465
466    @Override
467    public HistoryToken getHistoryArguments() {
468        HistoryToken arguments = super.getHistoryArguments();
469        if (table != null) {
470            columnSelect.addHistoryArguments(arguments, "columns");
471            arguments.put("sort", Utils.joinStrings(",", tableSorts));
472            commonPanel.addHistoryArguments(arguments);
473        }
474        return arguments;
475    }
476
477    @Override
478    public void handleHistoryArguments(Map<String, String> arguments) {
479        super.handleHistoryArguments(arguments);
480        columnSelect.handleHistoryArguments(arguments, "columns");
481        handleSortString(arguments.get("sort"));
482        updateViewFromState();
483    }
484
485    @Override
486    protected void fillDefaultHistoryValues(Map<String, String> arguments) {
487        HeaderField defaultSortField = headerFields.getFieldByName(DEFAULT_COLUMNS[0]);
488        Utils.setDefaultValue(arguments, "sort", defaultSortField.getSqlName());
489        Utils.setDefaultValue(arguments, "columns",
490                        Utils.joinStrings(",", Arrays.asList(DEFAULT_COLUMNS)));
491    }
492
493    private void handleSortString(String sortString) {
494        tableSorts.clear();
495        String[] components = sortString.split(",");
496        for (String component : components) {
497            tableSorts.add(SortSpec.fromString(component));
498        }
499    }
500
501    public void onClick(ClickEvent event) {
502        if (event.getSource() == queryButton) {
503            doQueryWithCommonPanelCheck();
504            updateHistory();
505        } else if (event.getSource() == groupCheckbox || event.getSource() == statusGroupCheckbox) {
506            updateFieldsFromCheckboxes();
507            setCheckboxesEnabled();
508        }
509    }
510
511    @Override
512    public void onRemoveGeneratedItem(Item generatedItem) {
513        updateCheckboxesFromFields();
514    }
515
516    private boolean isAnyGroupingEnabled() {
517        return getActiveGrouping() != GroupingType.NO_GROUPING;
518    }
519
520    private GroupingType getGroupingFromFields(List<HeaderField> fields) {
521        for (HeaderField field : fields) {
522            if (field.getName().equals(COUNT_NAME)) {
523                return GroupingType.TEST_GROUPING;
524            }
525            if (field.getName().equals(STATUS_COUNTS_NAME)) {
526                return GroupingType.STATUS_COUNTS;
527            }
528        }
529        return GroupingType.NO_GROUPING;
530    }
531
532    /**
533     * Get grouping currently active for displayed table.
534     */
535    private GroupingType getActiveGrouping() {
536        return getGroupingFromFields(savedColumns());
537    }
538
539    public Widget createWidget(int row, int cell, JSONObject rowObject) {
540        assert getActiveGrouping() == GroupingType.STATUS_COUNTS;
541        StatusSummary statusSummary = StatusSummary.getStatusSummary(
542            rowObject,
543            TestGroupDataSource.PASS_COUNT_FIELD,
544            TestGroupDataSource.COMPLETE_COUNT_FIELD,
545            TestGroupDataSource.INCOMPLETE_COUNT_FIELD,
546            TestGroupDataSource.GROUP_COUNT_FIELD);
547        SimplePanel panel = new SimplePanel();
548        panel.add(new HTML(statusSummary.formatContents()));
549        panel.getElement().addClassName(statusSummary.getCssClass());
550        return panel;
551    }
552
553    @Override
554    protected boolean hasFirstQueryOccurred() {
555        return table != null;
556    }
557
558    @Override
559    public void onSetControlsVisible(boolean visible) {
560        TkoUtils.setElementVisible("table_all_controls", visible);
561    }
562
563    @Override
564    public void onFieldsChanged() {
565        columnSelect.refreshFields();
566    }
567
568    public void onExportCsv() {
569        JSONObject extraParams = new JSONObject();
570        extraParams.put("columns", buildCsvColumnSpecs());
571        TkoUtils.doCsvRequest((RpcDataSource) table.getDataSource(), table.getCurrentQuery(),
572                              extraParams);
573    }
574
575    private JSONArray buildCsvColumnSpecs() {
576        String[][] columnSpecs = buildColumnSpecs();
577        JSONArray jsonColumnSpecs = new JSONArray();
578        for (String[] columnSpec : columnSpecs) {
579            JSONArray jsonColumnSpec = Utils.stringsToJSON(Arrays.asList(columnSpec));
580            jsonColumnSpecs.set(jsonColumnSpecs.size(), jsonColumnSpec);
581        }
582        return jsonColumnSpecs;
583    }
584}
585