From ea6f3d31d1753246f9e57dd1b90eaddd6f0dc988 Mon Sep 17 00:00:00 2001 From: Stepan Ermakov Date: Tue, 5 Dec 2023 08:02:21 +0300 Subject: [PATCH] "Export to CSV" functionality for main tables in oVirt Administration Portal Signed-off-by: Stepan Ermakov This feature adds new menu item "Export to CSV" for the following tables of oVirt Administration Portal Compute * Virtual Machines * Templates * Pools * Hosts * Data Centers Network * Networks Storage * Domains * Volumes * Disks Events The menu item allows to export current content (taking in to account current localization, columns visibility, sort order, filter) of the selected table into CSV. And initiates automatic download of the created CSV file. Known limitation: not more than 10,000 can be exported. --- .../ui/common/CommonApplicationConstants.java | 2 + .../model/SearchableTableModelProvider.java | 7 + .../widget/table/SimpleActionTable.java | 16 +- .../common/widget/table/TableCsvExporter.java | 258 ++++++++++++++++++ .../CommonApplicationConstants.properties | 1 + .../widget/table/TableCsvExporterTest.java | 221 +++++++++++++++ .../gin/uicommon/DataCenterModule.java | 5 + .../ui/webadmin/gin/uicommon/DiskModule.java | 5 + .../ui/webadmin/gin/uicommon/EventModule.java | 5 + .../ui/webadmin/gin/uicommon/HostModule.java | 5 + .../webadmin/gin/uicommon/NetworkModule.java | 4 + .../ui/webadmin/gin/uicommon/PoolModule.java | 5 + .../webadmin/gin/uicommon/StorageModule.java | 5 + .../webadmin/gin/uicommon/TemplateModule.java | 5 + .../gin/uicommon/VirtualMachineModule.java | 5 + .../webadmin/gin/uicommon/VolumeModule.java | 5 + .../uicommon/model/EventModelProvider.java | 5 + 17 files changed, 557 insertions(+), 2 deletions(-) create mode 100644 frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/TableCsvExporter.java create mode 100644 frontend/webadmin/modules/gwt-common/src/test/java/org/ovirt/engine/ui/common/widget/table/TableCsvExporterTest.java diff --git a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/CommonApplicationConstants.java b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/CommonApplicationConstants.java index 20b8cd6da1f..d7190ad1f37 100644 --- a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/CommonApplicationConstants.java +++ b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/CommonApplicationConstants.java @@ -16,6 +16,8 @@ public interface CommonApplicationConstants extends Constants { String emailUser(); + String exportCsv(); + @DefaultStringValue("") // Use annotation and not a properties key to leave it out of translations String empty(); diff --git a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/uicommon/model/SearchableTableModelProvider.java b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/uicommon/model/SearchableTableModelProvider.java index a23feceb083..76176ce5ba9 100644 --- a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/uicommon/model/SearchableTableModelProvider.java +++ b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/uicommon/model/SearchableTableModelProvider.java @@ -12,4 +12,11 @@ * List model type. */ public interface SearchableTableModelProvider extends SearchableModelProvider, ActionTableDataProvider { + /** + * Returns base file name for exported CSV content. Or null if CSV export is not supported by the table + * @return base file name or null + */ + default String csvExportFilenameBase() { + return null; + } } diff --git a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/SimpleActionTable.java b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/SimpleActionTable.java index 463a6881f0c..6befc0d0337 100644 --- a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/SimpleActionTable.java +++ b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/SimpleActionTable.java @@ -85,11 +85,11 @@ public SimpleActionTable(final SearchableTableModelProvider dataProvider, setLoadingState(LoadingState.LOADING); }); - createActionKebab(); + createActionKebab(dataProvider); showActionKebab(); } - private void createActionKebab() { + private void createActionKebab(SearchableTableModelProvider dataProvider) { ActionButton changeBtn = new ActionAnchorListItem(constants.changeColumnsVisibilityOrder()); changeBtn.addClickHandler(event -> showColumnModificationDialog(event)); actionKebab.addMenuItem(changeBtn); @@ -97,6 +97,13 @@ private void createActionKebab() { ActionButton resetBtn = new ActionAnchorListItem(constants.resetGridSettings()); resetBtn.addClickHandler(event -> resetGridSettings()); actionKebab.addMenuItem(resetBtn); + + String csvFilenameBase = dataProvider.csvExportFilenameBase(); + if (csvFilenameBase != null) { + ActionButton csvExportBtn = new ActionAnchorListItem(constants.exportCsv()); + csvExportBtn.addClickHandler(event -> exportCsv(csvFilenameBase)); + actionKebab.addMenuItem(csvExportBtn); + } } private void showColumnModificationDialog(ClickEvent event) { @@ -108,6 +115,11 @@ private void resetGridSettings() { table.resetGridSettings(); } + private void exportCsv(String filenameBase) { + TableCsvExporter csvExporter = new TableCsvExporter<>(filenameBase, getDataProvider(), table); + csvExporter.generateCsv(); + } + public void showActionKebab() { actionKebab.setVisible(true); } diff --git a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/TableCsvExporter.java b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/TableCsvExporter.java new file mode 100644 index 00000000000..28808575c9e --- /dev/null +++ b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/TableCsvExporter.java @@ -0,0 +1,258 @@ +package org.ovirt.engine.ui.common.widget.table; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +import org.ovirt.engine.core.common.businessentities.BusinessEntityWithStatus; +import org.ovirt.engine.ui.common.uicommon.model.SearchableTableModelProvider; +import org.ovirt.engine.ui.common.widget.table.column.AbstractColumn; +import org.ovirt.engine.ui.uicommonweb.models.ListModel; +import org.ovirt.engine.ui.uicompat.EnumTranslator; +import org.ovirt.engine.ui.uicompat.Event; +import org.ovirt.engine.ui.uicompat.EventArgs; +import org.ovirt.engine.ui.uicompat.IEventListener; + +import com.google.gwt.i18n.client.DateTimeFormat; +import com.google.gwt.safehtml.shared.SafeHtml; +import com.google.gwt.user.cellview.client.AbstractCellTable; +import com.google.gwt.user.cellview.client.Column; +import com.google.gwt.user.cellview.client.Header; + +/** + * A utility class that allows to export content of any {@link ActionCellTable} to CSV + *

+ * This class allows to export content of any {@link ActionCellTable} with {@link SearchableTableModelProvider} to CSV: + *

    + *
  • It takes into account current columns visibility. Only visible columns are exported
  • + *
  • Current sorting configuration would be applied to the exported content
  • + *
  • Current filtering configuration would be applied to the exported content
  • + *
  • It generates exported CSV file name in the following way: filenameBase.currentDateAndTime.csv, where the + * filenameBase is provided by {@link SearchableTableModelProvider}, see csvExportFilenameBase method; + * currentDateAndTime is the current date and time in the yyyy-MM-dd.HH-mm format
  • + *
  • It initiates automatic download of the generated CSV file
  • + *
  • The generated CSV file is limited by 10000 rows
  • + *
+ * @param + * Table row data type. + **/ +public class TableCsvExporter { + private static final int LINES_LIMIT = 10000; + private static final String HTML_TAG_PATTERN = "<[^>]*>"; //$NON-NLS-1$ + private static final String EMPTY = ""; //$NON-NLS-1$ + private static final char SPACE = ' '; //$NON-NLS-1$ + private static final char NEW_LINE = '\n'; //$NON-NLS-1$ + private static final char SEPARATOR = ','; //$NON-NLS-1$ + private static final char DOT = '.'; //$NON-NLS-1$ + private static final char SINGLE_QUOTE = '\''; //$NON-NLS-1$ + private static final char DOUBLE_QUOTE = '"'; //$NON-NLS-1$ + private static final String DOUBLE_QUOTE_STR = "\""; //$NON-NLS-1$ + private static final String DOUBLE_DOUBLE_QUOTE_STR = "\"\""; //$NON-NLS-1$ + private static final String FILE_EXT = ".csv"; //$NON-NLS-1$ + private static final String FILE_CURRENT_DATE_AND_TIME_FORMAT = "yyyy-MM-dd.HH-mm"; //$NON-NLS-1$ + + private final String filenameBase; + private final SearchableTableModelProvider modelProvider; + private final AbstractCellTable table; + private final ColumnController columnController; + private final boolean testMode; + private final StringBuilder csv; + private int pageOffset; + private int linesExported = -1; + + public TableCsvExporter(String filenameBase, SearchableTableModelProvider modelProvider, ActionCellTable table) { + this(filenameBase, modelProvider, table, table); + } + + TableCsvExporter(String filenameBase, SearchableTableModelProvider modelProvider, AbstractCellTable table, ColumnController columnController) { + this.filenameBase = filenameBase; + this.modelProvider = modelProvider; + this.table = table; + this.columnController = columnController; + this.csv = new StringBuilder(); + this.pageOffset = 0; + this.testMode = table != columnController; // For unit tests + } + + public void generateCsv() { + // Header + int colCount = table.getColumnCount(); + List> columns = new ArrayList<>(); + boolean firstInLine = true; + for (int i = 0; i < colCount; i++) { + Column col = table.getColumn(i); + if (columnController.isColumnVisible(col) && + col instanceof AbstractColumn) { + String colName = ((AbstractColumn) col).getContextMenuTitle(); + if (colName == null || colName.isEmpty()) { + Header header = table.getHeader(i); + colName = csvValue(header.getValue()); + } + if (colName != null && !colName.isEmpty()) { + columns.add((AbstractColumn) col); + firstInLine = appendItem(firstInLine, colName); + } + } + } + newLine(); + + // Content + // Note that in order to export content of the table we need to scroll to the first page, then export the content + // by moving forward page by page till the end (or till the 10000 rows limit is reached). And then return to the + // page where the export functionality was initiated. + ListModel model = modelProvider.getModel(); + Event itemsChangedEvent = model.getItemsChangedEvent(); + if (modelProvider.canGoBack()) { + // If we are not on the first page then let's move to the first page + itemsChangedEvent.addListener(new IEventListener() { + @Override + public void eventRaised(Event ev, Object sender, EventArgs args) { + if (modelProvider.canGoBack()) { + // We are still not on the first page + pageOffset--; + modelProvider.goBack(); + } else { + // The first page was reached. Let's generate the CSV file + itemsChangedEvent.removeListener(this); + generateContent(columns); + } + } + }); + pageOffset--; + modelProvider.goBack(); + } else { + // We are on the first page already. Let's generate the CSV file + generateContent(columns); + } + } + + private void generateContent(List> columns) { + ListModel model = modelProvider.getModel(); + // Export current page to CSV ... + generatePage(columns, model.getItems()); + if (hasMoreData()) { + // ... and then move to the next page if any + Event itemsChangedEvent = model.getItemsChangedEvent(); + itemsChangedEvent.addListener(new IEventListener() { + @Override + public void eventRaised(Event ev, Object sender, EventArgs args) { + // When the next page was loaded continue the export + itemsChangedEvent.removeListener(this); + generateContent(columns); + } + }); + pageOffset++; + modelProvider.goForward(); + } else { + // All the content was exported, so move to the initial page and initiate download of the exported CSV file + restorePageAndFinish(); + } + } + + private void restorePageAndFinish() { + // Before initiating the download of the exported content we want to return to the initial page of the table + if (pageOffset > 0 && modelProvider.canGoBack()) { + // We still are not on the initial page. Let move towards it + ListModel model = modelProvider.getModel(); + Event itemsChangedEvent = model.getItemsChangedEvent(); + itemsChangedEvent.addListener(new IEventListener() { + @Override + public void eventRaised(Event ev, Object sender, EventArgs args) { + itemsChangedEvent.removeListener(this); + restorePageAndFinish(); + } + }); + pageOffset--; + modelProvider.goBack(); + } else { + // We reached the initial page, let's initiate automatic download of the generated CSV file + if (!testMode) { // disabled for unit tests + downloadCsv(getFileName(), getGeneratedCsv()); + } + } + } + + private void generatePage(List> columns, Collection items) { + boolean firstInLine = true; + for (T item : items) { + for (AbstractColumn col : columns) { + String cellValue = csvValue(col.getValue(item)); + if (cellValue == null || cellValue.isEmpty()) { + cellValue = csvValue(col.getTooltip(item)); + } + firstInLine = appendItem(firstInLine, cellValue); + } + firstInLine = newLine(); + } + } + + private boolean hasMoreData() { + return modelProvider.canGoForward() && linesExported < LINES_LIMIT; + } + + private String csvValue(Object tableValue) { + String result = null; + if (tableValue instanceof String) { + result = (String) tableValue; + } else if (tableValue instanceof SafeHtml) { + result = ((SafeHtml) tableValue).asString(); + } else if (tableValue instanceof BusinessEntityWithStatus) { + result = translateEnum(((BusinessEntityWithStatus) tableValue).getStatus()); + } + + if (result != null) { + // Sometimes content of a cell contains images (encoded in HTML tags). Let's remove the images. Just leave a + // text of the cell + result = result.replaceAll(HTML_TAG_PATTERN, EMPTY).trim(); + } + + return result; + } + + private String translateEnum(Enum key) { + return testMode ? key.name() : EnumTranslator.getInstance().translate(key); + } + + private boolean appendItem(boolean firstInLine, String item) { + if (!firstInLine) { + csv.append(SEPARATOR); + } + if (item != null) { + csv.append(escapeSpecialCharacters(item)); + } + return false; + } + + private boolean newLine() { + csv.append(NEW_LINE); + linesExported++; + return true; + } + + private String escapeSpecialCharacters(String data) { + String escapedData = data.replace(NEW_LINE, SPACE); + if (escapedData.indexOf(SEPARATOR) >= 0 || + escapedData.indexOf(SINGLE_QUOTE) >= 0 || + escapedData.indexOf(DOUBLE_QUOTE) >= 0) { + escapedData = DOUBLE_QUOTE + escapedData.replace(DOUBLE_QUOTE_STR, DOUBLE_DOUBLE_QUOTE_STR) + DOUBLE_QUOTE; + } + return escapedData; + } + + String getFileName() { + String dt = DateTimeFormat.getFormat(FILE_CURRENT_DATE_AND_TIME_FORMAT).format(new Date()); //$NON-NLS-1$ + return filenameBase + DOT + dt + FILE_EXT; + } + String getGeneratedCsv() { + return csv.toString(); + } + + private native void downloadCsv(String filename, String text)/*-{ + var pom = document.createElement('a'); + pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); + pom.setAttribute('download', filename); + document.body.appendChild(pom); + pom.click(); + document.body.removeChild(pom); }-*/; +} diff --git a/frontend/webadmin/modules/gwt-common/src/main/resources/org/ovirt/engine/ui/common/CommonApplicationConstants.properties b/frontend/webadmin/modules/gwt-common/src/main/resources/org/ovirt/engine/ui/common/CommonApplicationConstants.properties index e890eb57c08..bd6e3b6c9d0 100644 --- a/frontend/webadmin/modules/gwt-common/src/main/resources/org/ovirt/engine/ui/common/CommonApplicationConstants.properties +++ b/frontend/webadmin/modules/gwt-common/src/main/resources/org/ovirt/engine/ui/common/CommonApplicationConstants.properties @@ -980,6 +980,7 @@ ppcChipset=pseries s390xChipset=zseries resetGridSettings=Reset settings changeColumnsVisibilityOrder=Change columns visibility/order +exportCsv=Export to CSV typeToSearchPlaceHolder=Type to search configChangesPending=Configuration changes may be pending. Unplug and replug to apply. permissionFilter=Permission Filters diff --git a/frontend/webadmin/modules/gwt-common/src/test/java/org/ovirt/engine/ui/common/widget/table/TableCsvExporterTest.java b/frontend/webadmin/modules/gwt-common/src/test/java/org/ovirt/engine/ui/common/widget/table/TableCsvExporterTest.java new file mode 100644 index 00000000000..f6d66cae304 --- /dev/null +++ b/frontend/webadmin/modules/gwt-common/src/test/java/org/ovirt/engine/ui/common/widget/table/TableCsvExporterTest.java @@ -0,0 +1,221 @@ +package org.ovirt.engine.ui.common.widget.table; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.ovirt.engine.core.common.businessentities.VM; +import org.ovirt.engine.core.common.businessentities.VMStatus; +import org.ovirt.engine.ui.common.uicommon.model.SearchableTableModelProvider; +import org.ovirt.engine.ui.common.widget.table.column.AbstractColumn; +import org.ovirt.engine.ui.uicommonweb.models.SearchableListModel; +import org.ovirt.engine.ui.uicompat.Event; +import org.ovirt.engine.ui.uicompat.EventArgs; + +import com.google.gwt.junit.GWTMockUtilities; +import com.google.gwt.safehtml.shared.SafeHtml; +import com.google.gwt.user.cellview.client.AbstractCellTable; +import com.google.gwt.user.cellview.client.Column; +import com.google.gwt.user.cellview.client.Header; + +@ExtendWith(MockitoExtension.class) +public class TableCsvExporterTest { + private final List> headers = new ArrayList<>(); + private final List> columns = new ArrayList<>(); + private final List> visibleColumns = new ArrayList<>(); + private final List> pages = new ArrayList<>(); + + private final Event itemsChangedEvent = new Event<>("Name", TableCsvExporterTest.class); //$NON-NLS-1$ + + private int currentPage; + + private TableCsvExporter exporter; + + @BeforeEach + public void init() { + GWTMockUtilities.disarm(); + SearchableTableModelProvider modelProvider = mock(SearchableTableModelProvider.class); + AbstractCellTable table = mock(AbstractCellTable.class); + SearchableListModel model = mock(SearchableListModel.class); + ColumnController columnController = mock(ColumnController.class); + + AbstractColumn statusIconCol = mockColumn(null); + when(statusIconCol.getContextMenuTitle()).thenReturn("Status Icon"); //$NON-NLS-1$ + lenient().when(statusIconCol.getValue(any(VM.class))).thenReturn(null); + lenient().when(statusIconCol.getTooltip(any(VM.class))).thenAnswer(invocationOnMock -> { + VM vm = invocationOnMock.getArgument(0, VM.class); + return mockHtml("
" + vm.getStatus().name() + "
"); //$NON-NLS-1$ //$NON-NLS-2$ + }); + AbstractColumn nameCol = mockColumn("Name"); //$NON-NLS-1$ + lenient().when(nameCol.getValue(any(VM.class))).thenAnswer(invocationOnMock -> { + VM vm = invocationOnMock.getArgument(0, VM.class); + return vm.getName(); + }); + AbstractColumn descriptionCol = mockColumn("Description"); //$NON-NLS-1$ + lenient().when(descriptionCol.getValue(any(VM.class))).thenAnswer(invocationOnMock -> { + VM vm = invocationOnMock.getArgument(0, VM.class); + return vm.getDescription(); + }); + AbstractColumn statusCol = mockColumn("Status"); //$NON-NLS-1$ + lenient().when(statusCol.getValue(any(VM.class))).thenAnswer(invocationOnMock -> invocationOnMock.getArgument(0, VM.class)); + + when(table.getColumnCount()).thenReturn(columns.size()); + when(table.getColumn(anyInt())).thenAnswer(invocationOnMock -> { + int index = invocationOnMock.getArgument(0, Integer.class); + return columns.get(index); + }); + when(columnController.isColumnVisible(any())).thenAnswer(invocationOnMock -> { + Column column = invocationOnMock.getArgument(0, Column.class); + return visibleColumns.contains(column); + }); + when(table.getHeader(anyInt())).thenAnswer(invocationOnMock -> { + int index = invocationOnMock.getArgument(0, Integer.class); + return headers.get(index); + }); + + when(modelProvider.getModel()).thenReturn(model); + when(model.getItemsChangedEvent()).thenReturn(itemsChangedEvent); + when(model.getItems()).thenAnswer(invocationOnMock -> { + if (currentPage < 0 || currentPage >= pages.size()) { + return Collections.emptyList(); + } + return pages.get(currentPage); + }); + when(modelProvider.canGoBack()).thenAnswer(invocationOnMock -> currentPage > 0); + when(modelProvider.canGoForward()).thenAnswer(invocationOnMock -> currentPage < pages.size() - 1); + lenient().doAnswer(invocationOnMock -> { + currentPage--; + lenient().when(model.getItems()).thenReturn(pages.get(currentPage)); + itemsChangedEvent.raise(null, null); + return null; + }).when(modelProvider).goBack(); + lenient().doAnswer(invocationOnMock -> { + currentPage++; + lenient().when(model.getItems()).thenReturn(pages.get(currentPage)); + itemsChangedEvent.raise(null, null); + return null; + }).when(modelProvider).goForward(); + + exporter = new TableCsvExporter<>("vms", modelProvider, table, columnController); //$NON-NLS-1$ + } + + @AfterEach + public void tearDown() { + GWTMockUtilities.restore(); + } + + private AbstractColumn mockColumn(String header) { + AbstractColumn column = mock(AbstractColumn.class); + columns.add(column); + visibleColumns.add(column); + Header hdr = mock(Header.class); + if (header != null) { + lenient().when(hdr.getValue()).thenReturn(header); + } + headers.add(hdr); + return column; + } + + private VM mockItem(VMStatus status, String name, String description) { + VM item = mock(VM.class); + lenient().when(item.getStatus()).thenReturn(status); + lenient().when(item.getName()).thenReturn(name); + lenient().when(item.getDescription()).thenReturn(description); + return item; + } + + private SafeHtml mockHtml(String html) { + SafeHtml result = mock(SafeHtml.class); + when(result.asString()).thenReturn(html); + return result; + } + + private void mockPage() { + pages.add(Arrays.asList( + mockItem(VMStatus.Down, "vm1", " descr1 with \n spec '\" symbols "), //$NON-NLS-1$ //$NON-NLS-2$ + mockItem(VMStatus.Up, "vm2", ""), //$NON-NLS-1$ //$NON-NLS-2$ + mockItem(VMStatus.Paused, "vm3", null)//$NON-NLS-1$ + )); + } + + @Test + public void exportEmptyTableTest() { + exporter.generateCsv(); + assertGeneratedCsv("Status Icon,Name,Description,Status\n"); //$NON-NLS-1$ + } + + @Test + public void exportOnePageTableTest() { + mockPage(); + exporter.generateCsv(); + assertGeneratedCsv("Status Icon,Name,Description,Status\n" + //$NON-NLS-1$ + "Down,vm1,\"descr1 with spec '\"\" symbols\",Down\n" + //$NON-NLS-1$ + "Up,vm2,,Up\n" + //$NON-NLS-1$ + "Paused,vm3,,Paused\n"); //$NON-NLS-1$ + assertEquals(0, currentPage); + } + + @Test + public void exportMultiplePagesTableTest() { + mockPage(); + mockPage(); + exporter.generateCsv(); + assertGeneratedCsv("Status Icon,Name,Description,Status\n" + //$NON-NLS-1$ + "Down,vm1,\"descr1 with spec '\"\" symbols\",Down\n" + //$NON-NLS-1$ + "Up,vm2,,Up\n" + //$NON-NLS-1$ + "Paused,vm3,,Paused\n" + //$NON-NLS-1$ + "Down,vm1,\"descr1 with spec '\"\" symbols\",Down\n" + //$NON-NLS-1$ + "Up,vm2,,Up\n" + //$NON-NLS-1$ + "Paused,vm3,,Paused\n"); //$NON-NLS-1$ + assertEquals(0, currentPage); + } + + @Test + public void exportMultiplePagesTableFromNonFirtsPageTest() { + mockPage(); + mockPage(); + mockPage(); + currentPage = 2; + exporter.generateCsv(); + assertGeneratedCsv("Status Icon,Name,Description,Status\n" + //$NON-NLS-1$ + "Down,vm1,\"descr1 with spec '\"\" symbols\",Down\n" + //$NON-NLS-1$ + "Up,vm2,,Up\n" + //$NON-NLS-1$ + "Paused,vm3,,Paused\n" + //$NON-NLS-1$ + "Down,vm1,\"descr1 with spec '\"\" symbols\",Down\n" + //$NON-NLS-1$ + "Up,vm2,,Up\n" + //$NON-NLS-1$ + "Paused,vm3,,Paused\n" + //$NON-NLS-1$ + "Down,vm1,\"descr1 with spec '\"\" symbols\",Down\n" + //$NON-NLS-1$ + "Up,vm2,,Up\n" + //$NON-NLS-1$ + "Paused,vm3,,Paused\n"); //$NON-NLS-1$ + assertEquals(2, currentPage); + } + + @Test + public void exportTableWithInvisibleColumnsTest() { + visibleColumns.remove(2); + mockPage(); + exporter.generateCsv(); + assertGeneratedCsv("Status Icon,Name,Status\n" + //$NON-NLS-1$ + "Down,vm1,Down\n" + //$NON-NLS-1$ + "Up,vm2,Up\n" + //$NON-NLS-1$ + "Paused,vm3,Paused\n"); //$NON-NLS-1$ + assertEquals(0, currentPage); + } + + private void assertGeneratedCsv(String expectedCsv) { + assertEquals(expectedCsv, exporter.getGeneratedCsv()); + } +} diff --git a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/DataCenterModule.java b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/DataCenterModule.java index 3be8e09a1ea..62ed4b2f6ff 100644 --- a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/DataCenterModule.java +++ b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/DataCenterModule.java @@ -103,6 +103,11 @@ public MainModelProvider getDataCenterListProv return super.getConfirmModelPopup(source, lastExecutedCommand); } } + + @Override + public String csvExportFilenameBase() { + return "dataCenters"; //$NON-NLS-1$ + } }; result.setModelProvider(modelProvider); return result; diff --git a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/DiskModule.java b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/DiskModule.java index 92d52e0b6e3..699b6ab2cf0 100644 --- a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/DiskModule.java +++ b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/DiskModule.java @@ -87,6 +87,11 @@ public MainModelProvider getDiskListProvider(EventBus event return super.getConfirmModelPopup(source, lastExecutedCommand); } } + + @Override + public String csvExportFilenameBase() { + return "disks"; //$NON-NLS-1$ + } }; result.setModelProvider(modelProvider); return result; diff --git a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/EventModule.java b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/EventModule.java index 29b4279455f..1a278df1393 100644 --- a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/EventModule.java +++ b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/EventModule.java @@ -41,6 +41,11 @@ public MainModelProvider> getEventListProvider(Ev return super.getModelPopup(source, lastExecutedCommand, windowModel); } } + + @Override + public String csvExportFilenameBase() { + return "events"; //$NON-NLS-1$ + } }; result.setModelProvider(modelProvider); return result; diff --git a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/HostModule.java b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/HostModule.java index f601088fc04..c7606a75e05 100644 --- a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/HostModule.java +++ b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/HostModule.java @@ -138,6 +138,11 @@ public MainModelProvider> getHostListProvider(EventBus return super.getConfirmModelPopup(source, lastExecutedCommand); } } + + @Override + public String csvExportFilenameBase() { + return "hosts"; //$NON-NLS-1$ + } }; result.setModelProvider(modelProvider); return result; diff --git a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/NetworkModule.java b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/NetworkModule.java index 7d51aefdf26..e186e5a1d61 100644 --- a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/NetworkModule.java +++ b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/NetworkModule.java @@ -106,6 +106,10 @@ public MainModelProvider getNetworkListProvider(E } } + @Override + public String csvExportFilenameBase() { + return "networks"; //$NON-NLS-1$ + } }; result.setModelProvider(modelProvider); return result; diff --git a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/PoolModule.java b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/PoolModule.java index 6649d4a3500..b113104bc00 100644 --- a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/PoolModule.java +++ b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/PoolModule.java @@ -75,6 +75,11 @@ public MainModelProvider getPoolListProvider(EventBus eve return super.getConfirmModelPopup(source, lastExecutedCommand); } } + + @Override + public String csvExportFilenameBase() { + return "pools"; //$NON-NLS-1$ + } }; result.setModelProvider(modelProvider); return result; diff --git a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/StorageModule.java b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/StorageModule.java index 0e1a91def43..9bb77e7ded1 100644 --- a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/StorageModule.java +++ b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/StorageModule.java @@ -113,6 +113,11 @@ public MainModelProvider getStorageListProvider return super.getConfirmModelPopup(source, lastExecutedCommand); } } + + @Override + public String csvExportFilenameBase() { + return "storageDomains"; //$NON-NLS-1$ + } }; result.setModelProvider(modelProvider); return result; diff --git a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/TemplateModule.java b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/TemplateModule.java index bf5001cd3d6..200a8013cc6 100644 --- a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/TemplateModule.java +++ b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/TemplateModule.java @@ -132,6 +132,11 @@ public MainModelProvider getTemplateListProvider( return super.getConfirmModelPopup(source, lastExecutedCommand); } } + + @Override + public String csvExportFilenameBase() { + return "templates"; //$NON-NLS-1$ + } }; result.setModelProvider(modelProvider); return result; diff --git a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/VirtualMachineModule.java b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/VirtualMachineModule.java index 97a05201121..9ba8ca3e77b 100644 --- a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/VirtualMachineModule.java +++ b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/VirtualMachineModule.java @@ -194,6 +194,11 @@ public MainModelProvider> getVmListProvider(EventBus event return super.getConfirmModelPopup(source, lastExecutedCommand); } } + + @Override + public String csvExportFilenameBase() { + return "vms"; //$NON-NLS-1$ + } }; result.setModelProvider(modelProvider); return result; diff --git a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/VolumeModule.java b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/VolumeModule.java index 3c5bf4090df..f41197684b7 100644 --- a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/VolumeModule.java +++ b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/gin/uicommon/VolumeModule.java @@ -109,6 +109,11 @@ public MainModelProvider getVolumeListProv return super.getConfirmModelPopup(source, lastExecutedCommand); } } + + @Override + public String csvExportFilenameBase() { + return "volumes"; //$NON-NLS-1$ + } }; result.setModelProvider(modelProvider); return result; diff --git a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/uicommon/model/EventModelProvider.java b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/uicommon/model/EventModelProvider.java index a279c06d88c..80598b64607 100644 --- a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/uicommon/model/EventModelProvider.java +++ b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/uicommon/model/EventModelProvider.java @@ -34,4 +34,9 @@ protected void initializeModelHandlers(final EventListModel model) { protected boolean handleItemsChangedEvent() { return false; } + + @Override + public String csvExportFilenameBase() { + return "events"; //$NON-NLS-1$ + } }