[NIFI-12615] - fix ExpressionChanged error on Counters page. (#8252)

* [NIFI-12615] - fix ExpressionChanged error on Counters page.

* * refactor sorting for extension-creation.component
* refactor sorting for controller-service-table.component
* refactor sorting for reporting-task--table.component
* refactor sorting for parameter-context-table.component

This closes #8252
This commit is contained in:
Rob Fellows 2024-01-16 15:46:08 -05:00 committed by GitHub
parent c6f5f534cb
commit b47ca20f56
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 266 additions and 162 deletions

View File

@ -44,6 +44,7 @@
[dataSource]="dataSource" [dataSource]="dataSource"
matSort matSort
matSortDisableClear matSortDisableClear
(matSortChange)="sortData($event)"
[matSortActive]="initialSortColumn" [matSortActive]="initialSortColumn"
[matSortDirection]="initialSortDirection"> [matSortDirection]="initialSortDirection">
<!-- Context column --> <!-- Context column -->

View File

@ -15,10 +15,10 @@
* limitations under the License. * limitations under the License.
*/ */
import { AfterViewInit, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core'; import { AfterViewInit, Component, EventEmitter, Input, Output } from '@angular/core';
import { CounterEntity } from '../../../state/counter-listing'; import { CounterEntity } from '../../../state/counter-listing';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { MatSort } from '@angular/material/sort'; import { Sort } from '@angular/material/sort';
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import { debounceTime } from 'rxjs'; import { debounceTime } from 'rxjs';
import { NiFiCommon } from '../../../../../service/nifi-common.service'; import { NiFiCommon } from '../../../../../service/nifi-common.service';
@ -39,24 +39,14 @@ export class CounterTable implements AfterViewInit {
dataSource: MatTableDataSource<CounterEntity> = new MatTableDataSource<CounterEntity>(); dataSource: MatTableDataSource<CounterEntity> = new MatTableDataSource<CounterEntity>();
filterForm: FormGroup; filterForm: FormGroup;
@Input() initialSortColumn: 'context' | 'name' = 'context'; @Input() initialSortColumn: 'context' | 'name' | 'value' = 'context';
@Input() initialSortDirection: 'asc' | 'desc' = 'asc'; @Input() initialSortDirection: 'asc' | 'desc' = 'asc';
@Input() set counters(counterEntities: CounterEntity[]) { @Input() set counters(counterEntities: CounterEntity[]) {
this.dataSource = new MatTableDataSource<CounterEntity>(counterEntities); this.dataSource.data = this.sortEntities(counterEntities, {
this.dataSource.sort = this.sort; active: this.initialSortColumn,
this.dataSource.sortingDataAccessor = (data: CounterEntity, displayColumn: string) => { direction: this.initialSortDirection
switch (displayColumn) { });
case 'context':
return this.formatContext(data);
case 'name':
return this.formatName(data);
case 'value':
return data.valueCount;
default:
return '';
}
};
this.dataSource.filterPredicate = (data: CounterEntity, filter: string) => { this.dataSource.filterPredicate = (data: CounterEntity, filter: string) => {
const { filterTerm, filterColumn } = JSON.parse(filter); const { filterTerm, filterColumn } = JSON.parse(filter);
@ -93,8 +83,6 @@ export class CounterTable implements AfterViewInit {
@Output() resetCounter: EventEmitter<CounterEntity> = new EventEmitter<CounterEntity>(); @Output() resetCounter: EventEmitter<CounterEntity> = new EventEmitter<CounterEntity>();
@ViewChild(MatSort) sort!: MatSort;
constructor( constructor(
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private nifiCommon: NiFiCommon private nifiCommon: NiFiCommon
@ -103,8 +91,6 @@ export class CounterTable implements AfterViewInit {
} }
ngAfterViewInit(): void { ngAfterViewInit(): void {
this.dataSource.sort = this.sort;
this.filterForm this.filterForm
.get('filterTerm') .get('filterTerm')
?.valueChanges.pipe(debounceTime(500)) ?.valueChanges.pipe(debounceTime(500))
@ -140,4 +126,32 @@ export class CounterTable implements AfterViewInit {
event.stopPropagation(); event.stopPropagation();
this.resetCounter.next(counter); this.resetCounter.next(counter);
} }
sortData(sort: Sort) {
this.dataSource.data = this.sortEntities(this.dataSource.data, sort);
}
private sortEntities(data: CounterEntity[], sort: Sort): CounterEntity[] {
if (!data) {
return [];
}
return data.slice().sort((a, b) => {
const isAsc = sort.direction === 'asc';
let retVal = 0;
switch (sort.active) {
case 'name':
retVal = this.nifiCommon.compareString(this.formatName(a), this.formatName(b));
break;
case 'value':
retVal = this.nifiCommon.compareNumber(a.valueCount, b.valueCount);
break;
case 'context':
retVal = this.nifiCommon.compareString(this.formatContext(a), this.formatContext(b));
break;
default:
return 0;
}
return retVal * (isAsc ? 1 : -1);
});
}
} }

View File

@ -17,7 +17,14 @@
<div class="relative h-full border"> <div class="relative h-full border">
<div class="listing-table absolute inset-0 overflow-y-auto"> <div class="listing-table absolute inset-0 overflow-y-auto">
<table mat-table [dataSource]="dataSource" matSort matSortDisableClear> <table
mat-table
[dataSource]="dataSource"
matSort
matSortDisableClear
(matSortChange)="sortData($event)"
[matSortActive]="initialSortColumn"
[matSortDirection]="initialSortDirection">
<!-- More Details Column --> <!-- More Details Column -->
<ng-container matColumnDef="moreDetails"> <ng-container matColumnDef="moreDetails">
<th mat-header-cell *matHeaderCellDef></th> <th mat-header-cell *matHeaderCellDef></th>
@ -35,12 +42,7 @@
<ng-container matColumnDef="name"> <ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th> <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
<td mat-cell *matCellDef="let item"> <td mat-cell *matCellDef="let item">
<ng-container *ngIf="canRead(item); else nameNoPermissions"> <div [ngClass]="{ unset: !canRead(item) }">{{ formatName(item) }}</div>
{{ formatName(item) }}
</ng-container>
<ng-template #nameNoPermissions>
<div class="unset">{{ item.id }}</div>
</ng-template>
</td> </td>
</ng-container> </ng-container>
@ -48,9 +50,7 @@
<ng-container matColumnDef="provider"> <ng-container matColumnDef="provider">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Provider</th> <th mat-header-cell *matHeaderCellDef mat-sort-header>Provider</th>
<td mat-cell *matCellDef="let item"> <td mat-cell *matCellDef="let item">
<ng-container *ngIf="canRead(item)"> {{ formatProvider(item) }}
{{ formatProvider(item) }}
</ng-container>
</td> </td>
</ng-container> </ng-container>
@ -58,9 +58,7 @@
<ng-container matColumnDef="description"> <ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Description</th> <th mat-header-cell *matHeaderCellDef mat-sort-header>Description</th>
<td mat-cell *matCellDef="let item"> <td mat-cell *matCellDef="let item">
<ng-container *ngIf="canRead(item)"> {{ formatDescription(item) }}
{{ formatDescription(item) }}
</ng-container>
</td> </td>
</ng-container> </ng-container>

View File

@ -19,6 +19,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ParameterContextTable } from './parameter-context-table.component'; import { ParameterContextTable } from './parameter-context-table.component';
import { MatTableModule } from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
import { MatSortModule } from '@angular/material/sort';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
describe('ParameterContextTable', () => { describe('ParameterContextTable', () => {
let component: ParameterContextTable; let component: ParameterContextTable;
@ -27,7 +29,7 @@ describe('ParameterContextTable', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ParameterContextTable], declarations: [ParameterContextTable],
imports: [MatTableModule] imports: [MatTableModule, MatSortModule, NoopAnimationsModule]
}); });
fixture = TestBed.createComponent(ParameterContextTable); fixture = TestBed.createComponent(ParameterContextTable);
component = fixture.componentInstance; component = fixture.componentInstance;

View File

@ -15,9 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
import { AfterViewInit, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { MatSort } from '@angular/material/sort'; import { Sort } from '@angular/material/sort';
import { NiFiCommon } from '../../../../../service/nifi-common.service'; import { NiFiCommon } from '../../../../../service/nifi-common.service';
import { ParameterContextEntity } from '../../../state/parameter-context-listing'; import { ParameterContextEntity } from '../../../state/parameter-context-listing';
import { FlowConfiguration } from '../../../../../state/flow-configuration'; import { FlowConfiguration } from '../../../../../state/flow-configuration';
@ -28,22 +28,15 @@ import { CurrentUser } from '../../../../../state/current-user';
templateUrl: './parameter-context-table.component.html', templateUrl: './parameter-context-table.component.html',
styleUrls: ['./parameter-context-table.component.scss', '../../../../../../assets/styles/listing-table.scss'] styleUrls: ['./parameter-context-table.component.scss', '../../../../../../assets/styles/listing-table.scss']
}) })
export class ParameterContextTable implements AfterViewInit { export class ParameterContextTable {
@Input() initialSortColumn: 'name' | 'provider' | 'description' = 'name';
@Input() initialSortDirection: 'asc' | 'desc' = 'asc';
@Input() set parameterContexts(parameterContextEntities: ParameterContextEntity[]) { @Input() set parameterContexts(parameterContextEntities: ParameterContextEntity[]) {
this.dataSource = new MatTableDataSource<ParameterContextEntity>(parameterContextEntities); this.dataSource.data = this.sortEntities(parameterContextEntities, {
this.dataSource.sort = this.sort; active: this.initialSortColumn,
this.dataSource.sortingDataAccessor = (data: ParameterContextEntity, displayColumn: string) => { direction: this.initialSortDirection
if (this.canRead(data)) { });
if (displayColumn === 'name') {
return this.formatName(data);
} else if (displayColumn === 'type') {
return this.formatProvider(data);
} else if (displayColumn === 'bundle') {
return this.formatDescription(data);
}
}
return '';
};
} }
@Input() selectedParameterContextId!: string; @Input() selectedParameterContextId!: string;
@ -57,14 +50,8 @@ export class ParameterContextTable implements AfterViewInit {
displayedColumns: string[] = ['moreDetails', 'name', 'provider', 'description', 'actions']; displayedColumns: string[] = ['moreDetails', 'name', 'provider', 'description', 'actions'];
dataSource: MatTableDataSource<ParameterContextEntity> = new MatTableDataSource<ParameterContextEntity>(); dataSource: MatTableDataSource<ParameterContextEntity> = new MatTableDataSource<ParameterContextEntity>();
@ViewChild(MatSort) sort!: MatSort;
constructor(private nifiCommon: NiFiCommon) {} constructor(private nifiCommon: NiFiCommon) {}
ngAfterViewInit(): void {
this.dataSource.sort = this.sort;
}
canRead(entity: ParameterContextEntity): boolean { canRead(entity: ParameterContextEntity): boolean {
return entity.permissions.canRead; return entity.permissions.canRead;
} }
@ -74,7 +61,7 @@ export class ParameterContextTable implements AfterViewInit {
} }
formatName(entity: ParameterContextEntity): string { formatName(entity: ParameterContextEntity): string {
return entity.component.name; return this.canRead(entity) ? entity.component.name : entity.id;
} }
formatProvider(entity: ParameterContextEntity): string { formatProvider(entity: ParameterContextEntity): string {
@ -82,7 +69,7 @@ export class ParameterContextTable implements AfterViewInit {
} }
formatDescription(entity: ParameterContextEntity): string { formatDescription(entity: ParameterContextEntity): string {
return entity.component.description; return this.canRead(entity) ? entity.component.description : '';
} }
editClicked(entity: ParameterContextEntity, event: MouseEvent): void { editClicked(entity: ParameterContextEntity, event: MouseEvent): void {
@ -120,4 +107,33 @@ export class ParameterContextTable implements AfterViewInit {
} }
return false; return false;
} }
sortData(sort: Sort) {
this.dataSource.data = this.sortEntities(this.dataSource.data, sort);
}
private sortEntities(data: ParameterContextEntity[], sort: Sort): ParameterContextEntity[] {
if (!data) {
return [];
}
return data.slice().sort((a, b) => {
const isAsc = sort.direction === 'asc';
let retVal = 0;
switch (sort.active) {
case 'name':
retVal = this.nifiCommon.compareString(this.formatName(a), this.formatName(b));
break;
case 'provider':
retVal = this.nifiCommon.compareString(this.formatProvider(a), this.formatProvider(b));
break;
case 'description':
retVal = this.nifiCommon.compareString(this.formatDescription(a), this.formatDescription(b));
break;
default:
return 0;
}
return retVal * (isAsc ? 1 : -1);
});
}
} }

View File

@ -17,7 +17,14 @@
<div class="relative h-full border"> <div class="relative h-full border">
<div class="reporting-task-table listing-table absolute inset-0 overflow-y-auto"> <div class="reporting-task-table listing-table absolute inset-0 overflow-y-auto">
<table mat-table [dataSource]="dataSource" matSort matSortDisableClear> <table
mat-table
[dataSource]="dataSource"
matSort
matSortDisableClear
(matSortChange)="sortData($event)"
[matSortActive]="initialSortColumn"
[matSortDirection]="initialSortDirection">
<!-- More Details Column --> <!-- More Details Column -->
<ng-container matColumnDef="moreDetails"> <ng-container matColumnDef="moreDetails">
<th mat-header-cell *matHeaderCellDef></th> <th mat-header-cell *matHeaderCellDef></th>
@ -59,12 +66,7 @@
<ng-container matColumnDef="name"> <ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th> <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
<td mat-cell *matCellDef="let item"> <td mat-cell *matCellDef="let item">
<ng-container *ngIf="canRead(item); else nameNoPermissions"> <div [ngClass]="{ unset: !canRead(item) }">{{ formatName(item) }}</div>
{{ item.component.name }}
</ng-container>
<ng-template #nameNoPermissions>
<div class="unset">{{ item.id }}</div>
</ng-template>
</td> </td>
</ng-container> </ng-container>
@ -72,9 +74,7 @@
<ng-container matColumnDef="type"> <ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Type</th> <th mat-header-cell *matHeaderCellDef mat-sort-header>Type</th>
<td mat-cell *matCellDef="let item"> <td mat-cell *matCellDef="let item">
<ng-container *ngIf="canRead(item)"> {{ formatType(item) }}
{{ formatType(item) }}
</ng-container>
</td> </td>
</ng-container> </ng-container>
@ -82,9 +82,7 @@
<ng-container matColumnDef="bundle"> <ng-container matColumnDef="bundle">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Bundle</th> <th mat-header-cell *matHeaderCellDef mat-sort-header>Bundle</th>
<td mat-cell *matCellDef="let item"> <td mat-cell *matCellDef="let item">
<ng-container *ngIf="canRead(item)"> {{ formatBundle(item) }}
{{ formatBundle(item) }}
</ng-container>
</td> </td>
</ng-container> </ng-container>

View File

@ -19,6 +19,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReportingTaskTable } from './reporting-task-table.component'; import { ReportingTaskTable } from './reporting-task-table.component';
import { MatTableModule } from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
import { MatSortModule } from '@angular/material/sort';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
describe('ReportingTaskTable', () => { describe('ReportingTaskTable', () => {
let component: ReportingTaskTable; let component: ReportingTaskTable;
@ -27,7 +29,7 @@ describe('ReportingTaskTable', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ReportingTaskTable], declarations: [ReportingTaskTable],
imports: [MatTableModule] imports: [MatTableModule, MatSortModule, NoopAnimationsModule]
}); });
fixture = TestBed.createComponent(ReportingTaskTable); fixture = TestBed.createComponent(ReportingTaskTable);
component = fixture.componentInstance; component = fixture.componentInstance;

View File

@ -15,9 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
import { AfterViewInit, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core'; import { AfterViewInit, Component, EventEmitter, Input, Output } from '@angular/core';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { MatSort } from '@angular/material/sort'; import { Sort } from '@angular/material/sort';
import { ReportingTaskEntity } from '../../../state/reporting-tasks'; import { ReportingTaskEntity } from '../../../state/reporting-tasks';
import { TextTip } from '../../../../../ui/common/tooltips/text-tip/text-tip.component'; import { TextTip } from '../../../../../ui/common/tooltips/text-tip/text-tip.component';
import { BulletinsTip } from '../../../../../ui/common/tooltips/bulletins-tip/bulletins-tip.component'; import { BulletinsTip } from '../../../../../ui/common/tooltips/bulletins-tip/bulletins-tip.component';
@ -32,23 +32,17 @@ import { CurrentUser } from '../../../../../state/current-user';
templateUrl: './reporting-task-table.component.html', templateUrl: './reporting-task-table.component.html',
styleUrls: ['./reporting-task-table.component.scss', '../../../../../../assets/styles/listing-table.scss'] styleUrls: ['./reporting-task-table.component.scss', '../../../../../../assets/styles/listing-table.scss']
}) })
export class ReportingTaskTable implements AfterViewInit { export class ReportingTaskTable {
@Input() initialSortColumn: 'name' | 'type' | 'bundle' | 'state' = 'name';
@Input() initialSortDirection: 'asc' | 'desc' = 'asc';
@Input() set reportingTasks(reportingTaskEntities: ReportingTaskEntity[]) { @Input() set reportingTasks(reportingTaskEntities: ReportingTaskEntity[]) {
this.dataSource = new MatTableDataSource<ReportingTaskEntity>(reportingTaskEntities); this.dataSource.data = this.sortEntities(reportingTaskEntities, {
this.dataSource.sort = this.sort; active: this.initialSortColumn,
this.dataSource.sortingDataAccessor = (data: ReportingTaskEntity, displayColumn: string) => { direction: this.initialSortDirection
if (displayColumn === 'name') { });
return this.formatType(data);
} else if (displayColumn === 'type') {
return this.formatType(data);
} else if (displayColumn === 'bundle') {
return this.formatBundle(data);
} else if (displayColumn === 'state') {
return this.formatState(data);
}
return '';
};
} }
@Input() selectedReportingTaskId!: string; @Input() selectedReportingTaskId!: string;
@Input() flowConfiguration!: FlowConfiguration; @Input() flowConfiguration!: FlowConfiguration;
@Input() currentUser!: CurrentUser; @Input() currentUser!: CurrentUser;
@ -66,14 +60,8 @@ export class ReportingTaskTable implements AfterViewInit {
displayedColumns: string[] = ['moreDetails', 'name', 'type', 'bundle', 'state', 'actions']; displayedColumns: string[] = ['moreDetails', 'name', 'type', 'bundle', 'state', 'actions'];
dataSource: MatTableDataSource<ReportingTaskEntity> = new MatTableDataSource<ReportingTaskEntity>(); dataSource: MatTableDataSource<ReportingTaskEntity> = new MatTableDataSource<ReportingTaskEntity>();
@ViewChild(MatSort) sort!: MatSort;
constructor(private nifiCommon: NiFiCommon) {} constructor(private nifiCommon: NiFiCommon) {}
ngAfterViewInit(): void {
this.dataSource.sort = this.sort;
}
canRead(entity: ReportingTaskEntity): boolean { canRead(entity: ReportingTaskEntity): boolean {
return entity.permissions.canRead; return entity.permissions.canRead;
} }
@ -156,12 +144,16 @@ export class ReportingTaskTable implements AfterViewInit {
return entity.status?.activeThreadCount > 0; return entity.status?.activeThreadCount > 0;
} }
formatName(entity: ReportingTaskEntity): string {
return this.canRead(entity) ? entity.component.name : entity.id;
}
formatType(entity: ReportingTaskEntity): string { formatType(entity: ReportingTaskEntity): string {
return this.nifiCommon.formatType(entity.component); return this.canRead(entity) ? this.nifiCommon.formatType(entity.component) : '';
} }
formatBundle(entity: ReportingTaskEntity): string { formatBundle(entity: ReportingTaskEntity): string {
return this.nifiCommon.formatBundle(entity.component.bundle); return this.canRead(entity) ? this.nifiCommon.formatBundle(entity.component.bundle) : '';
} }
isDisabled(entity: ReportingTaskEntity): boolean { isDisabled(entity: ReportingTaskEntity): boolean {
@ -259,4 +251,36 @@ export class ReportingTaskTable implements AfterViewInit {
} }
return false; return false;
} }
sortData(sort: Sort) {
this.dataSource.data = this.sortEntities(this.dataSource.data, sort);
}
private sortEntities(data: ReportingTaskEntity[], sort: Sort): ReportingTaskEntity[] {
if (!data) {
return [];
}
return data.slice().sort((a, b) => {
const isAsc = sort.direction === 'asc';
let retVal = 0;
switch (sort.active) {
case 'name':
retVal = this.nifiCommon.compareString(this.formatName(a), this.formatName(b));
break;
case 'type':
retVal = this.nifiCommon.compareString(this.formatType(a), this.formatType(b));
break;
case 'bundle':
retVal = this.nifiCommon.compareString(this.formatBundle(a), this.formatBundle(b));
break;
case 'state':
retVal = this.nifiCommon.compareString(this.formatState(a), this.formatState(b));
break;
default:
return 0;
}
return retVal * (isAsc ? 1 : -1);
});
}
} }

View File

@ -17,7 +17,14 @@
<div class="relative h-full border"> <div class="relative h-full border">
<div class="controller-service-table listing-table absolute inset-0 overflow-y-auto"> <div class="controller-service-table listing-table absolute inset-0 overflow-y-auto">
<table mat-table [dataSource]="dataSource" matSort matSortDisableClear> <table
mat-table
[dataSource]="dataSource"
matSort
matSortDisableClear
(matSortChange)="sortData($event)"
[matSortActive]="initialSortColumn"
[matSortDirection]="initialSortDirection">
<!-- More Details Column --> <!-- More Details Column -->
<ng-container matColumnDef="moreDetails"> <ng-container matColumnDef="moreDetails">
<th mat-header-cell *matHeaderCellDef></th> <th mat-header-cell *matHeaderCellDef></th>
@ -64,12 +71,7 @@
<ng-container matColumnDef="name"> <ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th> <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
<td mat-cell *matCellDef="let item"> <td mat-cell *matCellDef="let item">
<ng-container *ngIf="canRead(item); else nameNoPermissions"> <div [ngClass]="{ unset: !canRead(item) }">{{ formatName(item) }}</div>
{{ item.component.name }}
</ng-container>
<ng-template #nameNoPermissions>
<div class="unset">{{ item.id }}</div>
</ng-template>
</td> </td>
</ng-container> </ng-container>
@ -77,9 +79,7 @@
<ng-container matColumnDef="type"> <ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Type</th> <th mat-header-cell *matHeaderCellDef mat-sort-header>Type</th>
<td mat-cell *matCellDef="let item"> <td mat-cell *matCellDef="let item">
<ng-container *ngIf="canRead(item)"> {{ formatType(item) }}
{{ formatType(item) }}
</ng-container>
</td> </td>
</ng-container> </ng-container>
@ -87,9 +87,7 @@
<ng-container matColumnDef="bundle"> <ng-container matColumnDef="bundle">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Bundle</th> <th mat-header-cell *matHeaderCellDef mat-sort-header>Bundle</th>
<td mat-cell *matCellDef="let item"> <td mat-cell *matCellDef="let item">
<ng-container *ngIf="canRead(item)"> {{ formatBundle(item) }}
{{ formatBundle(item) }}
</ng-container>
</td> </td>
</ng-container> </ng-container>

View File

@ -15,12 +15,12 @@
* limitations under the License. * limitations under the License.
*/ */
import { AfterViewInit, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog';
import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { NiFiCommon } from '../../../../service/nifi-common.service'; import { NiFiCommon } from '../../../../service/nifi-common.service';
import { MatSort, MatSortModule } from '@angular/material/sort'; import { MatSortModule, Sort } from '@angular/material/sort';
import { NgClass, NgIf } from '@angular/common'; import { NgClass, NgIf } from '@angular/common';
import { import {
BulletinsTipInput, BulletinsTipInput,
@ -52,25 +52,17 @@ import { CurrentUser } from '../../../../state/current-user';
], ],
styleUrls: ['./controller-service-table.component.scss', '../../../../../assets/styles/listing-table.scss'] styleUrls: ['./controller-service-table.component.scss', '../../../../../assets/styles/listing-table.scss']
}) })
export class ControllerServiceTable implements AfterViewInit { export class ControllerServiceTable {
@Input() initialSortColumn: 'name' | 'type' | 'bundle' | 'state' | 'scope' = 'name';
@Input() initialSortDirection: 'asc' | 'desc' = 'asc';
@Input() set controllerServices(controllerServiceEntities: ControllerServiceEntity[]) { @Input() set controllerServices(controllerServiceEntities: ControllerServiceEntity[]) {
this.dataSource = new MatTableDataSource<ControllerServiceEntity>(controllerServiceEntities); this.dataSource.data = this.sortEntities(controllerServiceEntities, {
this.dataSource.sort = this.sort; active: this.initialSortColumn,
this.dataSource.sortingDataAccessor = (data: ControllerServiceEntity, displayColumn: string) => { direction: this.initialSortDirection
if (displayColumn == 'name') { });
return this.formatType(data);
} else if (displayColumn == 'type') {
return this.formatType(data);
} else if (displayColumn == 'bundle') {
return this.formatBundle(data);
} else if (displayColumn == 'state') {
return this.formatState(data);
} else if (displayColumn == 'scope') {
return this.formatScope(data);
}
return '';
};
} }
@Input() selectedServiceId!: string; @Input() selectedServiceId!: string;
@Input() formatScope!: (entity: ControllerServiceEntity) => string; @Input() formatScope!: (entity: ControllerServiceEntity) => string;
@Input() definedByCurrentGroup!: (entity: ControllerServiceEntity) => boolean; @Input() definedByCurrentGroup!: (entity: ControllerServiceEntity) => boolean;
@ -96,14 +88,8 @@ export class ControllerServiceTable implements AfterViewInit {
displayedColumns: string[] = ['moreDetails', 'name', 'type', 'bundle', 'state', 'scope', 'actions']; displayedColumns: string[] = ['moreDetails', 'name', 'type', 'bundle', 'state', 'scope', 'actions'];
dataSource: MatTableDataSource<ControllerServiceEntity> = new MatTableDataSource<ControllerServiceEntity>(); dataSource: MatTableDataSource<ControllerServiceEntity> = new MatTableDataSource<ControllerServiceEntity>();
@ViewChild(MatSort) sort!: MatSort;
constructor(private nifiCommon: NiFiCommon) {} constructor(private nifiCommon: NiFiCommon) {}
ngAfterViewInit(): void {
this.dataSource.sort = this.sort;
}
canRead(entity: ControllerServiceEntity): boolean { canRead(entity: ControllerServiceEntity): boolean {
return entity.permissions.canRead; return entity.permissions.canRead;
} }
@ -188,12 +174,16 @@ export class ControllerServiceTable implements AfterViewInit {
return ''; return '';
} }
formatName(entity: ControllerServiceEntity): string {
return this.canRead(entity) ? entity.component.name : entity.id;
}
formatType(entity: ControllerServiceEntity): string { formatType(entity: ControllerServiceEntity): string {
return this.nifiCommon.formatType(entity.component); return this.canRead(entity) ? this.nifiCommon.formatType(entity.component) : '';
} }
formatBundle(entity: ControllerServiceEntity): string { formatBundle(entity: ControllerServiceEntity): string {
return this.nifiCommon.formatBundle(entity.component.bundle); return this.canRead(entity) ? this.nifiCommon.formatBundle(entity.component.bundle) : '';
} }
getServiceLink(entity: ControllerServiceEntity): string[] { getServiceLink(entity: ControllerServiceEntity): string[] {
@ -279,4 +269,39 @@ export class ControllerServiceTable implements AfterViewInit {
} }
return false; return false;
} }
sortData(sort: Sort) {
this.dataSource.data = this.sortEntities(this.dataSource.data, sort);
}
private sortEntities(data: ControllerServiceEntity[], sort: Sort): ControllerServiceEntity[] {
if (!data) {
return [];
}
return data.slice().sort((a, b) => {
const isAsc = sort.direction === 'asc';
let retVal = 0;
switch (sort.active) {
case 'name':
retVal = this.nifiCommon.compareString(this.formatName(a), this.formatName(b));
break;
case 'type':
retVal = this.nifiCommon.compareString(this.formatType(a), this.formatType(b));
break;
case 'bundle':
retVal = this.nifiCommon.compareString(this.formatBundle(a), this.formatBundle(b));
break;
case 'state':
retVal = this.nifiCommon.compareString(this.formatState(a), this.formatState(b));
break;
case 'scope':
retVal = this.nifiCommon.compareString(this.formatScope(a), this.formatScope(b));
break;
default:
return 0;
}
return retVal * (isAsc ? 1 : -1);
});
}
} }

View File

@ -29,7 +29,14 @@
</div> </div>
<div class="type-table"> <div class="type-table">
<div class="h-96 overflow-y-auto overflow-x-hidden border"> <div class="h-96 overflow-y-auto overflow-x-hidden border">
<table mat-table [dataSource]="dataSource" matSort matSortDisableClear> <table
mat-table
[dataSource]="dataSource"
matSort
matSortDisableClear
(matSortChange)="sortData($event)"
[matSortActive]="initialSortColumn"
[matSortDirection]="initialSortDirection">
<!-- Type Column --> <!-- Type Column -->
<ng-container matColumnDef="type"> <ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Type</th> <th mat-header-cell *matHeaderCellDef mat-sort-header>Type</th>

View File

@ -15,12 +15,12 @@
* limitations under the License. * limitations under the License.
*/ */
import { AfterViewInit, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog';
import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { NiFiCommon } from '../../../service/nifi-common.service'; import { NiFiCommon } from '../../../service/nifi-common.service';
import { MatSort, MatSortModule } from '@angular/material/sort'; import { MatSortModule, Sort } from '@angular/material/sort';
import { NgIf } from '@angular/common'; import { NgIf } from '@angular/common';
import { ControllerServiceApiTipInput, DocumentedType, RestrictionsTipInput } from '../../../state/shared'; import { ControllerServiceApiTipInput, DocumentedType, RestrictionsTipInput } from '../../../state/shared';
import { NifiTooltipDirective } from '../tooltips/nifi-tooltip.directive'; import { NifiTooltipDirective } from '../tooltips/nifi-tooltip.directive';
@ -43,27 +43,23 @@ import { NifiSpinnerDirective } from '../spinner/nifi-spinner.directive';
], ],
styleUrls: ['./extension-creation.component.scss'] styleUrls: ['./extension-creation.component.scss']
}) })
export class ExtensionCreation implements AfterViewInit { export class ExtensionCreation {
@Input() set documentedTypes(documentedTypes: DocumentedType[]) { @Input() set documentedTypes(documentedTypes: DocumentedType[]) {
if (this.selectedType == null && documentedTypes.length > 0) { if (this.selectedType == null && documentedTypes.length > 0) {
this.selectedType = documentedTypes[0]; this.selectedType = documentedTypes[0];
} }
this.dataSource = new MatTableDataSource<DocumentedType>(documentedTypes); this.dataSource.data = this.sortEntities(documentedTypes, {
this.dataSource.sort = this.sort; active: this.initialSortColumn,
this.dataSource.sortingDataAccessor = (data: DocumentedType, displayColumn: string) => { direction: this.initialSortDirection
if (displayColumn == 'type') { });
return this.formatType(data);
} else if (displayColumn == 'version') {
return this.formatVersion(data);
} else if (displayColumn == 'tags') {
return this.formatTags(data);
}
return '';
};
} }
@Input() componentType!: string; @Input() componentType!: string;
@Input() saving!: boolean; @Input() saving!: boolean;
@Input() initialSortColumn: 'type' | 'version' | 'tags' = 'type';
@Input() initialSortDirection: 'asc' | 'desc' = 'asc';
@Output() extensionTypeSelected: EventEmitter<DocumentedType> = new EventEmitter<DocumentedType>(); @Output() extensionTypeSelected: EventEmitter<DocumentedType> = new EventEmitter<DocumentedType>();
protected readonly RestrictionsTip = RestrictionsTip; protected readonly RestrictionsTip = RestrictionsTip;
@ -73,14 +69,8 @@ export class ExtensionCreation implements AfterViewInit {
dataSource: MatTableDataSource<DocumentedType> = new MatTableDataSource<DocumentedType>(); dataSource: MatTableDataSource<DocumentedType> = new MatTableDataSource<DocumentedType>();
selectedType: DocumentedType | null = null; selectedType: DocumentedType | null = null;
@ViewChild(MatSort) sort!: MatSort;
constructor(private nifiCommon: NiFiCommon) {} constructor(private nifiCommon: NiFiCommon) {}
ngAfterViewInit(): void {
this.dataSource.sort = this.sort;
}
formatType(documentedType: DocumentedType): string { formatType(documentedType: DocumentedType): string {
if (documentedType) { if (documentedType) {
return this.nifiCommon.substringAfterLast(documentedType.type, '.'); return this.nifiCommon.substringAfterLast(documentedType.type, '.');
@ -115,7 +105,10 @@ export class ExtensionCreation implements AfterViewInit {
formatTags(documentedType: DocumentedType): string { formatTags(documentedType: DocumentedType): string {
if (documentedType?.tags) { if (documentedType?.tags) {
return documentedType.tags.join(', '); return documentedType.tags
.slice()
.sort((a, b) => this.nifiCommon.compareString(a, b))
.join(', ');
} }
return ''; return '';
} }
@ -156,4 +149,30 @@ export class ExtensionCreation implements AfterViewInit {
this.extensionTypeSelected.next(documentedType); this.extensionTypeSelected.next(documentedType);
} }
} }
sortData(sort: Sort) {
this.dataSource.data = this.sortEntities(this.dataSource.data, sort);
}
sortEntities(data: DocumentedType[], sort: Sort): DocumentedType[] {
if (!data) {
return [];
}
return data.slice().sort((a, b) => {
const isAsc = sort.direction === 'asc';
let retVal = 0;
switch (sort.active) {
case 'type':
retVal = this.nifiCommon.compareString(this.formatType(a), this.formatType(b));
break;
case 'version':
retVal = this.nifiCommon.compareString(this.formatVersion(a), this.formatVersion(b));
break;
case 'tags':
retVal = this.nifiCommon.compareString(this.formatTags(a), this.formatTags(b));
break;
}
return retVal * (isAsc ? 1 : -1);
});
}
} }