From 9034a8be7aec7b7e43f3a50f711c561015da8d4f Mon Sep 17 00:00:00 2001 From: Lucas Hocker Date: Sun, 14 Nov 2021 15:19:32 -0600 Subject: [PATCH] feat(table): Added experimental export as CSV functionality. This can be used by providing the `showExportButton` input on the table component itself. --- apps/demos/src/app-routing.module.ts | 10 + apps/demos/src/nav-items.ts | 8 + .../drawer-table/src/drawer-table.spec.ts | 5 +- libs/barista-components/table/README.md | 36 +++- .../table/src/table-data-source.ts | 24 +++ .../table/src/table-module.ts | 2 + libs/barista-components/table/src/table.html | 25 ++- libs/barista-components/table/src/table.scss | 18 ++ libs/barista-components/table/src/table.ts | 196 +++++++++++++++++- libs/examples/src/index.ts | 5 + libs/examples/src/table/index.ts | 2 + .../src/table/table-examples.module.ts | 4 + .../table-export-example.html | 40 ++++ .../table-export-example.scss | 10 + .../table-export-example.ts | 186 +++++++++++++++++ .../table-export-selection-example.html | 25 +++ .../table-export-selection-example.scss | 3 + .../table-export-selection-example.ts | 118 +++++++++++ package-lock.json | 15 +- 19 files changed, 719 insertions(+), 13 deletions(-) create mode 100644 libs/examples/src/table/table-export-example/table-export-example.html create mode 100644 libs/examples/src/table/table-export-example/table-export-example.scss create mode 100644 libs/examples/src/table/table-export-example/table-export-example.ts create mode 100644 libs/examples/src/table/table-export-selection-example/table-export-selection-example.html create mode 100644 libs/examples/src/table/table-export-selection-example/table-export-selection-example.scss create mode 100644 libs/examples/src/table/table-export-selection-example/table-export-selection-example.ts diff --git a/apps/demos/src/app-routing.module.ts b/apps/demos/src/app-routing.module.ts index 57207f5b7a..cfd7499cbb 100644 --- a/apps/demos/src/app-routing.module.ts +++ b/apps/demos/src/app-routing.module.ts @@ -284,6 +284,8 @@ import { DtExampleTableDynamicColumns, DtExampleTableEmptyState, DtExampleTableExpandableRows, + DtExampleTableExport, + DtExampleTableExportSelection, DtExampleTableFavoriteColumn, DtExampleTableFavoriteColumnNoHeader, DtExampleTableInteractiveRows, @@ -1101,6 +1103,14 @@ const ROUTES: Routes = [ path: 'table-expandable-rows-example', component: DtExampleTableExpandableRows, }, + { + path: 'table-export-example', + component: DtExampleTableExport, + }, + { + path: 'table-export-selection-example', + component: DtExampleTableExportSelection, + }, { path: 'table-favorite-column-example', component: DtExampleTableFavoriteColumn, diff --git a/apps/demos/src/nav-items.ts b/apps/demos/src/nav-items.ts index 79f7e58da2..3ffad7c429 100644 --- a/apps/demos/src/nav-items.ts +++ b/apps/demos/src/nav-items.ts @@ -1376,6 +1376,14 @@ export const DT_DEMOS_EXAMPLE_NAV_ITEMS = [ name: 'table-expandable-rows-example', route: '/table-expandable-rows-example', }, + { + name: 'table-export-example', + route: '/table-export-example', + }, + { + name: 'table-export-selection-example', + route: '/table-export-selection-example', + }, { name: 'table-favorite-column-example', route: '/table-favorite-column-example', diff --git a/libs/barista-components/experimental/drawer-table/src/drawer-table.spec.ts b/libs/barista-components/experimental/drawer-table/src/drawer-table.spec.ts index fb150abe01..4f956f28dc 100644 --- a/libs/barista-components/experimental/drawer-table/src/drawer-table.spec.ts +++ b/libs/barista-components/experimental/drawer-table/src/drawer-table.spec.ts @@ -77,8 +77,9 @@ describe('DtDrawerTable', () => { }); it('should show the selected row in drawer', () => { - const lastRow = - fixture.debugElement.nativeElement.querySelector('dt-row:last-child'); + const lastRow = fixture.debugElement.nativeElement.querySelector( + 'dt-row:last-of-type', + ); lastRow.click(); fixture.detectChanges(); expect(component.drawerTable.isOpen).toBe(true); diff --git a/libs/barista-components/table/README.md b/libs/barista-components/table/README.md index d43cf6c554..8bf7868e06 100644 --- a/libs/barista-components/table/README.md +++ b/libs/barista-components/table/README.md @@ -40,11 +40,12 @@ class MyModule {} The `DtTable` component supports the following inputs. Find details about the usage of each input below. -| Name | Type | Default | Description | -| ------------- | -------------------------------------- | ------- | -------------------------------------------------------------------- | -| `dataSource` | `object[] \| Observable \| DataSource` | | Data to be shown in the table. | -| `loading` | `boolean` | `false` | Whether the table is [loading](#loading) or not. | -| `multiExpand` | `boolean` | `false` | Whether the table allows [multiple rows to be expanded]() at a time. | +| Name | Type | Default | Description | +| -------------- | -------------------------------------- | ------- | -------------------------------------------------------------------- | +| `dataSource` | `object[] \| Observable \| DataSource` | | Data to be shown in the table. | +| `loading` | `boolean` | `false` | Whether the table is [loading](#loading) or not. | +| `multiExpand` | `boolean` | `false` | Whether the table allows [multiple rows to be expanded]() at a time. | +| `exportButton` | `boolean` | `false` | Whether the table includes an export button. | ## Simple columns for basic use cases @@ -675,3 +676,28 @@ simpleColumn could look like this (example from the `dt-simple-number-column`). ``` + +## Exporting + +### Simple + +By setting the `showExportButton` input to `true`, an `dtContextDialog` button +is added just below the table, or in line with pagination if present. This +dialog contains at least 2 buttons: + +- **Export table data** which triggers a download of the currently filtered data + as shown, without regard for pagination. +- **Export visible data** which triggers a download of the filtered data from + the datasource and does not use a displayAccessor. + + + +### Selection + +If `dtTableSelection` is enabled and you have connected `dtTableSelection` to +`dtTableDataSource` (similar to `dtSort`), you also will have a third button: + +- **Export selected rows** which triggers a download of the display data, but + just for selected rows. + + diff --git a/libs/barista-components/table/src/table-data-source.ts b/libs/barista-components/table/src/table-data-source.ts index e4c13323f3..a7e2e63094 100644 --- a/libs/barista-components/table/src/table-data-source.ts +++ b/libs/barista-components/table/src/table-data-source.ts @@ -34,6 +34,7 @@ import { DtSimpleColumnSortAccessorFunction, } from './simple-columns'; import { DtSort, DtSortEvent } from './sort/sort'; +import { DtTableSelection } from './selection/selection'; import { DtTable } from './table'; export type DtSortAccessorFunction = (data: T) => any; // tslint:disable-line:no-any @@ -55,6 +56,17 @@ export class DtTableDataSource extends DataSource { */ filteredData: T[]; + /** + * Data structure to expose exportable data to the table. + */ + exporter: { + filteredData: T[]; + selection: DtTableSelection | null; + } = { + filteredData: [], + selection: null, + }; + /** @internal DisplayAccessorMap for SimpleColumn displayAccessor functions. */ _displayAccessorMap: Map> = new Map(); @@ -135,6 +147,16 @@ export class DtTableDataSource extends DataSource { } private _sort: DtSort | null; + /** + * Instance of the DtTableSelection directive used by the table to provide selected data. + */ + get selection(): DtTableSelection | null { + return this.exporter.selection; + } + set selection(selection: DtTableSelection | null) { + this.exporter.selection = selection; + } + /** * Instance of the DtTableSearch directive used by the table to control which * rows are displayed. Search changes emitted by the DtTableSearch will @@ -453,6 +475,8 @@ export class DtTableDataSource extends DataSource { this._simpleComparatorMap = comparatorMap; this._updateChangeSubscription(); }); + _table._filteredData = this.filteredData; + _table._exporter = this.exporter; return this._renderData; } diff --git a/libs/barista-components/table/src/table-module.ts b/libs/barista-components/table/src/table-module.ts index c0b89aac40..19309c145c 100644 --- a/libs/barista-components/table/src/table-module.ts +++ b/libs/barista-components/table/src/table-module.ts @@ -30,6 +30,7 @@ import { DtFormattersModule } from '@dynatrace/barista-components/formatters'; import { DtIconModule } from '@dynatrace/barista-components/icon'; import { DtInputModule } from '@dynatrace/barista-components/input'; import { DtCheckboxModule } from '@dynatrace/barista-components/checkbox'; +import { DtContextDialogModule } from '@dynatrace/barista-components/context-dialog'; import { DtCell, DtCellDef, DtColumnDef } from './cell'; import { @@ -106,6 +107,7 @@ const EXPORTED_DECLARATIONS = [ DtEmptyStateModule, ReactiveFormsModule, DtCheckboxModule, + DtContextDialogModule, ], exports: [...EXPORTED_DECLARATIONS, DtIndicatorModule], declarations: [...EXPORTED_DECLARATIONS], diff --git a/libs/barista-components/table/src/table.html b/libs/barista-components/table/src/table.html index 0bcae7418d..9cfa3e4eb0 100644 --- a/libs/barista-components/table/src/table.html +++ b/libs/barista-components/table/src/table.html @@ -1,13 +1,36 @@ + + + + + + > + diff --git a/libs/barista-components/table/src/table.scss b/libs/barista-components/table/src/table.scss index 3d807005de..96bcf4fa5d 100644 --- a/libs/barista-components/table/src/table.scss +++ b/libs/barista-components/table/src/table.scss @@ -29,3 +29,21 @@ .dt-table-search + :host { margin-top: 8px; } + +.dt-context-dialog { + position: relative; + margin-top: 14px; + margin-bottom: -46px; + display: flex; + justify-content: flex-end; + flex-direction: row; +} + +::ng-deep .dt-exportMenu { + display: flex; + flex-direction: column; +} + +::ng-deep .dt-exportMenu button { + margin-bottom: 8px; +} diff --git a/libs/barista-components/table/src/table.ts b/libs/barista-components/table/src/table.ts index 0a18aa18a4..2e7e981b7f 100644 --- a/libs/barista-components/table/src/table.ts +++ b/libs/barista-components/table/src/table.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; import { Platform } from '@angular/cdk/platform'; import { CdkPortalOutlet, TemplatePortal } from '@angular/cdk/portal'; import { DOCUMENT } from '@angular/common'; @@ -62,6 +62,7 @@ import { _COALESCED_STYLE_SCHEDULER, } from '@angular/cdk/table'; import { ViewportRuler } from '@angular/cdk/scrolling'; +import { DtTableSelection } from './selection/selection'; interface SimpleColumnsAccessorMaps { displayAccessorMap: Map>; @@ -95,6 +96,7 @@ export class DtTable extends _DtTableBase implements OnDestroy { private _multiExpand: boolean; // TODO: discuss default value with UX, should maybe change from false to true private _loading: boolean; private _destroy$ = new Subject(); + private _showExportButton: boolean = false; //Revert to opt-in instead of opt-out per request /** Sort accessor map that holds all sort accessor functions from the registered simple columns. */ private _sortAccessorMap = new Map< @@ -118,6 +120,32 @@ export class DtTable extends _DtTableBase implements OnDestroy { /** @internal Whether a expandable row is registered with the table */ _hasExpandableRows = false; + /** @internal Reference to filteredData in table-data-source */ + _filteredData: T[]; + + /** @internal Reference to exportable data. */ + _exporter: { + filteredData: T[]; + selection: DtTableSelection | null; + }; + + /** @internal Determine if selection is enabled and properly connected. */ + get _selectionEnabledAndConnected(): boolean { + if (this._exporter && this._exporter.selection) return true; + else return false; + } + + /** The number of rows that have been selected by the user. */ + get numSelectedRows(): number { + if ( + this._exporter && + this._exporter.selection != null && + this._exporter.selection.selected + ) + return this._exporter.selection.selected.length; + else return 0; + } + /** Whether the loading state should be displayed. */ @Input() get loading(): boolean { @@ -141,6 +169,20 @@ export class DtTable extends _DtTableBase implements OnDestroy { return !(this._data && this._data.length); } + /** + * Experimental! + * Opt-in to the possibility to export all table data, visible table data or + * selected table data into a csv file. + */ + @Input() + get showExportButton(): boolean { + return this._showExportButton; + } + set showExportButton(value: boolean) { + this._showExportButton = coerceBooleanProperty(value); + } + static ngAcceptInputType_showExportButton: BooleanInput; + /** @internal The snapshot of the current data */ get _dataSnapshot(): T[] | readonly T[] { return this._data; @@ -321,4 +363,156 @@ export class DtTable extends _DtTableBase implements OnDestroy { /** CSS class added to any row or cell that has sticky positioning applied. */ protected stickyCssClass = 'dt-table-sticky'; + + /** @internal Exports the filtered source data from the dataSource. */ + // Note: this is different from the display text, see instead _exportDisplayData(). + _exportFilteredData(): void { + const exportData = this._filteredData; + if (this.isEmptyDataSource || typeof exportData[0] != 'object') { + return; + } + + const csvObj = { csv: '' }; + const keys: string[] = Object.keys(exportData[0]); + + if (!keys.length) { + return; + } + + //check for objects, expand properties into new columns + for (let i = keys.length - 1; i > -1; i--) { + const key = keys[i]; + const val = exportData[0][key]; + if (typeof val == 'object') { + const subkeys = Object.keys(val).map((sk) => `${key}.${sk}`); + keys.splice(i, 1, ...subkeys); + } + } + // header row + csvObj.csv += keys.join(',') + '\n'; + + for (const row of exportData) { + for (let idx = 0; idx < keys.length; idx++) { + const key = keys[idx]; + let val: any; + if (key.includes('.') && typeof row[key] == 'undefined') { + //derived key object.property + const keyArr = key.split('.'); + const objKey = keyArr[0]; + const prop = keyArr[1]; + const obj = row[objKey] || {}; + val = obj[prop]; + } else { + val = row[key]; + } + this._appendValToCSV(csvObj, val, idx, keys.length); + } + csvObj.csv += '\n'; + } + this._downloadCSV(csvObj.csv); + } + + /** @internal Exports the filtered display data from the dataSource after being formatted by a displayAccessor. */ + _exportDisplayData(exportData: T[] = this._filteredData): void { + if (this.isEmptyDataSource || typeof exportData[0] != 'object') { + return; + } + + const csvObj = { csv: '' }; + const keys: string[] = [...this._contentHeaderRowDefs.first.columns].filter( + (h: string) => h !== 'checkbox', + ); + //skip selection column + if (!keys.length) { + return; + } + + //get column names + const headerList = this._elementRef.nativeElement.querySelectorAll( + 'dt-header-row dt-header-cell', + ); + const headersArr = Array.from(headerList); + const headers = headersArr + .map((h: HTMLElement): String => { + const txt = h.innerText; + if (txt.includes(',')) return `"${txt}"`; + else return txt; + }) + .filter((h: string) => h !== ''); + + //skip selection column + if (headers.length !== keys.length) { + console.warn( + '_exportDisplayData: mismatched column count. Data may be shifted.', + ); + } + + // header row + csvObj.csv += headers.join(',') + '\n'; + + for (const row of exportData) { + for (let idx = 0; idx < keys.length; idx++) { + const key = keys[idx]; + const accessor = this._displayAccessorMap.get(key); + const val = accessor ? accessor(row, key) : row[key]; + this._appendValToCSV(csvObj, val, idx, keys.length); + } + csvObj.csv += '\n'; + } + + this._downloadCSV(csvObj.csv); + } + + /** Assemble the CSV while safely handling types. */ + private _appendValToCSV( + csvObj: { csv: string }, + val: any, + idx: number, + len: number, + ): void { + switch (typeof val) { + case 'string': + break; + case 'object': //if it's still complex, just convert to JSON and move on + val = JSON.stringify(val); + break; + case 'undefined': + val = ''; + break; + default: + val = val.toString(); + } + + if (val.includes(',')) { + val = val.replace(/"/g, '""'); //escape any existing double quotes + csvObj.csv += `"${val}"`; // + } else { + csvObj.csv += val; + } + if (idx < len) { + csvObj.csv += ','; + } + } + + /** @internal Export only the rows which are currently selected. */ + _exportSelection(): void { + if (this._exporter.selection) { + this._exportDisplayData(this._exporter.selection.selected); + } else { + console.log('no selection'); + } + } + + /** Take a CSV string and trigger a download in browser */ + private _downloadCSV(csv: string): void { + //make csv document + const blob = new Blob([csv], { type: 'text/csv' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.target = '_blank'; + link.download = 'table-' + new Date().getTime() + '.csv'; + link.click(); + link.remove(); + } } diff --git a/libs/examples/src/index.ts b/libs/examples/src/index.ts index 2b4b8db37a..7e5b9eb3eb 100644 --- a/libs/examples/src/index.ts +++ b/libs/examples/src/index.ts @@ -298,6 +298,8 @@ import { DtExampleTableDefault } from './table/table-default-example/table-defau import { DtExampleTableDynamicColumns } from './table/table-dynamic-columns-example/table-dynamic-columns-example'; import { DtExampleTableEmptyState } from './table/table-empty-state-example/table-empty-state-example'; import { DtExampleTableExpandableRows } from './table/table-expandable-rows-example/table-expandable-rows-example'; +import { DtExampleTableExport } from './table/table-export-example/table-export-example'; +import { DtExampleTableExportSelection } from './table/table-export-selection-example/table-export-selection-example'; import { DtExampleTableFavoriteColumn } from './table/table-favorite-column-example/table-favorite-column-example'; import { DtExampleTableFavoriteColumnNoHeader } from './table/table-favorite-column-no-header-example/table-favorite-column-no-header-example'; import { DtExampleTableInteractiveRows } from './table/table-interactive-rows-example/table-interactive-rows-example'; @@ -681,6 +683,8 @@ export { DtExampleTableDynamicColumns, DtExampleTableEmptyState, DtExampleTableExpandableRows, + DtExampleTableExport, + DtExampleTableExportSelection, DtExampleTableFavoriteColumn, DtExampleTableFavoriteColumnNoHeader, DtExampleTableInteractiveRows, @@ -1077,6 +1081,7 @@ export const EXAMPLES_MAP = new Map>([ ['DtExampleTableDynamicColumns', DtExampleTableDynamicColumns], ['DtExampleTableEmptyState', DtExampleTableEmptyState], ['DtExampleTableExpandableRows', DtExampleTableExpandableRows], + ['DtExampleTableExport', DtExampleTableExport], ['DtExampleTableFavoriteColumn', DtExampleTableFavoriteColumn], [ 'DtExampleTableFavoriteColumnNoHeader', diff --git a/libs/examples/src/table/index.ts b/libs/examples/src/table/index.ts index f4bd065e12..958c8da62e 100644 --- a/libs/examples/src/table/index.ts +++ b/libs/examples/src/table/index.ts @@ -24,6 +24,8 @@ export * from './table-dynamic-columns-example/table-dynamic-columns-example'; export * from './table-empty-state-example/table-empty-state-example'; export * from './table-examples.module'; export * from './table-expandable-rows-example/table-expandable-rows-example'; +export * from './table-export-example/table-export-example'; +export * from './table-export-selection-example/table-export-selection-example'; export * from './table-favorite-column-example/table-favorite-column-example'; export * from './table-favorite-column-no-header-example/table-favorite-column-no-header-example'; export * from './table-interactive-rows-example/table-interactive-rows-example'; diff --git a/libs/examples/src/table/table-examples.module.ts b/libs/examples/src/table/table-examples.module.ts index 1a77aa8b7a..8eeb1b9218 100644 --- a/libs/examples/src/table/table-examples.module.ts +++ b/libs/examples/src/table/table-examples.module.ts @@ -37,6 +37,8 @@ import { DtExampleTableCustomColumns } from './table-custom-columns-example/tabl import { DtExampleTableDefault } from './table-default-example/table-default-example'; import { DtExampleTableDynamicColumns } from './table-dynamic-columns-example/table-dynamic-columns-example'; import { DtExampleTableEmptyState } from './table-empty-state-example/table-empty-state-example'; +import { DtExampleTableExport } from './table-export-example/table-export-example'; +import { DtExampleTableExportSelection } from './table-export-selection-example/table-export-selection-example'; import { DtExampleTableFavoriteColumn } from './table-favorite-column-example/table-favorite-column-example'; import { DtExampleTableFavoriteColumnNoHeader } from './table-favorite-column-no-header-example/table-favorite-column-no-header-example'; import { DtExampleTableInteractiveRows } from './table-interactive-rows-example/table-interactive-rows-example'; @@ -85,6 +87,8 @@ import { FormsModule } from '@angular/forms'; DtExampleTableDefault, DtExampleTableDynamicColumns, DtExampleTableEmptyState, + DtExampleTableExport, + DtExampleTableExportSelection, DtExampleTableExpandableRows, DtExampleTableFavoriteColumn, DtExampleTableFavoriteColumnNoHeader, diff --git a/libs/examples/src/table/table-export-example/table-export-example.html b/libs/examples/src/table/table-export-example/table-export-example.html new file mode 100644 index 0000000000..4be49e029a --- /dev/null +++ b/libs/examples/src/table/table-export-example/table-export-example.html @@ -0,0 +1,40 @@ + + + + + + + + + + + + diff --git a/libs/examples/src/table/table-export-example/table-export-example.scss b/libs/examples/src/table/table-export-example/table-export-example.scss new file mode 100644 index 0000000000..75cac41aee --- /dev/null +++ b/libs/examples/src/table/table-export-example/table-export-example.scss @@ -0,0 +1,10 @@ +:host { + display: flex; + align-items: flex-start; + flex-direction: column; +} + +dt-pagination { + align-self: center; + margin: 1em 0; +} diff --git a/libs/examples/src/table/table-export-example/table-export-example.ts b/libs/examples/src/table/table-export-example/table-export-example.ts new file mode 100644 index 0000000000..58ef8688ab --- /dev/null +++ b/libs/examples/src/table/table-export-example/table-export-example.ts @@ -0,0 +1,186 @@ +/** + * @license + * Copyright 2021 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, OnInit, ViewChild } from '@angular/core'; + +import { + formatBytes, + formatPercent, + formatRate, +} from '@dynatrace/barista-components/formatters'; +import { DtPagination } from '@dynatrace/barista-components/pagination'; +import { DtSort, DtTableDataSource } from '@dynatrace/barista-components/table'; + +@Component({ + selector: 'dt-example-table-export', + styleUrls: ['./table-export-example.scss'], + templateUrl: './table-export-example.html', +}) +export class DtExampleTableExport implements OnInit { + private data: object[] = [ + { + host: 'et-demo-2-win4', + cpu: 30, + memoryPerc: 38, + memoryTotal: 5830000000, + traffic: 98700000, + diskLatency: { + diskRead: 100, + diskWrite: 200, + }, + diskUsage: [ + { + mount: '/', + diskPerc: 38, + diskTotal: 12345678, + }, + { + mount: '/mnt', + diskPerc: 45, + diskTotal: 12345, + }, + ], + }, + { + host: 'et-demo-2-win3', + cpu: 26, + memoryPerc: 46, + memoryTotal: 6000000000, + traffic: 62500000, + diskLatency: { + diskRead: 101, + diskWrite: 201, + }, + }, + { + host: 'docker-host2', + cpu: 25.4, + memoryPerc: 35, + memoryTotal: 5810000000, + traffic: 41900000, + diskLatency: { + diskRead: 102, + diskWrite: 202, + }, + }, + { + host: 'et-demo-2-win1', + cpu: 23, + memoryPerc: 7.86, + memoryTotal: 5820000000, + traffic: 98700000, + diskLatency: { + diskRead: 103, + diskWrite: 203, + }, + }, + { + host: 'et-demo-2-win8', + cpu: 78, + memoryPerc: 21, + memoryTotal: 3520000000, + traffic: 91870000, + diskLatency: { + diskRead: 104, + diskWrite: 204, + }, + }, + { + host: 'et-demo-2-macOS', + cpu: 21, + memoryPerc: 34, + memoryTotal: 3200000000, + traffic: 1200000, + diskLatency: { + diskRead: 105, + diskWrite: 205, + }, + }, + { + host: 'kyber-host6', + cpu: 12.3, + memoryPerc: 12, + memoryTotal: 2120000000, + traffic: 4500000, + diskLatency: { + diskRead: 106, + diskWrite: 206, + }, + diskUsage: [ + { + mount: '/', + diskPerc: 38, + diskTotal: 12345678, + }, + ], + }, + { + host: 'dev-demo-5-macOS', + cpu: 24, + memoryPerc: 8.6, + memoryTotal: 4670000000, + traffic: 3270000, + diskLatency: { + diskRead: 107, + diskWrite: 207, + }, + }, + ]; + + // Get the viewChild to pass the sorter reference to the data-source. + @ViewChild('sortable', { read: DtSort, static: true }) sortable: DtSort; + @ViewChild(DtPagination, { static: true }) pagination: DtPagination; + + dataSource: DtTableDataSource; + + constructor() { + this.dataSource = new DtTableDataSource(this.data); + } + + ngOnInit(): void { + // Set the dtSort reference on the dataSource, so it can react to sorting. + this.dataSource.sort = this.sortable; + // Set the dtPagination reference on the dataSource, so it can page the data. + this.dataSource.pagination = this.pagination; + // Set the pageSize to override the default page size. + this.dataSource.pageSize = 2; + } + + percentageFormatter = formatPercent; + + trafficFormatter = (value: number) => + formatBytes(formatRate(value, 's'), { + inputUnit: 'byte', + outputUnit: 'MB', + factor: 1024, + }); + + // tslint:disable-next-line: no-any + combineMemory(row: any): string { + const memoryPercentage = formatPercent(row.memoryPerc); + const memoryTotal = formatBytes(row.memoryTotal, { + inputUnit: 'byte', + outputUnit: 'GB', + factor: 1024, + }); + return `${memoryPercentage} of ${memoryTotal}`; + } + + // tslint:disable-next-line: no-any + memorySortAccessor(row: any): number { + return row.memoryPerc; + } +} diff --git a/libs/examples/src/table/table-export-selection-example/table-export-selection-example.html b/libs/examples/src/table/table-export-selection-example/table-export-selection-example.html new file mode 100644 index 0000000000..344fdd2e72 --- /dev/null +++ b/libs/examples/src/table/table-export-selection-example/table-export-selection-example.html @@ -0,0 +1,25 @@ + + + + + + + + + + + +Current selection: {{ getCurrentSelection() }} diff --git a/libs/examples/src/table/table-export-selection-example/table-export-selection-example.scss b/libs/examples/src/table/table-export-selection-example/table-export-selection-example.scss new file mode 100644 index 0000000000..226b8fd100 --- /dev/null +++ b/libs/examples/src/table/table-export-selection-example/table-export-selection-example.scss @@ -0,0 +1,3 @@ +.dt-selection-panel { + margin-top: 20px; +} diff --git a/libs/examples/src/table/table-export-selection-example/table-export-selection-example.ts b/libs/examples/src/table/table-export-selection-example/table-export-selection-example.ts new file mode 100644 index 0000000000..6c14320252 --- /dev/null +++ b/libs/examples/src/table/table-export-selection-example/table-export-selection-example.ts @@ -0,0 +1,118 @@ +/** + * @license + * Copyright 2021 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AfterViewInit, Component, ViewChild } from '@angular/core'; + +import { + DT_TABLE_SELECTION_CONFIG, + DtSort, + DtTableDataSource, + DtTableSelection, +} from '@dynatrace/barista-components/table'; +import { isNil } from 'lodash-es'; + +interface Row { + host: string; + cpu: number; +} + +@Component({ + selector: 'dt-example-table-export-selectable-column', + templateUrl: './table-export-selection-example.html', + styleUrls: ['./table-export-selection-example.scss'], + providers: [ + { + provide: DT_TABLE_SELECTION_CONFIG, + useValue: { selectionLimit: 5 }, + }, + ], +}) +export class DtExampleTableExportSelection implements AfterViewInit { + data: Row[] = [ + { + host: 'et-demo-2-win4', + cpu: 30, + }, + { + host: 'et-demo-2-win3', + cpu: 26, + }, + { + host: 'docker-host2', + cpu: 25.4, + }, + { + host: 'et-demo-2-win1', + cpu: 23, + }, + { + host: 'et-demo-2-win5', + cpu: 23, + }, + { + host: 'et-demo-2-win6', + cpu: 23, + }, + { + host: 'et-demo-2-win7', + cpu: 23, + }, + { + host: 'et-demo-2-win8', + cpu: 23, + }, + { + host: 'my host (disabled)', + cpu: 13, + }, + ]; + + // Get the viewChild to pass the sorter reference to the datasource. + @ViewChild(DtSort, { read: DtSort, static: true }) sortable: DtSort; + @ViewChild(DtTableSelection, { read: DtTableSelection, static: true }) + selection: DtTableSelection; + + // Initialize the table's data source + dataSource: DtTableDataSource; + constructor() { + this.dataSource = new DtTableDataSource(this.data); + } + + ngAfterViewInit(): void { + // Set the dtSort reference on the dataSource, so it can react to sorting. + this.dataSource.sort = this.sortable; + // Set the dtTableSelection on the dataSource, for exporting. + setTimeout(() => { + //async to avoid https://angular.io/errors/NG0100 + this.dataSource.selection = this.selection; + }); + } + + getCurrentSelection(): string { + return this.selection.selected.map((data: Row) => data.host).join(', '); + } + + isDisabled(entry: { host: string; cpu: number }): boolean { + return entry.host.endsWith('(disabled)'); + } + + getAriaLabel(value: Row | undefined): string { + if (isNil(value)) { + return 'Select hosts'; + } + return 'Select ' + value!.host; + } +} diff --git a/package-lock.json b/package-lock.json index 6b3fb1c5b6..b54c0778d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6072,10 +6072,17 @@ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } } }, "path-exists": {