1 package autotest.tko; 2 3 import autotest.common.StatusSummary; 4 import autotest.common.Utils; 5 import autotest.common.CustomHistory.HistoryToken; 6 import autotest.common.table.DataTable; 7 import autotest.common.table.DynamicTable; 8 import autotest.common.table.RpcDataSource; 9 import autotest.common.table.SelectionManager; 10 import autotest.common.table.SimpleFilter; 11 import autotest.common.table.TableDecorator; 12 import autotest.common.table.DataSource.SortDirection; 13 import autotest.common.table.DataSource.SortSpec; 14 import autotest.common.table.DataTable.TableWidgetFactory; 15 import autotest.common.table.DynamicTable.DynamicTableListener; 16 import autotest.common.ui.ContextMenu; 17 import autotest.common.ui.DoubleListSelector; 18 import autotest.common.ui.MultiListSelectPresenter; 19 import autotest.common.ui.NotifyManager; 20 import autotest.common.ui.MultiListSelectPresenter.Item; 21 import autotest.common.ui.TableActionsPanel.TableActionsWithExportCsvListener; 22 import autotest.tko.CommonPanel.CommonPanelListener; 23 24 import com.google.gwt.event.dom.client.ClickEvent; 25 import com.google.gwt.event.dom.client.ClickHandler; 26 import com.google.gwt.json.client.JSONArray; 27 import com.google.gwt.json.client.JSONObject; 28 import com.google.gwt.user.client.Command; 29 import com.google.gwt.user.client.Event; 30 import com.google.gwt.user.client.History; 31 import com.google.gwt.user.client.ui.Button; 32 import com.google.gwt.user.client.ui.CheckBox; 33 import com.google.gwt.user.client.ui.HTML; 34 import com.google.gwt.user.client.ui.Panel; 35 import com.google.gwt.user.client.ui.SimplePanel; 36 import com.google.gwt.user.client.ui.VerticalPanel; 37 import com.google.gwt.user.client.ui.Widget; 38 39 import java.util.ArrayList; 40 import java.util.Arrays; 41 import java.util.Collection; 42 import java.util.Iterator; 43 import java.util.List; 44 import java.util.ListIterator; 45 import java.util.Map; 46 47 public 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