From e4efe533ad221135f8bc3a779661d88ee2d16772 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Erik=20Hudcovsk=C3=BD?=
<47628511+erzik987@users.noreply.github.com>
Date: Tue, 30 Jul 2024 13:17:37 +0200
Subject: [PATCH] Add paginator addon into table widget (#701)
* Add paginator addon into table widget
* Refactor
* Add unit tests
* Adjust ITableWidgetConfig + add some tests
* Paginator configuration prototype
* Add pageSize and pageSizeSet into configuration
* Add unit tests + minor changes
* Final fixes + small refactor
* Add documentation
* Fix failing build
* Minor fix
* Fix bad import
* PR review requests + better error handling
* Lint error + bad datasource
* Update imports
---
docs/CHANGELOG.md | 4 +
.../src/components/docs/demo.files.ts | 11 +-
.../widget-types/table/table-docs.module.ts | 14 +
.../docs/table-paginator-docs.component.html | 41 +++
.../docs/table-paginator-docs.component.ts | 47 +++
...le-widget-paginator-example.component.html | 29 ++
...le-widget-paginator-example.component.less | 3 +
...able-widget-paginator-example.component.ts | 309 ++++++++++++++++++
.../acme-table-mock-data-source.service.ts | 24 +-
.../components/prototypes/table/widgets.ts | 18 +-
.../src/docs/development/summary.json | 4 +
.../pages/widget-types/table-paginator.md | 1 +
.../src/docs/production/summary.json | 4 +
.../addons/paginator-feature-addon.service.ts | 95 ++++++
.../virtual-scroll-feature-addon.service.ts | 6 +-
.../table-widget/table-widget.component.html | 18 +
.../table-widget.component.spec.ts | 111 ++++++-
.../table-widget/table-widget.component.ts | 75 ++++-
.../src/lib/components/table-widget/types.ts | 26 +-
.../widget-editor-accordion.component.ts | 5 +
.../table-filters-editor.component.ts | 2 +
.../components/widgets/table/public-api.ts | 1 +
.../scroll-type-editor.component.html | 109 ++++++
.../scroll-type-editor.component.less | 21 ++
.../scroll-type-editor.component.spec.ts | 184 +++++++++++
.../scroll-type-editor.component.ts | 279 ++++++++++++++++
.../scroll-type-editor.service.ts | 40 +++
.../lib/configurator/configurator.module.ts | 4 +
.../services/converters/table/mocks.ts | 24 +-
.../table-filters-converter.service.spec.ts | 2 +
.../table/table-filters-converter.service.ts | 25 +-
...able-scroll-type-converter.service.spec.ts | 131 ++++++++
.../table-scroll-type-converter.service.ts | 112 +++++++
.../dashboards/src/lib/dashboards.module.ts | 2 +
.../lib/services/provider-registry.service.ts | 8 +
packages/dashboards/src/lib/services/types.ts | 2 +
.../widget-types/table/table-configurator.ts | 16 +-
37 files changed, 1742 insertions(+), 65 deletions(-)
create mode 100644 packages/dashboards/examples/src/components/docs/widget-types/table/table-widget-paginator/docs/table-paginator-docs.component.html
create mode 100644 packages/dashboards/examples/src/components/docs/widget-types/table/table-widget-paginator/docs/table-paginator-docs.component.ts
create mode 100644 packages/dashboards/examples/src/components/docs/widget-types/table/table-widget-paginator/example/table-widget-paginator-example.component.html
create mode 100644 packages/dashboards/examples/src/components/docs/widget-types/table/table-widget-paginator/example/table-widget-paginator-example.component.less
create mode 100644 packages/dashboards/examples/src/components/docs/widget-types/table/table-widget-paginator/example/table-widget-paginator-example.component.ts
create mode 100644 packages/dashboards/src/docs/pages/widget-types/table-paginator.md
create mode 100644 packages/dashboards/src/lib/components/table-widget/addons/paginator-feature-addon.service.ts
create mode 100644 packages/dashboards/src/lib/configurator/components/widgets/table/scrollType-editor/scroll-type-editor.component.html
create mode 100644 packages/dashboards/src/lib/configurator/components/widgets/table/scrollType-editor/scroll-type-editor.component.less
create mode 100644 packages/dashboards/src/lib/configurator/components/widgets/table/scrollType-editor/scroll-type-editor.component.spec.ts
create mode 100644 packages/dashboards/src/lib/configurator/components/widgets/table/scrollType-editor/scroll-type-editor.component.ts
create mode 100644 packages/dashboards/src/lib/configurator/components/widgets/table/scrollType-editor/scroll-type-editor.service.ts
create mode 100644 packages/dashboards/src/lib/configurator/services/converters/table/table-scroll-type-converter.service.spec.ts
create mode 100644 packages/dashboards/src/lib/configurator/services/converters/table/table-scroll-type-converter.service.ts
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 577f27fc7..6b301e91c 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -2,6 +2,10 @@
## [15.0.8] 📅 2024-06-03
+### Added
+
+- `@nova-ui/dashboards` | Paginator added for table widget.
+
### Bugfix
- `@nova-ui/dashboards` | Added missing empty image for _nuiListWidgetComponent_ with no data.
diff --git a/packages/dashboards/examples/src/components/docs/demo.files.ts b/packages/dashboards/examples/src/components/docs/demo.files.ts
index 2bbefd4ee..5ad29b77a 100644
--- a/packages/dashboards/examples/src/components/docs/demo.files.ts
+++ b/packages/dashboards/examples/src/components/docs/demo.files.ts
@@ -24,11 +24,11 @@ export const DEMO_PATHS = [
"overview/overview-docs.component.html",
"overview/overview-docs.component.ts",
"overview/overview.module.ts",
+ "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.html",
+ "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.less",
+ "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts",
"tutorials/customization/configurator-section/custom-configurator-section-docs.component.html",
"tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts",
- "tutorials/customization/configurator-section/custom-configurator-section.example.component.html",
- "tutorials/customization/configurator-section/custom-configurator-section.example.component.less",
- "tutorials/customization/configurator-section/custom-configurator-section.example.component.ts",
"tutorials/customization/configurator-section/custom-configurator-section.module.ts",
"tutorials/customization/customization.module.ts",
"tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.html",
@@ -164,6 +164,11 @@ export const DEMO_PATHS = [
"widget-types/table/table-widget-interactive/table-widget-interactive-example.component.html",
"widget-types/table/table-widget-interactive/table-widget-interactive-example.component.less",
"widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts",
+ "widget-types/table/table-widget-paginator/docs/table-paginator-docs.component.html",
+ "widget-types/table/table-widget-paginator/docs/table-paginator-docs.component.ts",
+ "widget-types/table/table-widget-paginator/example/table-widget-paginator-example.component.html",
+ "widget-types/table/table-widget-paginator/example/table-widget-paginator-example.component.less",
+ "widget-types/table/table-widget-paginator/example/table-widget-paginator-example.component.ts",
"widget-types/table/table-widget-search-example/docs/table-search-docs.component.html",
"widget-types/table/table-widget-search-example/docs/table-search-docs.component.ts",
"widget-types/table/table-widget-search-example/example/table-widget-search.example.component.html",
diff --git a/packages/dashboards/examples/src/components/docs/widget-types/table/table-docs.module.ts b/packages/dashboards/examples/src/components/docs/widget-types/table/table-docs.module.ts
index a2cabf933..fa6b591ed 100644
--- a/packages/dashboards/examples/src/components/docs/widget-types/table/table-docs.module.ts
+++ b/packages/dashboards/examples/src/components/docs/widget-types/table/table-docs.module.ts
@@ -40,6 +40,8 @@ import { TableWidgetInteractiveExampleComponent } from "./table-widget-interacti
import { TableSearchDocsComponent } from "./table-widget-search-example/docs/table-search-docs.component";
import { TableWidgetSearchExampleComponent } from "./table-widget-search-example/example/table-widget-search.example.component";
import { TableWidgetExampleComponent } from "./table-widget/table-widget-example.component";
+import { TablePaginatorDocsComponent } from "./table-widget-paginator/docs/table-paginator-docs.component";
+import { TableWidgetPaginatorExampleComponent } from "./table-widget-paginator/example/table-widget-paginator-example.component";
const routes: Routes = [
{
@@ -71,6 +73,16 @@ const routes: Routes = [
showThemeSwitcher: true,
},
},
+ {
+ path: "table-paginator",
+ component: TablePaginatorDocsComponent,
+ data: {
+ srlc: {
+ hideIndicator: true,
+ },
+ showThemeSwitcher: true,
+ },
+ },
];
@NgModule({
@@ -85,9 +97,11 @@ const routes: Routes = [
declarations: [
TableDocsComponent,
TableSearchDocsComponent,
+ TablePaginatorDocsComponent,
TableWidgetInteractiveExampleComponent,
TableWidgetExampleComponent,
TableWidgetSearchExampleComponent,
+ TableWidgetPaginatorExampleComponent,
],
providers: [
{
diff --git a/packages/dashboards/examples/src/components/docs/widget-types/table/table-widget-paginator/docs/table-paginator-docs.component.html b/packages/dashboards/examples/src/components/docs/widget-types/table/table-widget-paginator/docs/table-paginator-docs.component.html
new file mode 100644
index 000000000..634e4a3b6
--- /dev/null
+++ b/packages/dashboards/examples/src/components/docs/widget-types/table/table-widget-paginator/docs/table-paginator-docs.component.html
@@ -0,0 +1,41 @@
+
Table Widget with paginator
+
+
+ Table Widget can have pagination functionality. This page contains
+ information only about setting up the pagination, so before proceeding get
+ familiar with the following:
+
+
+
+ Data Sources
+ - for information about Data Sources and their Features.
+
+
+ Table Widget
+ - for configuring a table widget itself.
+
+
+
+
+
+
+
+Configuring the Widget
+
+ To configure the widget you have to enable paginator in the widget
+ configuration:
+
+
+ {{ tableConfigurationText }}
+
diff --git a/packages/dashboards/examples/src/components/docs/widget-types/table/table-widget-paginator/docs/table-paginator-docs.component.ts b/packages/dashboards/examples/src/components/docs/widget-types/table/table-widget-paginator/docs/table-paginator-docs.component.ts
new file mode 100644
index 000000000..f46b3320c
--- /dev/null
+++ b/packages/dashboards/examples/src/components/docs/widget-types/table/table-widget-paginator/docs/table-paginator-docs.component.ts
@@ -0,0 +1,47 @@
+// © 2022 SolarWinds Worldwide, LLC. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import { Component } from "@angular/core";
+
+@Component({
+ selector: "nui-table-paginator-docs",
+ templateUrl: "./table-paginator-docs.component.html",
+})
+export class TablePaginatorDocsComponent {
+ public tableConfigurationText = `
+ "table": {
+ ...
+ properties: {
+ configuration: {
+ // define paginator configuration here
+ scrollType: ScrollType.paginator,
+ paginatorConfiguration: {
+ pageSize: 10, // Value have to be one of pageSizeSet values
+ pageSizeSet: [10, 20, 30],
+ },
+ // If not specified, default is set to
+ // pageSize: 10,
+ // pageSizeSet: [10, 20, 50],
+ hasVirtualScroll: false, // Has to be speciefied because of backward compatibility
+ } as ITableWidgetConfig,
+ },
+ },
+ `;
+}
diff --git a/packages/dashboards/examples/src/components/docs/widget-types/table/table-widget-paginator/example/table-widget-paginator-example.component.html b/packages/dashboards/examples/src/components/docs/widget-types/table/table-widget-paginator/example/table-widget-paginator-example.component.html
new file mode 100644
index 000000000..e7d8cbdc8
--- /dev/null
+++ b/packages/dashboards/examples/src/components/docs/widget-types/table/table-widget-paginator/example/table-widget-paginator-example.component.html
@@ -0,0 +1,29 @@
+
+
+ Edit Mode
+
+
+ Restore Widget
+
+
+
+
+
+
+
+
diff --git a/packages/dashboards/examples/src/components/docs/widget-types/table/table-widget-paginator/example/table-widget-paginator-example.component.less b/packages/dashboards/examples/src/components/docs/widget-types/table/table-widget-paginator/example/table-widget-paginator-example.component.less
new file mode 100644
index 000000000..c38e702f2
--- /dev/null
+++ b/packages/dashboards/examples/src/components/docs/widget-types/table/table-widget-paginator/example/table-widget-paginator-example.component.less
@@ -0,0 +1,3 @@
+.dashboard {
+ height: 400px;
+}
diff --git a/packages/dashboards/examples/src/components/docs/widget-types/table/table-widget-paginator/example/table-widget-paginator-example.component.ts b/packages/dashboards/examples/src/components/docs/widget-types/table/table-widget-paginator/example/table-widget-paginator-example.component.ts
new file mode 100644
index 000000000..291319800
--- /dev/null
+++ b/packages/dashboards/examples/src/components/docs/widget-types/table/table-widget-paginator/example/table-widget-paginator-example.component.ts
@@ -0,0 +1,309 @@
+// © 2022 SolarWinds Worldwide, LLC. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import { HttpClient } from "@angular/common/http";
+import { ChangeDetectorRef, Component, OnInit } from "@angular/core";
+import { GridsterConfig, GridsterItem } from "angular-gridster2";
+
+import { LoggerService } from "@nova-ui/bits";
+import {
+ DATA_SOURCE,
+ DEFAULT_PIZZAGNA_ROOT,
+ IDashboard,
+ IProviderConfiguration,
+ ITableWidgetConfig,
+ IWidget,
+ IWidgets,
+ NOVA_URL_INTERACTION_HANDLER,
+ PizzagnaLayer,
+ ProviderRegistryService,
+ RawFormatterComponent,
+ ScrollType,
+ WellKnownPathKey,
+ WellKnownProviders,
+ WidgetTypesService,
+} from "@nova-ui/dashboards";
+
+import { AcmeTableMockDataSource } from "../../../../../prototypes/data/table/acme-table-mock-data-source.service";
+
+/**
+ * A component that instantiates the dashboard
+ */
+@Component({
+ selector: "table-widget-paginator-example",
+ templateUrl: "./table-widget-paginator-example.component.html",
+ styleUrls: ["./table-widget-paginator-example.component.less"],
+})
+export class TableWidgetPaginatorExampleComponent implements OnInit {
+ public dashboard: IDashboard | undefined;
+ public gridsterConfig: GridsterConfig = {};
+ public editMode: boolean = false;
+
+ constructor(
+ private widgetTypesService: WidgetTypesService,
+ private providerRegistry: ProviderRegistryService,
+ private changeDetectorRef: ChangeDetectorRef
+ ) {}
+
+ public ngOnInit(): void {
+ const widgetTemplate = this.widgetTypesService.getWidgetType(
+ "table",
+ 1
+ );
+ this.widgetTypesService.setNode(
+ widgetTemplate,
+ "configurator",
+ WellKnownPathKey.DataSourceProviders,
+ [AcmeTableMockDataSource.providerId]
+ );
+
+ this.providerRegistry.setProviders({
+ [AcmeTableMockDataSource.providerId]: {
+ provide: DATA_SOURCE,
+ useClass: AcmeTableMockDataSource,
+ deps: [LoggerService, HttpClient],
+ },
+ });
+
+ this.initializeDashboard();
+ }
+
+ /** Used for restoring widgets state */
+ public reInitializeDashboard(): void {
+ // destroys the components and their providers so the dashboard can re init data
+ this.dashboard = undefined;
+ this.changeDetectorRef.detectChanges();
+
+ this.initializeDashboard();
+ }
+
+ public initializeDashboard(): void {
+ const tableWithPaginator = tableWidgetWithPaginator;
+ const tableWithVirtualScroll = tableWidgetWithVirtualScroll;
+
+ const widgetIndex: IWidgets = {
+ [tableWithPaginator.id]:
+ this.widgetTypesService.mergeWithWidgetType(tableWithPaginator),
+ [tableWithVirtualScroll.id]:
+ this.widgetTypesService.mergeWithWidgetType(
+ tableWithVirtualScroll
+ ),
+ };
+
+ const positions: Record = {
+ [tableWithPaginator.id]: {
+ cols: 6,
+ rows: 6,
+ y: 0,
+ x: 0,
+ },
+ [tableWithVirtualScroll.id]: {
+ cols: 6,
+ rows: 6,
+ y: 0,
+ x: 0,
+ },
+ };
+
+ this.dashboard = {
+ positions,
+ widgets: widgetIndex,
+ };
+ }
+}
+
+export const tableWidgetWithPaginator: IWidget = {
+ id: "widget1",
+ type: "table",
+ pizzagna: {
+ [PizzagnaLayer.Configuration]: {
+ [DEFAULT_PIZZAGNA_ROOT]: {
+ providers: {
+ [WellKnownProviders.InteractionHandler]: {
+ providerId: NOVA_URL_INTERACTION_HANDLER,
+ },
+ },
+ },
+ header: {
+ properties: {
+ title: "Table Widget with paginator!",
+ subtitle: "Basic table widget",
+ collapsible: true,
+ },
+ },
+ table: {
+ providers: {
+ [WellKnownProviders.DataSource]: {
+ providerId: AcmeTableMockDataSource.providerId,
+ } as IProviderConfiguration,
+ },
+ properties: {
+ configuration: {
+ interactive: true,
+ columns: [
+ {
+ id: "column1",
+ label: "No.",
+ isActive: true,
+ formatter: {
+ componentType:
+ RawFormatterComponent.lateLoadKey,
+ properties: {
+ dataFieldIds: {
+ value: "position",
+ },
+ },
+ },
+ },
+ {
+ id: "column2",
+ label: "Name",
+ isActive: true,
+ formatter: {
+ componentType:
+ RawFormatterComponent.lateLoadKey,
+ properties: {
+ dataFieldIds: {
+ value: "name",
+ },
+ },
+ },
+ },
+ {
+ id: "column3",
+ label: "Status",
+ isActive: true,
+ formatter: {
+ componentType:
+ RawFormatterComponent.lateLoadKey,
+ properties: {
+ dataFieldIds: {
+ value: "status",
+ },
+ },
+ },
+ },
+ ],
+ sorterConfiguration: {
+ descendantSorting: false,
+ sortBy: "column1",
+ },
+ scrollType: ScrollType.paginator,
+ paginatorConfiguration: {
+ pageSize: 5,
+ pageSizeSet: [5, 10, 20, 30],
+ },
+ hasVirtualScroll: false,
+ searchConfiguration: {
+ enabled: true,
+ },
+ } as ITableWidgetConfig,
+ },
+ },
+ },
+ },
+};
+
+export const tableWidgetWithVirtualScroll: IWidget = {
+ id: "widget2",
+ type: "table",
+ pizzagna: {
+ [PizzagnaLayer.Configuration]: {
+ [DEFAULT_PIZZAGNA_ROOT]: {
+ providers: {
+ [WellKnownProviders.InteractionHandler]: {
+ providerId: NOVA_URL_INTERACTION_HANDLER,
+ },
+ },
+ },
+ header: {
+ properties: {
+ title: "Table Widget with virtual scroll!",
+ subtitle: "Basic table widget",
+ collapsible: true,
+ },
+ },
+ table: {
+ providers: {
+ [WellKnownProviders.DataSource]: {
+ providerId: AcmeTableMockDataSource.providerId,
+ } as IProviderConfiguration,
+ },
+ properties: {
+ configuration: {
+ interactive: true,
+ columns: [
+ {
+ id: "column1",
+ label: "No.",
+ isActive: true,
+ formatter: {
+ componentType:
+ RawFormatterComponent.lateLoadKey,
+ properties: {
+ dataFieldIds: {
+ value: "position",
+ },
+ },
+ },
+ },
+ {
+ id: "column2",
+ label: "Name",
+ isActive: true,
+ formatter: {
+ componentType:
+ RawFormatterComponent.lateLoadKey,
+ properties: {
+ dataFieldIds: {
+ value: "name",
+ },
+ },
+ },
+ },
+ {
+ id: "column3",
+ label: "Status",
+ isActive: true,
+ formatter: {
+ componentType:
+ RawFormatterComponent.lateLoadKey,
+ properties: {
+ dataFieldIds: {
+ value: "status",
+ },
+ },
+ },
+ },
+ ],
+ sorterConfiguration: {
+ descendantSorting: false,
+ sortBy: "column1",
+ },
+ hasVirtualScroll: true,
+ searchConfiguration: {
+ enabled: true,
+ },
+ } as ITableWidgetConfig,
+ },
+ },
+ },
+ },
+};
diff --git a/packages/dashboards/examples/src/components/prototypes/data/table/acme-table-mock-data-source.service.ts b/packages/dashboards/examples/src/components/prototypes/data/table/acme-table-mock-data-source.service.ts
index 70abde2d0..4fe434249 100644
--- a/packages/dashboards/examples/src/components/prototypes/data/table/acme-table-mock-data-source.service.ts
+++ b/packages/dashboards/examples/src/components/prototypes/data/table/acme-table-mock-data-source.service.ts
@@ -23,6 +23,7 @@ import { Inject, Injectable } from "@angular/core";
import { BehaviorSubject, Subject } from "rxjs";
import {
+ ClientSideDataSource,
IDataField,
INovaFilters,
LocalFilteringDataSource,
@@ -73,32 +74,13 @@ export class AcmeTableMockDataSource extends LocalFilteringDataSource {
setTimeout(async () => {
- const virtualScrollFilter =
- filters.virtualScroll && filters.virtualScroll.value;
-
- if (virtualScrollFilter) {
- // The multiplier used here is a way to fetch more items per scroll
- const start = filters.virtualScroll?.value.start;
- const end = filters.virtualScroll?.value.end;
- // Note: We should start with a clean cache every time first page is requested
- if (start === 0) {
- this.cache = [];
- }
- const nextChunk = TABLE_DATA.slice(start, end);
- // We identify here whether the cached array does already contain some of the fetched data.
- // Then we update the cached array with the only values it doesn't contain
- this.cache = this.cache.concat(
- nextChunk.filter((item) => !this.cache.includes(item))
- );
- super.setData(this.cache);
- }
+ // Set the data to the table
+ super.setData(TABLE_DATA);
const filteredData = await super.getFilteredData(filters);
-
if (filteredData.paginator) {
filteredData.paginator.total = TABLE_DATA.length;
}
-
resolve({
...filteredData,
dataFields: this.dataFields,
diff --git a/packages/dashboards/examples/src/components/prototypes/table/widgets.ts b/packages/dashboards/examples/src/components/prototypes/table/widgets.ts
index c8bb042bd..7ae7235ab 100644
--- a/packages/dashboards/examples/src/components/prototypes/table/widgets.ts
+++ b/packages/dashboards/examples/src/components/prototypes/table/widgets.ts
@@ -36,6 +36,7 @@ import {
PizzagnaLayer,
ProportionalWidgetChartTypes,
RawFormatterComponent,
+ ScrollType,
WellKnownProviders,
} from "@nova-ui/dashboards";
@@ -112,7 +113,7 @@ export const widgets: IWidget[] = [
},
header: {
properties: {
- title: "Table Widget!",
+ title: "Table Widget with paginator!",
subtitle: "Basic table widget",
collapsible: true,
},
@@ -174,7 +175,12 @@ export const widgets: IWidget[] = [
descendantSorting: false,
sortBy: "column1",
},
- hasVirtualScroll: true,
+ scrollType: ScrollType.paginator,
+ paginatorConfiguration: {
+ pageSize: 5,
+ pageSizeSet: [5, 10, 20, 30],
+ },
+ hasVirtualScroll: false,
searchConfiguration: {
enabled: true,
},
@@ -323,7 +329,7 @@ export const widgets: IWidget[] = [
},
header: {
properties: {
- title: "Table Widget!",
+ title: "Table Widget with virtual scroll!",
subtitle: "Basic table widget",
collapsible: true,
},
@@ -533,7 +539,8 @@ export const widgets: IWidget[] = [
descendantSorting: false,
sortBy: "column1",
},
- hasVirtualScroll: true,
+ scrollType: ScrollType.virtual,
+ hasVirtualScroll: false,
searchConfiguration: {
enabled: true,
},
@@ -685,7 +692,8 @@ export const widgets: IWidget[] = [
descendantSorting: false,
sortBy: "column1",
},
- hasVirtualScroll: true,
+ scrollType: ScrollType.paginator,
+ hasVirtualScroll: false,
searchConfiguration: {
enabled: true,
},
diff --git a/packages/dashboards/src/docs/development/summary.json b/packages/dashboards/src/docs/development/summary.json
index 142de2bed..28f3b7e7d 100644
--- a/packages/dashboards/src/docs/development/summary.json
+++ b/packages/dashboards/src/docs/development/summary.json
@@ -134,6 +134,10 @@
{
"title": "Table with Search",
"file": "../pages/widget-types/table-search.md"
+ },
+ {
+ "title": "Table with Paginator",
+ "file": "../pages/widget-types/table-paginator.md"
}
]
},
diff --git a/packages/dashboards/src/docs/pages/widget-types/table-paginator.md b/packages/dashboards/src/docs/pages/widget-types/table-paginator.md
new file mode 100644
index 000000000..6cbc497aa
--- /dev/null
+++ b/packages/dashboards/src/docs/pages/widget-types/table-paginator.md
@@ -0,0 +1 @@
+
diff --git a/packages/dashboards/src/docs/production/summary.json b/packages/dashboards/src/docs/production/summary.json
index 142de2bed..28f3b7e7d 100644
--- a/packages/dashboards/src/docs/production/summary.json
+++ b/packages/dashboards/src/docs/production/summary.json
@@ -134,6 +134,10 @@
{
"title": "Table with Search",
"file": "../pages/widget-types/table-search.md"
+ },
+ {
+ "title": "Table with Paginator",
+ "file": "../pages/widget-types/table-paginator.md"
}
]
},
diff --git a/packages/dashboards/src/lib/components/table-widget/addons/paginator-feature-addon.service.ts b/packages/dashboards/src/lib/components/table-widget/addons/paginator-feature-addon.service.ts
new file mode 100644
index 000000000..e514e689d
--- /dev/null
+++ b/packages/dashboards/src/lib/components/table-widget/addons/paginator-feature-addon.service.ts
@@ -0,0 +1,95 @@
+// © 2022 SolarWinds Worldwide, LLC. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import { Injectable } from "@angular/core";
+import { INovaFilteringOutputs, ISelectChangedEvent } from "@nova-ui/bits";
+import { IPaginatorState, TableWidgetComponent } from "../public-api";
+
+@Injectable()
+export class PaginatorFeatureAddonService {
+ private widget: TableWidgetComponent; // TODO: generic widget
+
+ public defaultPaginatorState: IPaginatorState = {
+ page: 1,
+ pageSize: 10,
+ pageSizeSet: [10, 20, 50],
+ total: 0,
+ };
+ public paginatorState: IPaginatorState = this.defaultPaginatorState;
+
+ public initPaginator(widget: TableWidgetComponent): void {
+ this.widget = widget;
+ this.setPaginatorState();
+ }
+
+ public applyFilters(): void {
+ this.widget.dataSource.applyFilters();
+ }
+
+ private registerPaginator() {
+ if (this.widget.dataSource) {
+ this.widget.dataSource.registerComponent({
+ paginator: {
+ componentInstance: this.widget.paginator,
+ },
+ });
+ }
+ }
+
+ private deregisterPaginator() {
+ if (this.widget.dataSource) {
+ this.widget.dataSource.deregisterComponent?.("paginator");
+ }
+ }
+
+ private setPaginatorState() {
+ const paginatorConfiguration =
+ this.widget.configuration?.paginatorConfiguration;
+
+ if (this.widget.hasPaginator) {
+ this.paginatorState.pageSize =
+ paginatorConfiguration?.pageSize ??
+ this.defaultPaginatorState.pageSize;
+
+ this.paginatorState.pageSizeSet =
+ paginatorConfiguration?.pageSizeSet ??
+ this.defaultPaginatorState.pageSizeSet;
+
+ this.widget.dataSource.outputsSubject.subscribe(
+ (data: INovaFilteringOutputs) => {
+ this.paginatorState.total = data.paginator?.total ?? 0;
+ }
+ );
+
+ // When changing page size from configurator, this will force preview update
+ if (this.widget.paginator) {
+ const pageSize: ISelectChangedEvent = {
+ oldValue: 0,
+ newValue: this.paginatorState.pageSize,
+ };
+ this.widget.paginator.setItemsPerPage(pageSize);
+ }
+
+ this.registerPaginator();
+ } else {
+ this.deregisterPaginator();
+ }
+ }
+}
diff --git a/packages/dashboards/src/lib/components/table-widget/addons/virtual-scroll-feature-addon.service.ts b/packages/dashboards/src/lib/components/table-widget/addons/virtual-scroll-feature-addon.service.ts
index 45afbe37a..b5abccef3 100644
--- a/packages/dashboards/src/lib/components/table-widget/addons/virtual-scroll-feature-addon.service.ts
+++ b/packages/dashboards/src/lib/components/table-widget/addons/virtual-scroll-feature-addon.service.ts
@@ -39,7 +39,11 @@ export class VirtualScrollFeatureAddonService {
this.widget = widget;
}
- public initVirtualScroll(): void {
+ public initVirtualScroll(widget: TableWidgetComponent): void {
+ if (!this.widget) {
+ this.initWidget(widget);
+ }
+
if (this.widget.hasVirtualScroll) {
this.registerVirtualScroll();
} else {
diff --git a/packages/dashboards/src/lib/components/table-widget/table-widget.component.html b/packages/dashboards/src/lib/components/table-widget/table-widget.component.html
index 66f8e4fdd..8dc930631 100644
--- a/packages/dashboards/src/lib/components/table-widget/table-widget.component.html
+++ b/packages/dashboards/src/lib/components/table-widget/table-widget.component.html
@@ -122,4 +122,22 @@
+
+
+
+
+
+
+
diff --git a/packages/dashboards/src/lib/components/table-widget/table-widget.component.spec.ts b/packages/dashboards/src/lib/components/table-widget/table-widget.component.spec.ts
index f8afe0da8..54c02970e 100644
--- a/packages/dashboards/src/lib/components/table-widget/table-widget.component.spec.ts
+++ b/packages/dashboards/src/lib/components/table-widget/table-widget.component.spec.ts
@@ -28,9 +28,9 @@ import { BehaviorSubject } from "rxjs";
import { skip, take, tap } from "rxjs/operators";
import {
+ ClientSideDataSource,
EventBus,
IDataField,
- LocalFilteringDataSource,
LoggerService,
NuiBusyModule,
NuiImageModule,
@@ -50,7 +50,11 @@ import { ProviderRegistryService } from "../../services/provider-registry.servic
import { REFRESH, SCROLL_NEXT_PAGE } from "../../services/types";
import { DATA_SOURCE, PIZZAGNA_EVENT_BUS } from "../../types";
import { TableWidgetComponent } from "./table-widget.component";
-import { ITableWidgetColumnConfig, ITableWidgetConfig } from "./types";
+import {
+ ITableWidgetColumnConfig,
+ ITableWidgetConfig,
+ ScrollType,
+} from "./types";
interface BasicTableModel {
position: number;
@@ -65,7 +69,7 @@ interface BasicTableModel {
secondUrlLabel: string;
}
-class MockDatasource extends LocalFilteringDataSource {
+class MockDatasource extends ClientSideDataSource {
public busy = new BehaviorSubject(true);
}
@@ -355,6 +359,7 @@ describe("TableWidgetComponent", () => {
component.dataFields = dataFields;
component.configuration = configuration;
component.range = tableData.length;
+
fixture.detectChanges();
});
@@ -600,6 +605,106 @@ describe("TableWidgetComponent", () => {
});
});
+ describe("scroll type >", () => {
+ describe("virtual scroll >", () => {
+ it("should be enabled if is hasVirtualScroll is set to true", () => {
+ const configWithVirtualScroll: ITableWidgetConfig = {
+ ...configuration,
+ hasVirtualScroll: true,
+ };
+
+ component.ngOnChanges(
+ createSimpleChanges(
+ configWithVirtualScroll,
+ tableData,
+ dataFields
+ )
+ );
+
+ fixture.detectChanges();
+
+ expect(component.hasPaginator).toBe(false);
+ expect(component.hasVirtualScroll).toBe(true);
+ });
+
+ it("should be enabled if is hasVirtualScroll is set to true even if scrollType is set to paginator", () => {
+ const configWithVirtualScrollAndScrollType: ITableWidgetConfig =
+ {
+ ...configuration,
+ hasVirtualScroll: true,
+ scrollType: ScrollType.paginator,
+ };
+
+ component.ngOnChanges(
+ createSimpleChanges(
+ configWithVirtualScrollAndScrollType,
+ tableData,
+ dataFields
+ )
+ );
+
+ fixture.detectChanges();
+
+ expect(component.hasPaginator).toBe(false);
+ expect(component.hasVirtualScroll).toBe(true);
+ });
+
+ it("should be enabled if not defined otherwise in config", () => {
+ const configurationWithoutScrollTypeOrHasVirtualScroll: ITableWidgetConfig =
+ {
+ columns: [],
+ sorterConfiguration: {
+ descendantSorting: true,
+ sortBy: "column1",
+ },
+ };
+
+ component.ngOnChanges(
+ createSimpleChanges(
+ configurationWithoutScrollTypeOrHasVirtualScroll,
+ tableData,
+ dataFields
+ )
+ );
+
+ fixture.detectChanges();
+
+ expect(component.hasPaginator).toBe(false);
+ expect(component.hasVirtualScroll).toBe(true);
+ });
+ });
+
+ // TODO: Not working properly, fix in NUI-5893
+ describe("paginator >", () => {
+ xit("should be enabled if hasVirtualScroll is set to false and scrollType is set to paginator", () => {
+ const configWithoutVirtualScrollAndScrollType: ITableWidgetConfig =
+ {
+ ...configuration,
+ hasVirtualScroll: false,
+ scrollType: ScrollType.paginator,
+ };
+
+ // component.paginator = new PaginatorComponent(
+ // mockLoggerService,
+ // mockPopupContainer,
+ // mockChangeDetector
+ // );
+ component.ngOnChanges(
+ createSimpleChanges(
+ configWithoutVirtualScrollAndScrollType,
+ tableData,
+ dataFields
+ )
+ );
+
+ fixture.detectChanges();
+
+ expect(component.hasPaginator).toBe(true);
+ expect(component.hasVirtualScroll).toBe(false);
+ });
+ });
+ });
+
describe("table columns mapping >", () => {
it("should correctly map data with one data field", () => {
configuration.columns = oneDataFieldColumns;
diff --git a/packages/dashboards/src/lib/components/table-widget/table-widget.component.ts b/packages/dashboards/src/lib/components/table-widget/table-widget.component.ts
index 1d58cc748..20d17b6ac 100644
--- a/packages/dashboards/src/lib/components/table-widget/table-widget.component.ts
+++ b/packages/dashboards/src/lib/components/table-widget/table-widget.component.ts
@@ -54,8 +54,10 @@ import {
IDataSource,
IEvent,
IFilter,
+ INovaFilteringOutputs,
ISortedItem,
LoggerService,
+ PaginatorComponent,
SorterDirection,
TableAlignmentOptions,
TableComponent,
@@ -81,7 +83,13 @@ import {
import { ITableFormatterDefinition } from "../types";
import { SearchFeatureAddonService } from "./addons/search-feature-addon.service";
import { VirtualScrollFeatureAddonService } from "./addons/virtual-scroll-feature-addon.service";
-import { ITableWidgetColumnConfig, ITableWidgetConfig } from "./types";
+import {
+ IPaginatorState,
+ ITableWidgetColumnConfig,
+ ITableWidgetConfig,
+ ScrollType,
+} from "./types";
+import { PaginatorFeatureAddonService } from "./addons/paginator-feature-addon.service";
/**
* @ignore
@@ -91,7 +99,11 @@ import { ITableWidgetColumnConfig, ITableWidgetConfig } from "./types";
templateUrl: "./table-widget.component.html",
styleUrls: ["./table-widget.component.less"],
changeDetection: ChangeDetectionStrategy.OnPush,
- providers: [SearchFeatureAddonService, VirtualScrollFeatureAddonService],
+ providers: [
+ SearchFeatureAddonService,
+ VirtualScrollFeatureAddonService,
+ PaginatorFeatureAddonService,
+ ],
host: {
// Note: Moved here from configuration to ensure that consumers will not override it.
// Used to prevent table overflowing preview container in the edit/configuration mode.
@@ -137,7 +149,7 @@ export class TableWidgetComponent
string,
number | undefined
>();
- public hasVirtualScroll: boolean = true;
+ public scrollType: ScrollType = ScrollType.virtual;
public tableContainerHeight: number;
public isSearchEnabled: boolean = false;
public searchTerm$ = new Subject();
@@ -175,6 +187,7 @@ export class TableWidgetComponent
public isBusy: boolean = this.lastPageFetched !== this.totalPages;
@ViewChild("widgetTable") table: TableComponent;
+ @ViewChild("paginator") paginator: PaginatorComponent;
@ViewChild(CdkVirtualScrollViewport)
vscrollViewport?: CdkVirtualScrollViewport;
@ViewChildren(TableRowComponent, { read: ElementRef })
@@ -202,6 +215,7 @@ export class TableWidgetComponent
private el: ElementRef,
private logger: LoggerService,
private searchAddon: SearchFeatureAddonService,
+ public paginatorAddon: PaginatorFeatureAddonService,
public virtualScrollAddon: VirtualScrollFeatureAddonService,
private formattersRegistryService: TableFormatterRegistryService
) {}
@@ -239,15 +253,7 @@ export class TableWidgetComponent
this.onSortOrderChanged(sortedColumn);
}
- const newHasVirtualScroll = get(
- changes,
- "configuration.currentValue.hasVirtualScroll",
- true
- ) as boolean;
- if (this.hasVirtualScroll !== newHasVirtualScroll) {
- this.hasVirtualScroll = newHasVirtualScroll;
- this.virtualScrollAddon.initVirtualScroll();
- }
+ this.resolveScrollType(changes);
}
if (changes.totalItems) {
@@ -255,12 +261,35 @@ export class TableWidgetComponent
}
}
+ public resolveScrollType(changes: SimpleChanges) {
+ // Since removing "hasVirtualScroll" from configuration would cause breaking changes, it is used as primary source of truth
+ const newHasVirtualScroll = get(
+ changes,
+ "configuration.currentValue.hasVirtualScroll",
+ true
+ ) as boolean;
+
+ const scrollType = get(
+ changes,
+ "configuration.currentValue.scrollType",
+ ScrollType.virtual
+ ) as ScrollType;
+
+ this.scrollType = newHasVirtualScroll ? ScrollType.virtual : scrollType;
+
+ this.virtualScrollAddon.initVirtualScroll(this);
+ this.paginatorAddon.initPaginator(this);
+
+ if (this.scrollTypeChanged(changes.configuration)) {
+ this.dataSource.applyFilters();
+ }
+ }
+
public ngOnInit(): void {
if (this.dataSource) {
// Since the sortFilter is not initialized, we have to retrieve and set the correct filter value from the configuration before registering it
this.setSortFilter();
this.registerSorter();
- this.virtualScrollAddon.initWidget(this);
}
}
@@ -277,8 +306,10 @@ export class TableWidgetComponent
)
.subscribe();
- this.virtualScrollAddon.initVirtualScroll();
+ this.virtualScrollAddon.initVirtualScroll(this);
this.searchAddon.initWidget(this);
+ this.paginatorAddon.initPaginator(this);
+
const tableHeightChanged$: Observable = this.eventBus
.getStream(WIDGET_RESIZE)
.pipe(
@@ -561,6 +592,14 @@ export class TableWidgetComponent
);
}
+ public get hasVirtualScroll(): boolean {
+ return this.scrollType === ScrollType.virtual;
+ }
+
+ public get hasPaginator(): boolean {
+ return this.scrollType === ScrollType.paginator;
+ }
+
private setSortFilter() {
if (this.configuration) {
const sortBy = this.configuration.sorterConfiguration?.sortBy;
@@ -647,6 +686,14 @@ export class TableWidgetComponent
this.tableData = [];
}
+ private scrollTypeChanged(configurationChange: SimpleChange): boolean {
+ return (
+ !!configurationChange.previousValue &&
+ configurationChange.previousValue.scrollType !==
+ configurationChange.currentValue.scrollType
+ );
+ }
+
private getTableScrollRange(): number {
// Note: To work properly virtual viewport should be scrollable
// to ensure that container will be scrollable we're adding 50% more items
diff --git a/packages/dashboards/src/lib/components/table-widget/types.ts b/packages/dashboards/src/lib/components/table-widget/types.ts
index 4e6b1d76b..037d0f34d 100644
--- a/packages/dashboards/src/lib/components/table-widget/types.ts
+++ b/packages/dashboards/src/lib/components/table-widget/types.ts
@@ -46,7 +46,11 @@ export interface ITableWidgetConfig {
columns: Array;
sortable?: boolean;
sorterConfiguration: ITableWidgetSorterConfig;
- hasVirtualScroll: boolean;
+ /**
+ * @deprecated Use scrollType and set it to "infinite" instead
+ */
+ hasVirtualScroll?: boolean;
+ scrollType?: ScrollType;
interactive?: boolean;
headerTooltipsEnabled?: boolean;
scrollActivationDelayMs?: number;
@@ -62,9 +66,29 @@ export interface ITableWidgetConfig {
searchTerm?: string;
searchDebounce?: number;
};
+
+ paginatorConfiguration?: ITableWidgetPaginatorConfig;
}
export interface ITableWidgetSorterConfig {
descendantSorting: boolean;
sortBy: string;
}
+
+export interface ITableWidgetPaginatorConfig {
+ pageSizeSet?: number[];
+ pageSize?: number;
+}
+
+export interface IPaginatorState {
+ page: number;
+ pageSize: number;
+ pageSizeSet: number[];
+ total: number;
+}
+
+export enum ScrollType {
+ virtual = "virtual",
+ paginator = "paginator",
+ default = "default",
+}
diff --git a/packages/dashboards/src/lib/configurator/components/widget-editor-accordion/widget-editor-accordion.component.ts b/packages/dashboards/src/lib/configurator/components/widget-editor-accordion/widget-editor-accordion.component.ts
index 1270602b0..806e9176d 100644
--- a/packages/dashboards/src/lib/configurator/components/widget-editor-accordion/widget-editor-accordion.component.ts
+++ b/packages/dashboards/src/lib/configurator/components/widget-editor-accordion/widget-editor-accordion.component.ts
@@ -22,9 +22,11 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
+ EventEmitter,
Input,
OnDestroy,
OnInit,
+ Output,
ViewEncapsulation,
} from "@angular/core";
import { Subject } from "rxjs";
@@ -45,6 +47,8 @@ export class WidgetEditorAccordionComponent implements OnInit, OnDestroy {
@Input() public state: AccordionState = AccordionState.DEFAULT;
+ @Output() public openToggle = new EventEmitter();
+
public open = false;
public openSubject = new Subject();
public destroySubject = new Subject();
@@ -59,6 +63,7 @@ export class WidgetEditorAccordionComponent implements OnInit, OnDestroy {
}
public openChange(isOpened: boolean): void {
+ this.openToggle.emit(isOpened);
if (isOpened) {
this.openSubject.next();
} else {
diff --git a/packages/dashboards/src/lib/configurator/components/widgets/table/filters-editor/table-filters-editor.component.ts b/packages/dashboards/src/lib/configurator/components/widgets/table/filters-editor/table-filters-editor.component.ts
index 9487fceff..20b4a3b11 100644
--- a/packages/dashboards/src/lib/configurator/components/widgets/table/filters-editor/table-filters-editor.component.ts
+++ b/packages/dashboards/src/lib/configurator/components/widgets/table/filters-editor/table-filters-editor.component.ts
@@ -99,6 +99,7 @@ export class TableFiltersEditorComponent
const descendantSortingFormControl = this.form
.get("sorterConfiguration")
?.get("descendantSorting");
+
if (changes.sorterConfiguration) {
const sortedColumn = this.sortableColumns.find(
(column) => column.id === this.sorterConfiguration?.sortBy
@@ -132,6 +133,7 @@ export class TableFiltersEditorComponent
descendantSortingFormControl?.enable();
}
}
+
this.changeDetector.detectChanges();
}
diff --git a/packages/dashboards/src/lib/configurator/components/widgets/table/public-api.ts b/packages/dashboards/src/lib/configurator/components/widgets/table/public-api.ts
index 4ec4b1352..843296bd1 100644
--- a/packages/dashboards/src/lib/configurator/components/widgets/table/public-api.ts
+++ b/packages/dashboards/src/lib/configurator/components/widgets/table/public-api.ts
@@ -19,6 +19,7 @@
// THE SOFTWARE.
export * from "./filters-editor/table-filters-editor.component";
+export * from "./scrollType-editor/scroll-type-editor.component";
export * from "./columns-editor/table-columns-configuration.component";
export * from "./columns-editor/column-configuration/presentation-configuration/portals/formatter-configurator.component";
export * from "./columns-editor/column-configuration/presentation-configuration/portals/link-configurator/link-configurator.component";
diff --git a/packages/dashboards/src/lib/configurator/components/widgets/table/scrollType-editor/scroll-type-editor.component.html b/packages/dashboards/src/lib/configurator/components/widgets/table/scrollType-editor/scroll-type-editor.component.html
new file mode 100644
index 000000000..9b0a674cb
--- /dev/null
+++ b/packages/dashboards/src/lib/configurator/components/widgets/table/scrollType-editor/scroll-type-editor.component.html
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+ {{ column.title }}
+
+
+
+
+
+
+
+
+
+
0"
+ class="d-flex align-items-end"
+ >
+
+
+
+ {{ option }}
+
+
+
+ Page size is required
+
+
+
+
+
+
+
+
diff --git a/packages/dashboards/src/lib/configurator/components/widgets/table/scrollType-editor/scroll-type-editor.component.less b/packages/dashboards/src/lib/configurator/components/widgets/table/scrollType-editor/scroll-type-editor.component.less
new file mode 100644
index 000000000..878e8715f
--- /dev/null
+++ b/packages/dashboards/src/lib/configurator/components/widgets/table/scrollType-editor/scroll-type-editor.component.less
@@ -0,0 +1,21 @@
+@import (reference) "@nova-ui/bits/sdk/less/nui-framework-variables";
+
+.table-filters-configuration {
+ &__accordion-content {
+ padding: @nui-space-md @nui-space-md @nui-space-md
+ (@nui-space-md * 2 + @icon-size-default);
+
+ .scroll-type-field {
+ margin-bottom: @nui-space-sm;
+ }
+
+ .expander-content {
+ display: flex;
+
+ .page-size-set-menu {
+ margin-top: 26px;
+ margin-right: @nui-space-sm;
+ }
+ }
+ }
+}
diff --git a/packages/dashboards/src/lib/configurator/components/widgets/table/scrollType-editor/scroll-type-editor.component.spec.ts b/packages/dashboards/src/lib/configurator/components/widgets/table/scrollType-editor/scroll-type-editor.component.spec.ts
new file mode 100644
index 000000000..66dd02cdf
--- /dev/null
+++ b/packages/dashboards/src/lib/configurator/components/widgets/table/scrollType-editor/scroll-type-editor.component.spec.ts
@@ -0,0 +1,184 @@
+// © 2022 SolarWinds Worldwide, LLC. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing";
+import { FormBuilder } from "@angular/forms";
+
+import { EventBus } from "@nova-ui/bits";
+
+import { NuiDashboardsModule } from "../../../../../dashboards.module";
+import { DynamicComponentCreator } from "../../../../../pizzagna/services/dynamic-component-creator.service";
+import { PizzagnaService } from "../../../../../pizzagna/services/pizzagna.service";
+import { PIZZAGNA_EVENT_BUS } from "../../../../../types";
+import { TableScrollTypeEditorComponent } from "./scroll-type-editor.component";
+import { SimpleChange, SimpleChanges } from "@angular/core";
+import { ScrollType } from "@nova-ui/dashboards";
+import { ScrollTypeEditorService } from "./scroll-type-editor.service";
+
+describe("TableScrollTypeEditorComponent", () => {
+ let component: TableScrollTypeEditorComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ imports: [NuiDashboardsModule],
+ providers: [
+ PizzagnaService,
+ DynamicComponentCreator,
+ {
+ provide: PIZZAGNA_EVENT_BUS,
+ useClass: EventBus,
+ },
+ {
+ provide: FormBuilder,
+ useValue: new FormBuilder(),
+ },
+ {
+ provide: ScrollTypeEditorService,
+ },
+ ],
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(TableScrollTypeEditorComponent);
+ component = fixture.componentInstance;
+ component.ngOnInit();
+ });
+
+ it("should create", () => {
+ expect(component).toBeTruthy();
+ });
+
+ describe("ngOnChanges > ", () => {
+ it("should set the 'paginatorConfiguration' form controls", () => {
+ component.paginatorConfiguration = {
+ pageSize: 20,
+ pageSizeSet: [10, 20, 30],
+ };
+
+ const changes: SimpleChanges = {
+ paginatorConfiguration: {} as SimpleChange,
+ };
+
+ component.ngOnChanges(changes);
+ const paginatorConfigurationFormGroup = component.form.get(
+ "paginatorConfiguration"
+ );
+
+ expect(
+ paginatorConfigurationFormGroup?.get("pageSize")?.value
+ ).toEqual(component.paginatorConfiguration.pageSize);
+ expect(
+ paginatorConfigurationFormGroup?.get("pageSizeSet")?.value
+ ).toEqual(component.paginatorConfiguration.pageSizeSet);
+ });
+
+ it("should set the 'scrollType' form controls", () => {
+ component.scrollType = ScrollType.paginator;
+
+ const changes: SimpleChanges = {
+ scrollType: {} as SimpleChange,
+ };
+
+ component.ngOnChanges(changes);
+
+ const scrollTypeFormControl = component.form
+ .get("paginatorConfiguration")
+ ?.get("scrollType")?.value;
+ expect(scrollTypeFormControl).toEqual(component.scrollType);
+ });
+
+ it("should update 'pageSizeOptions' when there is change in 'paginatorConfiguration'", () => {
+ component.paginatorConfiguration = {
+ pageSize: 20,
+ pageSizeSet: [20, 50, 100],
+ };
+
+ const changes: SimpleChanges = {
+ paginatorConfiguration: {} as SimpleChange,
+ };
+
+ component.ngOnChanges(changes);
+
+ expect(component.paginatorConfiguration.pageSizeSet).toEqual(
+ component.pageSizeOptions
+ );
+ });
+
+ it("should set 'pageSizeSetOptions' according to values from 'paginatorConfiguration.pageSizeSet'", () => {
+ component.paginatorConfiguration = {
+ pageSize: 20,
+ pageSizeSet: [20, 50, 100],
+ };
+
+ const changes: SimpleChanges = {
+ paginatorConfiguration: {} as SimpleChange,
+ };
+
+ component.ngOnChanges(changes);
+
+ component.pageSizeSetOptions.forEach((option) => {
+ component.paginatorConfiguration.pageSizeSet?.forEach(
+ (pageValue) => {
+ if (option.value === pageValue) {
+ expect(option.checked).toBeTrue();
+ }
+ }
+ );
+ });
+ });
+
+ it("should display advanced configuration only for 'scrollType' set to paginator", () => {
+ component.scrollType = ScrollType.virtual;
+
+ const changes: SimpleChanges = {
+ scrollType: {} as SimpleChange,
+ };
+
+ component.ngOnChanges(changes);
+
+ expect(component.hasPaginator).toBeFalse();
+
+ component.scrollType = ScrollType.paginator;
+
+ component.ngOnChanges(changes);
+
+ expect(component.hasPaginator).toBeTrue();
+ });
+
+ it("should set correctly subtitle according to selected 'scrollType'", () => {
+ component.scrollType = ScrollType.virtual;
+
+ const changes: SimpleChanges = {
+ scrollType: {} as SimpleChange,
+ };
+
+ component.ngOnChanges(changes);
+
+ expect(component.subtitle).toEqual(
+ "Scroll Type: " +
+ component.scrollTypeEditorService.loadStrategies.find(
+ (ls) => ls.id === ScrollType.virtual
+ )?.title
+ );
+ });
+ });
+});
diff --git a/packages/dashboards/src/lib/configurator/components/widgets/table/scrollType-editor/scroll-type-editor.component.ts b/packages/dashboards/src/lib/configurator/components/widgets/table/scrollType-editor/scroll-type-editor.component.ts
new file mode 100644
index 000000000..59f1dc522
--- /dev/null
+++ b/packages/dashboards/src/lib/configurator/components/widgets/table/scrollType-editor/scroll-type-editor.component.ts
@@ -0,0 +1,279 @@
+// © 2022 SolarWinds Worldwide, LLC. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import {
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ EventEmitter,
+ Input,
+ OnChanges,
+ OnDestroy,
+ OnInit,
+ Output,
+ SimpleChanges,
+} from "@angular/core";
+import {
+ AbstractControl,
+ FormBuilder,
+ FormControl,
+ FormGroup,
+ Validators,
+} from "@angular/forms";
+import get from "lodash/get";
+import { Subject } from "rxjs";
+import { takeUntil } from "rxjs/operators";
+
+import {
+ ITableWidgetPaginatorConfig,
+ ScrollType,
+} from "../../../../../components/table-widget/types";
+import { IHasChangeDetector, IHasForm } from "../../../../../types";
+import { ConfiguratorHeadingService } from "../../../../services/configurator-heading.service";
+import { ScrollTypeEditorService } from "./scroll-type-editor.service";
+
+export interface IPageSizeSetMenuOption {
+ value: number;
+ checked: boolean;
+}
+@Component({
+ selector: "nui-scroll-type-editor-component",
+ templateUrl: "scroll-type-editor.component.html",
+ styleUrls: ["scroll-type-editor.component.less"],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class TableScrollTypeEditorComponent
+ implements OnInit, OnChanges, OnDestroy, IHasForm, IHasChangeDetector
+{
+ static lateLoadKey = "TableScrollTypeEditorComponent";
+
+ @Input() paginatorConfiguration: ITableWidgetPaginatorConfig;
+ @Input() hasVirtualScroll: boolean;
+ @Input() scrollType: ScrollType = ScrollType.virtual;
+
+ @Output() formReady = new EventEmitter();
+
+ public form: FormGroup;
+ private onDestroy$ = new Subject();
+
+ private pageSizeSetAll = [
+ 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80, 90, 100,
+ ];
+ public pageSizeSetOptions: IPageSizeSetMenuOption[] = [];
+ public pageSizeOptions: number[] = [];
+ public subtitle = "";
+ public isExpanderOpen = false;
+ public displayPageSizeSetErrorMessage = false;
+ public displayPageSizeErrorMessage = false;
+
+ public scrollTypeFormControl?: AbstractControl | null;
+ public pageSizeSetFormControl?: AbstractControl | null;
+ public pageSizeFormControl?: AbstractControl | null;
+
+ constructor(
+ private formBuilder: FormBuilder,
+ public configuratorHeading: ConfiguratorHeadingService,
+ public changeDetector: ChangeDetectorRef,
+ public scrollTypeEditorService: ScrollTypeEditorService
+ ) {
+ this.updatePaginatorSelectOptions(this.pageSizeSetAll, false);
+ }
+
+ public ngOnChanges(changes: SimpleChanges): void {
+ if (changes.scrollType) {
+ this.scrollTypeFormControl?.setValue(this.scrollType, {
+ emitEvent: false,
+ });
+
+ this.changeExpanderState(false);
+ this.updateSubtitle();
+ this.updateValidators();
+ }
+
+ if (changes.paginatorConfiguration) {
+ this.updatePaginatorSelectOptions(
+ this.paginatorConfiguration.pageSizeSet || [],
+ true
+ );
+ this.updateDefaultPageSizeOptions(
+ this.paginatorConfiguration.pageSizeSet || []
+ );
+
+ this.pageSizeSetFormControl?.setValue(
+ this.paginatorConfiguration.pageSizeSet,
+ {
+ emitEvent: false,
+ }
+ );
+
+ this.pageSizeFormControl?.setValue(
+ this.paginatorConfiguration.pageSize,
+ {
+ emitEvent: true,
+ }
+ );
+ }
+
+ this.changeDetector.detectChanges();
+ }
+
+ public ngOnInit(): void {
+ this.form = this.formBuilder.group({
+ paginatorConfiguration: this.formBuilder.group({
+ scrollType: get(this.scrollType, "", ScrollType.virtual),
+ pageSize: get(this.paginatorConfiguration, "pageSize", 10),
+ pageSizeSet: new FormControl(
+ get(
+ this.paginatorConfiguration,
+ "pageSizeSet",
+ [10, 20, 50]
+ )
+ ),
+ }),
+ });
+
+ this.scrollTypeFormControl = this.form
+ .get("paginatorConfiguration")
+ ?.get("scrollType");
+
+ this.pageSizeSetFormControl = this.form
+ .get("paginatorConfiguration")
+ ?.get("pageSizeSet");
+
+ this.pageSizeFormControl = this.form
+ .get("paginatorConfiguration")
+ ?.get("pageSize");
+
+ this.form.valueChanges
+ .pipe(takeUntil(this.onDestroy$))
+ .subscribe((val) => {
+ this.displayPageSizeErrorMessage =
+ !val.paginatorConfiguration.pageSize;
+ });
+
+ this.updateSubtitle();
+
+ this.scrollTypeFormControl?.valueChanges
+ .pipe(takeUntil(this.onDestroy$))
+ .subscribe((val) => {
+ this.updateSubtitle();
+ this.updateValidators();
+ });
+
+ this.formReady.emit(this.form);
+ }
+
+ public onPageSizeSetChange(item: IPageSizeSetMenuOption): void {
+ const option = this.pageSizeSetOptions.find(
+ (n) => n.value === item.value
+ );
+ if (option) {
+ option.checked = !item.checked;
+ }
+
+ this.displayPageSizeSetErrorMessage =
+ this.pageSizeSetOptions.filter((o) => o.checked).length === 0;
+
+ this.emitUpdatedSelectedOptions();
+ }
+
+ public get hasPaginator() {
+ return this.scrollTypeFormControl?.value === ScrollType.paginator;
+ }
+
+ public accordionToggle(isOpened: boolean) {
+ this.changeExpanderState(false);
+ }
+
+ public changeExpanderState(isOpen: boolean) {
+ this.isExpanderOpen = isOpen;
+ }
+
+ private updateSubtitle(): void {
+ this.subtitle = this.scrollTypeEditorService.setAccordionSubtitleValues(
+ this.hasVirtualScroll,
+ this.scrollTypeFormControl?.value
+ );
+ }
+
+ private updateValidators() {
+ if (this.hasPaginator) {
+ this.pageSizeFormControl?.addValidators(Validators.required);
+ this.pageSizeSetFormControl?.addValidators(Validators.required);
+ } else {
+ this.pageSizeFormControl?.clearValidators();
+ this.pageSizeSetFormControl?.clearValidators();
+ }
+
+ this.updatePaginatorSelectOptions(
+ this.pageSizeSetFormControl?.value,
+ true
+ );
+ this.updateDefaultPageSizeOptions(this.pageSizeSetFormControl?.value);
+
+ this.pageSizeFormControl?.updateValueAndValidity();
+ this.pageSizeSetFormControl?.updateValueAndValidity();
+ }
+
+ private emitUpdatedSelectedOptions() {
+ let filteredPageSizeSet = this.pageSizeSetOptions
+ .filter((o) => o.checked)
+ .map((o) => o.value);
+
+ this.updateDefaultPageSizeOptions(filteredPageSizeSet);
+ this.pageSizeSetFormControl?.setValue(filteredPageSizeSet, {
+ emitEvent: false,
+ });
+ }
+
+ private updateDefaultPageSizeOptions(options: number[]) {
+ this.pageSizeOptions = options;
+ }
+
+ private updatePaginatorSelectOptions(
+ options: number[],
+ isChecked: boolean
+ ) {
+ this.clearPageSizeSetOptions();
+
+ options.forEach((o) => {
+ const option = this.pageSizeSetOptions.find((po) => po.value === o);
+ if (option) {
+ option.checked = isChecked;
+ } else {
+ this.pageSizeSetOptions.push({
+ value: o,
+ checked: isChecked,
+ });
+ }
+ });
+ }
+
+ private clearPageSizeSetOptions() {
+ this.pageSizeSetOptions.forEach((option) => {
+ option.checked = false;
+ });
+ }
+
+ public ngOnDestroy(): void {
+ this.onDestroy$.next();
+ this.onDestroy$.complete();
+ }
+}
diff --git a/packages/dashboards/src/lib/configurator/components/widgets/table/scrollType-editor/scroll-type-editor.service.ts b/packages/dashboards/src/lib/configurator/components/widgets/table/scrollType-editor/scroll-type-editor.service.ts
new file mode 100644
index 000000000..e46c1cb2b
--- /dev/null
+++ b/packages/dashboards/src/lib/configurator/components/widgets/table/scrollType-editor/scroll-type-editor.service.ts
@@ -0,0 +1,40 @@
+import { Injectable } from "@angular/core";
+import { ScrollType } from "./../../../../../components/table-widget/types";
+
+@Injectable()
+export class ScrollTypeEditorService {
+ public loadStrategies = [
+ {
+ id: ScrollType.virtual,
+ title: $localize`Virtual scroll`,
+ },
+ {
+ id: ScrollType.paginator,
+ title: $localize`Paginator`,
+ },
+ {
+ id: ScrollType.default,
+ title: $localize`Default scroll`,
+ },
+ ];
+
+ public setAccordionSubtitleValues(
+ hasVirtualScroll: boolean,
+ scrollType: ScrollType
+ ): string {
+ const prefix = $localize`Scroll Type: `;
+ const result = hasVirtualScroll
+ ? `${prefix} ${this.getScrollTypeTitle(ScrollType.virtual)}`
+ : `${prefix} ${this.getScrollTypeTitle(scrollType)}`;
+
+ return result;
+ }
+
+ public getScrollTypeTitle(scrollType: ScrollType): string {
+ const result =
+ this.loadStrategies.find((ls) => ls.id === scrollType)?.title ||
+ $localize`Unknown`;
+
+ return result;
+ }
+}
diff --git a/packages/dashboards/src/lib/configurator/configurator.module.ts b/packages/dashboards/src/lib/configurator/configurator.module.ts
index 6e0046ff6..09412053b 100644
--- a/packages/dashboards/src/lib/configurator/configurator.module.ts
+++ b/packages/dashboards/src/lib/configurator/configurator.module.ts
@@ -133,6 +133,8 @@ import { KpiWidgetColorService } from "./services/kpi-widget-color.service";
import { ConfiguratorHeadingService } from "./services/public-api";
import { WidgetClonerService } from "./services/widget-cloner.service";
import { WidgetEditorService } from "./services/widget-editor.service";
+import { TableScrollTypeEditorComponent } from "./components/widgets/table/scrollType-editor/scroll-type-editor.component";
+import { ScrollTypeEditorService } from "./components/widgets/table/scrollType-editor/scroll-type-editor.service";
/* eslint-enable max-len */
const entryComponents: IComponentWithLateLoadKey[] = [
@@ -149,6 +151,7 @@ const entryComponents: IComponentWithLateLoadKey[] = [
ProportionalChartOptionsEditorComponent,
ProportionalChartOptionsEditorV2Component,
TableFiltersEditorComponent,
+ TableScrollTypeEditorComponent,
TimeseriesMetadataConfigurationComponent,
TimeseriesSeriesCollectionConfigurationComponent,
TableColumnsConfigurationComponent,
@@ -252,6 +255,7 @@ const exportedDeclarations = [
KpiWidgetColorService,
TimeseriesChartPresetService,
TimeseriesScalesService,
+ ScrollTypeEditorService,
],
exports: exportedDeclarations,
})
diff --git a/packages/dashboards/src/lib/configurator/services/converters/table/mocks.ts b/packages/dashboards/src/lib/configurator/services/converters/table/mocks.ts
index d7fb85045..bfe77d0f4 100644
--- a/packages/dashboards/src/lib/configurator/services/converters/table/mocks.ts
+++ b/packages/dashboards/src/lib/configurator/services/converters/table/mocks.ts
@@ -18,6 +18,7 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
+import { ScrollType } from "./../../../../components/table-widget/types";
import { DEFAULT_PIZZAGNA_ROOT } from "../../../../services/types";
import { PizzagnaLayer } from "../../../../types";
@@ -60,7 +61,12 @@ export const TABLE_WIDGET_PREVIEW_PIZZAGNA = {
descendantSorting: true,
sortBy: "column1",
},
- hasVirtualScroll: true,
+ scrollType: ScrollType.paginator,
+ paginatorConfiguration: {
+ pageSize: 10,
+ pageSizeSet: [10, 20, 30],
+ },
+ hasVirtualScroll: false,
},
},
},
@@ -83,7 +89,12 @@ export const EDITOR_PIZZAGNA = {
componentType: "WidgetConfiguratorSectionComponent",
properties: {
headerText: "Presentation",
- nodes: ["titleAndDescription", "dataSource", "filters"],
+ nodes: [
+ "titleAndDescription",
+ "dataSource",
+ "filters",
+ "scrollType",
+ ],
},
},
titleAndDescription: {
@@ -119,6 +130,15 @@ export const EDITOR_PIZZAGNA = {
},
},
},
+ scrollType: {
+ id: "scrollType",
+ componentType: "TableScrollTypeEditorComponent",
+ providers: {
+ NOVA_TABLE_SCROLL_TYPE_CONVERTER: {
+ providerId: "NOVA_TABLE_SCROLL_TYPE_CONVERTER",
+ },
+ },
+ },
columns: {
id: "columns",
componentType: "TableColumnsConfigurationComponent",
diff --git a/packages/dashboards/src/lib/configurator/services/converters/table/table-filters-converter.service.spec.ts b/packages/dashboards/src/lib/configurator/services/converters/table/table-filters-converter.service.spec.ts
index e3d3db4ac..8835e9146 100644
--- a/packages/dashboards/src/lib/configurator/services/converters/table/table-filters-converter.service.spec.ts
+++ b/packages/dashboards/src/lib/configurator/services/converters/table/table-filters-converter.service.spec.ts
@@ -101,9 +101,11 @@ describe("TableFiltersConverterService >", () => {
};
expectedPreviewPizzagna.table.properties.configuration.sorterConfiguration =
mockedSortingState;
+
component.form
.get("sorterConfiguration")
?.patchValue(mockedSortingState);
+
tick(0);
expect(service.updatePreview).toHaveBeenCalledWith(
expectedPreviewPizzagna
diff --git a/packages/dashboards/src/lib/configurator/services/converters/table/table-filters-converter.service.ts b/packages/dashboards/src/lib/configurator/services/converters/table/table-filters-converter.service.ts
index 38bcbf625..6920526b0 100644
--- a/packages/dashboards/src/lib/configurator/services/converters/table/table-filters-converter.service.ts
+++ b/packages/dashboards/src/lib/configurator/services/converters/table/table-filters-converter.service.ts
@@ -69,18 +69,17 @@ export class TableFiltersConverterService
}
public toPreview(form: FormGroup): void {
- form.valueChanges
- .pipe(takeUntil(this.destroy$))
- .subscribe((filters) => {
- let preview = this.getPreview();
- preview = immutableSet(
- preview,
- "table.properties.configuration.sorterConfiguration",
- filters.sorterConfiguration
- );
- this.updatePreview(preview);
- // we need to update form with columns that are available
- this.buildForm();
- });
+ form.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((form) => {
+ let preview = this.getPreview();
+ preview = immutableSet(
+ preview,
+ "table.properties.configuration.sorterConfiguration",
+ form.sorterConfiguration
+ );
+
+ this.updatePreview(preview);
+ // we need to update form with columns that are available
+ this.buildForm();
+ });
}
}
diff --git a/packages/dashboards/src/lib/configurator/services/converters/table/table-scroll-type-converter.service.spec.ts b/packages/dashboards/src/lib/configurator/services/converters/table/table-scroll-type-converter.service.spec.ts
new file mode 100644
index 000000000..88b866c30
--- /dev/null
+++ b/packages/dashboards/src/lib/configurator/services/converters/table/table-scroll-type-converter.service.spec.ts
@@ -0,0 +1,131 @@
+// © 2022 SolarWinds Worldwide, LLC. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import { fakeAsync, tick } from "@angular/core/testing";
+import { FormBuilder, FormGroup } from "@angular/forms";
+
+import { EventBus, IEvent } from "@nova-ui/bits";
+
+import { DynamicComponentCreator } from "../../../../pizzagna/services/dynamic-component-creator.service";
+import { PizzagnaService } from "../../../../pizzagna/services/pizzagna.service";
+import { PreviewService } from "../../preview.service";
+import { EDITOR_PIZZAGNA, TABLE_WIDGET_PREVIEW_PIZZAGNA } from "./mocks";
+import { TableScrollTypeConverterService } from "./table-scroll-type-converter.service";
+import { ScrollType } from "@nova-ui/dashboards";
+
+class MockComponent {
+ public static lateLoadKey = "MockComponent";
+ public form: FormGroup;
+
+ constructor(private formBuilder: FormBuilder) {
+ this.form = formBuilder.group({
+ paginatorConfiguration: formBuilder.group({
+ scrollType: ScrollType.virtual,
+ pageSize: 0,
+ pageSizeSet: [],
+ }),
+ });
+ }
+}
+
+const mockedPaginatorState = {
+ pageSize: 20,
+ pageSizeSet: [10, 20, 30, 40],
+ scrollType: ScrollType.paginator,
+};
+
+describe("TableScrollTypeConverterService >", () => {
+ const eventBus = new EventBus();
+ const formBuilder = new FormBuilder();
+ const component = new MockComponent(formBuilder);
+ const previewService = new PreviewService();
+ const dynamicComponentCreator = new DynamicComponentCreator();
+ const pizzagnaService = new PizzagnaService(
+ eventBus,
+ dynamicComponentCreator
+ );
+
+ let service: TableScrollTypeConverterService;
+
+ beforeEach(() => {
+ previewService.preview =
+ TABLE_WIDGET_PREVIEW_PIZZAGNA.pizzagna.configuration;
+ pizzagnaService.pizzagna = EDITOR_PIZZAGNA;
+ service = new TableScrollTypeConverterService(
+ eventBus,
+ previewService,
+ pizzagnaService
+ );
+ service.setComponent(component as any, "");
+ service.ngAfterViewInit();
+ });
+
+ it("should have component set", () => {
+ expect(service.component).toBeDefined();
+ });
+
+ it("should properly pass data from preview to form pizzagna", () => {
+ const paginatorConfigurationInFormPizzagna =
+ pizzagnaService.pizzagna.data.scrollType.properties
+ ?.paginatorConfiguration;
+ const paginatorConfigurationInPreviewPizzagna =
+ TABLE_WIDGET_PREVIEW_PIZZAGNA.pizzagna.configuration.table
+ .properties.configuration.paginatorConfiguration;
+
+ const scrollTypeInFormPizzagna =
+ pizzagnaService.pizzagna.data.scrollType.properties?.scrollType;
+ const scrollTypeInPreviewPizzagna =
+ TABLE_WIDGET_PREVIEW_PIZZAGNA.pizzagna.configuration.table
+ .properties.configuration.scrollType;
+
+ const hasVirtualScrollInFormPizzagna =
+ pizzagnaService.pizzagna.data.scrollType.properties
+ ?.hasVirtualScroll;
+ const shasVirtualScrollInPreviewPizzagna =
+ TABLE_WIDGET_PREVIEW_PIZZAGNA.pizzagna.configuration.table
+ .properties.configuration.hasVirtualScroll;
+
+ expect(paginatorConfigurationInPreviewPizzagna).toEqual(
+ paginatorConfigurationInFormPizzagna
+ );
+ expect(scrollTypeInPreviewPizzagna).toEqual(scrollTypeInFormPizzagna);
+ expect(shasVirtualScrollInPreviewPizzagna).toEqual(
+ hasVirtualScrollInFormPizzagna
+ );
+ });
+
+ it("should properly update preview from form in editor", fakeAsync(() => {
+ spyOn(service, "updatePreview");
+ const expectedPreviewPizzagna = {
+ ...TABLE_WIDGET_PREVIEW_PIZZAGNA.pizzagna.configuration,
+ };
+ expectedPreviewPizzagna.table.properties.configuration.paginatorConfiguration =
+ mockedPaginatorState;
+
+ component.form
+ .get("paginatorConfiguration")
+ ?.patchValue(mockedPaginatorState);
+
+ tick(0);
+ expect(service.updatePreview).toHaveBeenCalledWith(
+ expectedPreviewPizzagna
+ );
+ }));
+});
diff --git a/packages/dashboards/src/lib/configurator/services/converters/table/table-scroll-type-converter.service.ts b/packages/dashboards/src/lib/configurator/services/converters/table/table-scroll-type-converter.service.ts
new file mode 100644
index 000000000..ea82bc5eb
--- /dev/null
+++ b/packages/dashboards/src/lib/configurator/services/converters/table/table-scroll-type-converter.service.ts
@@ -0,0 +1,112 @@
+// © 2022 SolarWinds Worldwide, LLC. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import { AfterViewInit, Inject, Injectable } from "@angular/core";
+import { FormGroup } from "@angular/forms";
+import { takeUntil } from "rxjs/operators";
+
+import { EventBus, IEvent, immutableSet } from "@nova-ui/bits";
+
+import { PizzagnaService } from "../../../../pizzagna/services/pizzagna.service";
+import { PizzagnaLayer, PIZZAGNA_EVENT_BUS } from "../../../../types";
+import { PreviewService } from "../../preview.service";
+import { BaseConverter } from "../base-converter";
+
+@Injectable()
+export class TableScrollTypeConverterService
+ extends BaseConverter
+ implements AfterViewInit
+{
+ constructor(
+ @Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus,
+ previewService: PreviewService,
+ pizzagnaService: PizzagnaService
+ ) {
+ super(eventBus, previewService, pizzagnaService);
+ }
+
+ public ngAfterViewInit(): void {
+ super.ngAfterViewInit();
+ }
+
+ public buildForm(): void {
+ let formPizzagna = this.pizzagnaService.pizzagna;
+
+ const table = this.getPreview()?.table;
+
+ const paginatorConfiguration =
+ table?.properties?.configuration?.paginatorConfiguration;
+ const hasVirtualScroll =
+ table?.properties?.configuration?.hasVirtualScroll;
+ const scrollType = table?.properties?.configuration?.scrollType;
+
+ formPizzagna = immutableSet(
+ formPizzagna,
+ `${PizzagnaLayer.Data}.scrollType.properties.paginatorConfiguration`,
+ paginatorConfiguration
+ );
+
+ formPizzagna = immutableSet(
+ formPizzagna,
+ `${PizzagnaLayer.Data}.scrollType.properties.hasVirtualScroll`,
+ hasVirtualScroll
+ );
+
+ formPizzagna = immutableSet(
+ formPizzagna,
+ `${PizzagnaLayer.Data}.scrollType.properties.scrollType`,
+ scrollType
+ );
+
+ this.updateFormPizzagna(formPizzagna);
+ }
+
+ public toPreview(form: FormGroup): void {
+ form.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((form) => {
+ let preview = this.getPreview();
+
+ preview = immutableSet(
+ preview,
+ "table.properties.configuration.hasVirtualScroll",
+ false
+ );
+
+ preview = immutableSet(
+ preview,
+ "table.properties.configuration.scrollType",
+ form.paginatorConfiguration.scrollType
+ );
+
+ preview = immutableSet(
+ preview,
+ "table.properties.configuration.paginatorConfiguration.pageSizeSet",
+ form.paginatorConfiguration.pageSizeSet
+ );
+
+ preview = immutableSet(
+ preview,
+ "table.properties.configuration.paginatorConfiguration.pageSize",
+ form.paginatorConfiguration.pageSize
+ );
+
+ this.updatePreview(preview);
+ });
+ }
+}
diff --git a/packages/dashboards/src/lib/dashboards.module.ts b/packages/dashboards/src/lib/dashboards.module.ts
index 8b8fd58b2..1687272bd 100644
--- a/packages/dashboards/src/lib/dashboards.module.ts
+++ b/packages/dashboards/src/lib/dashboards.module.ts
@@ -35,6 +35,7 @@ import {
NuiIconModule,
NuiImageModule,
NuiMenuModule,
+ NuiPaginatorModule,
NuiPopoverModule,
NuiPopupModule,
NuiProgressModule,
@@ -213,6 +214,7 @@ const entryComponents: IComponentWithLateLoadKey[] = [
NuiRiskScoreModule,
NuiSelectModule,
NuiPopoverModule,
+ NuiPaginatorModule,
],
declarations: dashboardComponents,
providers: [
diff --git a/packages/dashboards/src/lib/services/provider-registry.service.ts b/packages/dashboards/src/lib/services/provider-registry.service.ts
index 81eadbeff..78f60a645 100644
--- a/packages/dashboards/src/lib/services/provider-registry.service.ts
+++ b/packages/dashboards/src/lib/services/provider-registry.service.ts
@@ -60,6 +60,8 @@ import { GenericConverterService } from "../configurator/services/converters/sha
import { TitleAndDescriptionConverterService } from "../configurator/services/converters/shared/title-and-description-converter/title-and-description-converter.service";
import { TableColumnsConverterService } from "../configurator/services/converters/table/table-columns-converter.service";
import { TableFiltersConverterService } from "../configurator/services/converters/table/table-filters-converter.service";
+import { TableScrollTypeConverterService } from "../configurator/services/converters/table/table-scroll-type-converter.service";
+
import { TimeseriesMetadataConverterService } from "../configurator/services/converters/timeseries/timeseries-metadata-converter.service";
import { TimeseriesSeriesConverterService } from "../configurator/services/converters/timeseries/timeseries-series-converter.service";
import { TimeseriesTileIndicatorDataConverterService } from "../configurator/services/converters/timeseries/timeseries-tile-indicator-data-converter.service";
@@ -114,6 +116,7 @@ import {
NOVA_URL_INTERACTION_HANDLER,
NOVA_VIRTUAL_VIEWPORT_MANAGER,
NOVA_RISK_SCORE_FORMATTERS_REGISTRY,
+ NOVA_TABLE_SCROLL_TYPE_CONVERTER,
} from "./types";
import { UrlInteractionService } from "./url-interaction.service";
import { WidgetConfigurationService } from "./widget-configuration.service";
@@ -254,6 +257,11 @@ export class ProviderRegistryService {
useClass: TableFiltersConverterService,
deps: [PIZZAGNA_EVENT_BUS, PreviewService, PizzagnaService],
},
+ [NOVA_TABLE_SCROLL_TYPE_CONVERTER]: {
+ provide: CONFIGURATOR_CONVERTER,
+ useClass: TableScrollTypeConverterService,
+ deps: [PIZZAGNA_EVENT_BUS, PreviewService, PizzagnaService],
+ },
[NOVA_LOADING_ADAPTER]: {
provide: LoadingAdapter,
deps: [PIZZAGNA_EVENT_BUS, PizzagnaService],
diff --git a/packages/dashboards/src/lib/services/types.ts b/packages/dashboards/src/lib/services/types.ts
index b7df49841..818feac91 100644
--- a/packages/dashboards/src/lib/services/types.ts
+++ b/packages/dashboards/src/lib/services/types.ts
@@ -124,6 +124,8 @@ export const NOVA_TIMESERIES_SERIES_CONVERTER =
export const NOVA_DASHBOARD_EVENT_PROXY = "NOVA_DASHBOARD_EVENT_PROXY";
export const NOVA_TABLE_COLUMNS_CONVERTER = "NOVA_TABLE_COLUMNS_CONVERTER";
export const NOVA_TABLE_FILTERS_CONVERTER = "NOVA_TABLE_FILTERS_CONVERTER";
+export const NOVA_TABLE_SCROLL_TYPE_CONVERTER =
+ "NOVA_TABLE_SCROLL_TYPE_CONVERTER";
export const NOVA_TABLE_DATASOURCE_ADAPTER = "NOVA_TABLE_DATASOURCE_ADAPTER";
export const NOVA_GENERIC_CONVERTER = "NOVA_GENERIC_CONVERTER";
export const NOVA_GENERIC_ARRAY_CONVERTER = "NOVA_GENERIC_ARRAY_CONVERTER";
diff --git a/packages/dashboards/src/lib/widget-types/table/table-configurator.ts b/packages/dashboards/src/lib/widget-types/table/table-configurator.ts
index e38804993..020eb6bec 100644
--- a/packages/dashboards/src/lib/widget-types/table/table-configurator.ts
+++ b/packages/dashboards/src/lib/widget-types/table/table-configurator.ts
@@ -19,6 +19,7 @@
// THE SOFTWARE.
/* eslint-disable max-len */
+import { TableScrollTypeEditorComponent } from "../../configurator/components/widgets/table/scrollType-editor/scroll-type-editor.component";
import { FormStackComponent } from "../../configurator/components/form-stack/form-stack.component";
import { WidgetConfiguratorSectionComponent } from "../../configurator/components/widget-configurator-section/widget-configurator-section.component";
import { DataSourceConfigurationComponent } from "../../configurator/components/widgets/configurator-items/data-source-configuration/data-source-configuration.component";
@@ -33,6 +34,7 @@ import {
NOVA_TABLE_COLUMNS_CONVERTER,
NOVA_TABLE_FILTERS_CONVERTER,
NOVA_TABLE_FORMATTERS_REGISTRY,
+ NOVA_TABLE_SCROLL_TYPE_CONVERTER,
NOVA_TITLE_AND_DESCRIPTION_CONVERTER,
} from "../../services/types";
import { IPizzagna, PizzagnaLayer, WellKnownProviders } from "../../types";
@@ -68,7 +70,7 @@ export const tableConfigurator: IPizzagna = {
properties: {
headerText: $localize`Presentation`,
// references to other components laid out in this form
- nodes: ["titleAndDescription", "filters"],
+ nodes: ["titleAndDescription", "filters", "scrollType"],
},
},
// /presentation/titleAndDescription
@@ -82,7 +84,7 @@ export const tableConfigurator: IPizzagna = {
},
},
},
- // /presentation/filters - configuration of built-in filters like search, sorting and pagination
+ // /presentation/filters - !WARNING! configuration of built-in sorting, naming is obsolete
filters: {
id: "filters",
componentType: TableFiltersEditorComponent.lateLoadKey,
@@ -92,6 +94,16 @@ export const tableConfigurator: IPizzagna = {
},
},
},
+ // /presentation/scrollType - configuration of built-in pagination
+ scrollType: {
+ id: "scrollType",
+ componentType: TableScrollTypeEditorComponent.lateLoadKey,
+ providers: {
+ [WellKnownProviders.Converter]: {
+ providerId: NOVA_TABLE_SCROLL_TYPE_CONVERTER,
+ },
+ },
+ },
refresher: REFRESHER_CONFIGURATOR,
// /dataAndCalculations
dataAndCalculations: {