From f3fe98436e670be27ea71b93c652f1dfead7383f Mon Sep 17 00:00:00 2001 From: Sean Date: Thu, 3 Sep 2020 17:10:43 +0200 Subject: [PATCH] Vscroll for user import Allows to import giant sets of users as CSV. Tested 500k. The client is fine. The python server and the SQL data base really do not like that. --- .../list-view-table.component.scss | 10 + client/src/app/site/base/base-import-list.ts | 46 ++-- .../user-import-list.component.html | 204 ++++-------------- .../user-import-list.component.scss | 8 + .../user-import/user-import-list.component.ts | 59 ++++- client/src/styles.scss | 10 - 6 files changed, 149 insertions(+), 188 deletions(-) create mode 100644 client/src/app/site/users/components/user-import/user-import-list.component.scss diff --git a/client/src/app/shared/components/list-view-table/list-view-table.component.scss b/client/src/app/shared/components/list-view-table/list-view-table.component.scss index 0c3e27635..395e1e535 100644 --- a/client/src/app/shared/components/list-view-table/list-view-table.component.scss +++ b/client/src/app/shared/components/list-view-table/list-view-table.component.scss @@ -17,6 +17,16 @@ $pbl-height: var(--pbl-height); display: block; } + /** + * Compilcated ngrid hack: The meta row won't disappear (just like that) + * Select the first ever container pbl-ngrid-container div and hide + */ + .pbl-ngrid-container { + > div { + height: 0; + } + } + .vscroll-list-view { flex: 1 1 auto; height: 100%; diff --git a/client/src/app/site/base/base-import-list.ts b/client/src/app/site/base/base-import-list.ts index e783ca690..7096dba17 100644 --- a/client/src/app/site/base/base-import-list.ts +++ b/client/src/app/site/base/base-import-list.ts @@ -5,7 +5,8 @@ import { MatTable, MatTableDataSource } from '@angular/material/table'; import { Title } from '@angular/platform-browser'; import { TranslateService } from '@ngx-translate/core'; -import { auditTime } from 'rxjs/operators'; +import { createDS, PblDataSource } from '@pebula/ngrid'; +import { auditTime, distinctUntilChanged } from 'rxjs/operators'; import { BaseImportService, NewEntry, ValueLabelCombination } from 'app/core/ui-services/base-import.service'; import { BaseModel } from 'app/shared/models/base/base-model'; @@ -20,6 +21,11 @@ export abstract class BaseImportListComponentDirective exte */ public dataSource: MatTableDataSource>; + /** + * Data source for ngrid + */ + public vScrollDataSource: PblDataSource>; + /** * Helper function for previews */ @@ -136,14 +142,23 @@ export abstract class BaseImportListComponentDirective exte */ public initTable(): void { this.dataSource = new MatTableDataSource(); - this.setFilter(); - this.importer - .getNewEntries() - .pipe(auditTime(100)) - .subscribe(newEntries => { - this.dataSource.data = newEntries; + + const entryObservable = this.importer.getNewEntries(); + this.subscriptions.push( + entryObservable.pipe(distinctUntilChanged(), auditTime(100)).subscribe(newEntries => { + if (newEntries?.length) { + this.dataSource.data = newEntries; + } this.hasFile = newEntries.length > 0; - }); + }) + ); + + this.vScrollDataSource = createDS>() + .keepAlive() + .onTrigger(() => entryObservable) + .create(); + + this.setFilter(); } /** @@ -180,21 +195,26 @@ export abstract class BaseImportListComponentDirective exte public setFilter(): void { this.dataSource.filter = ''; if (this.shown === 'all') { - this.dataSource.filterPredicate = (data, filter) => { - return true; - }; + this.dataSource.filterPredicate = () => true; + this.vScrollDataSource.setFilter(); } else if (this.shown === 'noerror') { - this.dataSource.filterPredicate = (data, filter) => { + const noErrorFilter = data => { if (data.status === 'done') { return true; } else if (data.status !== 'error') { return true; } }; + + this.dataSource.filterPredicate = noErrorFilter; + this.vScrollDataSource.setFilter(noErrorFilter); } else if (this.shown === 'error') { - this.dataSource.filterPredicate = (data, filter) => { + const hasErrorFilter = data => { return !!data.errors.length || data.hasDuplicates; }; + + this.dataSource.filterPredicate = hasErrorFilter; + this.vScrollDataSource.setFilter(hasErrorFilter); } this.dataSource.filter = 'X'; // TODO: This is just a bogus non-null string to trigger the filter } diff --git a/client/src/app/site/users/components/user-import/user-import-list.component.html b/client/src/app/site/users/components/user-import/user-import-list.component.html index c42c2a2b8..9fa75eeda 100644 --- a/client/src/app/site/users/components/user-import/user-import-list.component.html +++ b/client/src/app/site/users/components/user-import/user-import-list.component.html @@ -51,7 +51,7 @@
- + {{ entry | translate }},
@@ -126,9 +126,9 @@ - +

{{ 'Preview' | translate }}

-
+
  @@ -151,168 +151,54 @@
{{ 'After verifiy the preview click on "import" please (see top right).' | translate }}
+ {{ 'Show all' | translate }} {{ 'Show errors only' | translate }} {{ 'Show correct entries only' | translate }} -
- - - - - -
- - {{ getActionIcon(entry) }} - - - warning - -
-
- - {{ getActionIcon(entry) }} - -
-
- - {{ getActionIcon(entry) }} - -
-
-
- - - {{ 'Title' | translate }} - - - - warning - -   - - {{ entry.newEntry.title }} - - +
+ + +
+ +
- - - {{ 'Given name' | translate }} - - - - warning - -   - - {{ entry.newEntry.first_name }} - - - - - {{ 'Surname' | translate }} - - - - warning - -   - - {{ entry.newEntry.last_name }} - - - - - {{ 'Structure level' | translate }} - {{ entry.newEntry.structure_level }} - - - - {{ 'Participant number' | translate }} - {{ entry.newEntry.number }} - - - - - {{ 'Groups' | translate }} - -
- - - warning - - - - {{ group.name }} - add -   - -
-
-
- - - {{ 'Comment' | translate }} - {{ entry.newEntry.comment }} - - - - {{ 'Is active' | translate }} - - - - - - - {{ 'Is present' | translate }} - - - - - - - {{ 'Is committee' | translate }} - - - - - - - {{ 'Initial password' | translate }} - {{ entry.newEntry.default_password }} - - - - {{ 'Email' | translate }} - {{ entry.newEntry.email }} - - - - {{ 'Username' | translate }} - {{ entry.newEntry.username }} - - - - {{ 'Gender' | translate }} - {{ entry.newEntry.gender }} - - - - {{ 'Vote weight' | translate }} - {{ entry.newEntry.vote_weight }} - - - - -
+ +
+
+ + {{ getActionIcon(entry) }} + + + warning + +
+
+ + {{ getActionIcon(entry) }} + +
+
+ + {{ getActionIcon(entry) }} + +
+
+
diff --git a/client/src/app/site/users/components/user-import/user-import-list.component.scss b/client/src/app/site/users/components/user-import/user-import-list.component.scss new file mode 100644 index 000000000..a993beed9 --- /dev/null +++ b/client/src/app/site/users/components/user-import/user-import-list.component.scss @@ -0,0 +1,8 @@ +.import-preview-table { + display: block; + height: calc(100vh - 200px); +} + +.pbl-ngrid-row { + height: 50px; +} diff --git a/client/src/app/site/users/components/user-import/user-import-list.component.ts b/client/src/app/site/users/components/user-import/user-import-list.component.ts index be4987692..7e9dde1da 100644 --- a/client/src/app/site/users/components/user-import/user-import-list.component.ts +++ b/client/src/app/site/users/components/user-import/user-import-list.component.ts @@ -1,9 +1,10 @@ -import { Component } from '@angular/core'; +import { Component, ViewEncapsulation } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; import { MatSnackBar } from '@angular/material/snack-bar'; import { Title } from '@angular/platform-browser'; import { TranslateService } from '@ngx-translate/core'; +import { columnFactory, PblColumnDefinition } from '@pebula/ngrid'; import { NewEntry } from 'app/core/ui-services/base-import.service'; import { CsvExportService } from 'app/core/ui-services/csv-export.service'; @@ -16,12 +17,14 @@ import { UserImportService } from '../../services/user-import.service'; */ @Component({ selector: 'os-user-import-list', - templateUrl: './user-import-list.component.html' + templateUrl: './user-import-list.component.html', + styleUrls: ['./user-import-list.component.scss'], + encapsulation: ViewEncapsulation.None }) export class UserImportListComponent extends BaseImportListComponentDirective { public textAreaForm: FormGroup; - public headerRow = [ + public headerRowDefinition = [ 'Title', 'Given name', 'Surname', @@ -39,6 +42,29 @@ export class UserImportListComponent extends BaseImportListComponentDirective { + const singleColumnDef: PblColumnDefinition = { + label: this.translate.instant(this.headerRowDefinition[index]), + prop: `newEntry.${property}`, + type: this.guessType(property as keyof User) + }; + console.log('singleColumnDef ', singleColumnDef); + + return singleColumnDef; + }); + } + + public columnSet = columnFactory() + .default({ minWidth: 150 }) + .table(this.statusImportColumn, ...this.generateImportColumns) + .build(); + /** * Constructor for list view bases * @@ -55,7 +81,7 @@ export class UserImportListComponent extends BaseImportListComponentDirective div { - height: 0; - } -} - .cdk-column-menu { padding: 0 16px 0 0 !important; }