This is an automated email from the ASF dual-hosted git repository.
mmiklavcic pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/metron.git
The following commit(s) were added to refs/heads/master by this push:
new 46e8625 METRON-2060 Improving Alerts table config pane (tiborm via
mmiklavc) closes apache/metron#1375
46e8625 is described below
commit 46e8625865100b35ee69fe6499e8bda56197fbcd
Author: tiborm <[email protected]>
AuthorDate: Fri Apr 12 11:43:16 2019 -0600
METRON-2060 Improving Alerts table config pane (tiborm via mmiklavc) closes
apache/metron#1375
---
.../alert-details/alert-details.component.html | 59 ++++----
.../alert-details/alert-details.component.scss | 15 +++
.../alerts-list/alerts-list.component.spec.ts | 2 +-
.../configure-table/configure-table.component.html | 148 +++++++++++++--------
.../configure-table/configure-table.component.scss | 39 ++++++
.../configure-table.component.spec.ts | 147 +++++++++++++++++---
.../configure-table/configure-table.component.ts | 110 +++++++++------
metron-interface/metron-alerts/src/slider.scss | 28 ++--
metron-interface/metron-alerts/src/styles.scss | 17 +++
metron-interface/metron-alerts/src/vendor.scss | 1 +
10 files changed, 399 insertions(+), 167 deletions(-)
diff --git
a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.html
b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.html
index abc01ca..c4bcc88 100644
---
a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.html
+++
b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.html
@@ -12,7 +12,7 @@
the specific language governing permissions and limitations under the
License.
-->
<div class="metron-slider-pane-details load-right-to-left dialog1x"
[ngClass]="{'is-meta-alert': isMetaAlert}">
- <div class="container-fluid pl-0 h-100" [ngClass]="{'pr-0': isMetaAlert}">
+ <div class="container-fluid pl-0 pr-0 h-100">
<div class="h-100 d-flex">
<div class="nav-container" *ngIf="!isMetaAlert">
<ul class="nav flex-column">
@@ -81,33 +81,38 @@
</table>
</div>
- <div class="ml-1 my-3 form" *ngIf="activeTab ===
tabs.DETAILS">
- <ng-container *ngFor="let alert of alertSources; let i
= index;" >
- <div class="pb-2 alert-details-title"> Alert {{ i
+ 1 }} of {{ alertSources.length }}</div>
- <div *ngFor="let field of alert |
alertDetailsKeys" class="row ml-1">
- <div class="col-6 mb-1 key">{{ field }}</div>
<div class="col-6"> {{ alert[field] }} </div>
- </div>
- </ng-container>
- </div>
+ <div class="tabContainer">
+ <div *ngIf="activeTab === tabs.DETAILS" class="ml-1
my-3 form" >
+ <ng-container *ngFor="let alert of alertSources;
let i = index;" >
+ <div class="pb-2 alert-details-title"> Alert
{{ i + 1 }} of {{ alertSources.length }}</div>
+ <ul>
+ <li *ngFor="let field of alert |
alertDetailsKeys">
+ <div class="key">{{ field }}</div>
+ <div> {{ alert[field] }} </div>
+ </li>
+ </ul>
+ </ng-container>
+ </div>
- <div *ngIf="activeTab === tabs.COMMENTS" class="my-4">
- <div> Comments <span
*ngIf="alertCommentsWrapper.length > 0"> ({{alertCommentsWrapper.length}})
</span></div>
- <textarea class="form-control"
[(ngModel)]="alertCommentStr"> </textarea>
- <button class="btn btn-mine_shaft_2"
[disabled]="alertCommentStr.trim().length === 0" (click)="onAddComment()">ADD
COMMENT</button>
- <ng-container *ngFor="let alertCommentWrapper of
alertCommentsWrapper; let i = index">
- <hr>
- <div class="comment-container"
data-qe-id="comment">
- <i
- class="fa fa-trash-o"
- aria-hidden="true"
- (click)="onDeleteComment(i)"
- data-qe-id="delete-comment"
- >
- </i>
- <div class="comment"> {{
alertCommentWrapper.alertComment.comment }} </div>
- <div class="font-italic username-timestamp"> -
{{ alertCommentWrapper.alertComment.username }} -
{{alertCommentWrapper.displayTime}}</div>
- </div>
- </ng-container>
+ <div *ngIf="activeTab === tabs.COMMENTS" class="my-4">
+ <div> Comments <span
*ngIf="alertCommentsWrapper.length > 0"> ({{alertCommentsWrapper.length}})
</span></div>
+ <textarea class="form-control"
[(ngModel)]="alertCommentStr"> </textarea>
+ <button class="btn btn-mine_shaft_2"
[disabled]="alertCommentStr.trim().length === 0" (click)="onAddComment()">ADD
COMMENT</button>
+ <ng-container *ngFor="let alertCommentWrapper of
alertCommentsWrapper; let i = index">
+ <hr>
+ <div class="comment-container"
data-qe-id="comment">
+ <i
+ class="fa fa-trash-o"
+ aria-hidden="true"
+ (click)="onDeleteComment(i)"
+ data-qe-id="delete-comment"
+ >
+ </i>
+ <div class="comment"> {{
alertCommentWrapper.alertComment.comment }} </div>
+ <div class="font-italic
username-timestamp"> - {{ alertCommentWrapper.alertComment.username }} -
{{alertCommentWrapper.displayTime}}</div>
+ </div>
+ </ng-container>
+ </div>
</div>
</div>
</div>
diff --git
a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.scss
b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.scss
index 3b10c8f..3373292 100644
---
a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.scss
+++
b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.scss
@@ -176,3 +176,18 @@ textarea {
opacity: 0.5;
cursor: not-allowed;
}
+
+.tabContainer {
+ max-height: 100%;
+ height: 100%;
+ overflow: scroll;
+
+ ul {
+ padding-inline-start: 20px;
+ padding-bottom: 1rem;
+
+ li {
+ margin-bottom: 1rem;
+ }
+ }
+}
diff --git
a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.spec.ts
b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.spec.ts
index 7adbbe9..fe838b3 100644
---
a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.spec.ts
+++
b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.spec.ts
@@ -31,7 +31,7 @@ import { DialogService } from 'app/service/dialog.service';
import { Observable } from 'rxjs';
import { Filter } from 'app/model/filter';
-fdescribe('AlertsListComponent', () => {
+describe('AlertsListComponent', () => {
let component: AlertsListComponent;
let fixture: ComponentFixture<AlertsListComponent>;
diff --git
a/metron-interface/metron-alerts/src/app/alerts/configure-table/configure-table.component.html
b/metron-interface/metron-alerts/src/app/alerts/configure-table/configure-table.component.html
index 9cedca2..0ddf729 100644
---
a/metron-interface/metron-alerts/src/app/alerts/configure-table/configure-table.component.html
+++
b/metron-interface/metron-alerts/src/app/alerts/configure-table/configure-table.component.html
@@ -11,70 +11,100 @@
OR CONDITIONS OF ANY KIND, either express or implied. See the License
for
the specific language governing permissions and limitations under the
License.
-->
-<div class="metron-slider-pane-editable load-right-to-left dialog2x">
- <div class="container-fluid">
- <div class="row mb-3">
- <div class="col-md-12">
- <div class="d-flex pb-3">
- <div class="form-title font-weight-bold">Configure Table</div>
- <i class="fa fa-times ml-auto close-button" aria-hidden="true"
(click)="goBack()"></i>
- </div>
- <div class="input-group">
- <input class="input flex-fill" data-qe-id="filter-input" type="text"
placeholder="Filter list" #filterColResults >
- <div class="input-group-append">
- <button class="btn btn-secondary btn-search-clear"
data-qe-id="filter-reset" (click)="clearFilter()"></button>
- </div>
- </div>
+<div class="metron-slider-pane-editable pb-0 d-flex flex-column
load-right-to-left custom-dialog2x">
+
+ <div class="container-fluid mb-3">
+ <div class="d-flex pb-3">
+ <div class="form-title font-weight-bold">Configure Table Columns</div>
+ <i class="fa fa-times ml-auto close-button" aria-hidden="true"
(click)="goBack()"></i>
+ </div>
+ <div class="input-group">
+ <input class="input flex-fill" data-qe-id="filter-input" type="text"
placeholder="Filter list of available fields" #columnFilterInput >
+ <div class="input-group-append">
+ <button class="btn btn-secondary btn-search-clear"
data-qe-id="filter-reset" (click)="clearFilter()"></button>
</div>
</div>
- <div class="row mx-0">
- <table class="table">
- <thead>
- <tr>
- <th style="width: 10%"> <input id="select-deselect-all-col"
class="fontawesome-checkbox" type="checkbox"
(click)="onSelectDeselectAll($event)"><label
for="select-deselect-all-col"></label> </th>
- <th style="width: 30%"> Field </th>
- <th style="width: 10%"> Short Name </th>
- <th style="width: 30%"> Type </th>
- <th style="width: 10%"> </th>
- <th style="width: 10%"> </th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td><input #selectColName id="select-deselect-score"
class="fontawesome-checkbox" type="checkbox" [checked]="true" disabled><label
for="select-deselect-score"></label></td>
- <td> <span> Score </span></td>
- <td> </td>
- <td> <span> STRING </span></td>
- <td> - </td>
- <td> - </td>
- </tr>
- <tr *ngFor="let columns of filteredColumns; let i = index"
[ngClass]="{'background-tiber': columns.selected}">
- <td>
- <input #selectColName id="select-deselect-{{
columns.columnMetadata.name }}" class="fontawesome-checkbox" type="checkbox"
[checked]="columns.selected" (click)="selectColumn(columns)">
- <label for="select-deselect-{{ columns.columnMetadata.name
}}"></label>
- </td>
- <td #element>
- <span [attr.title]="columns.key"> {{ columns.columnMetadata.name
| centerEllipses }} </span>
- </td>
- <td>
- <input class="input" attr.data-qe-id="display-name-{{
columns.columnMetadata.name }}" placeholder="rename"
[(ngModel)]="columns.displayName">
- </td>
- <td>
- <span class="text-uppercase"> {{ columns.columnMetadata.type }}
</span>
- </td>
- <td>
- <span id="up-{{ columns.columnMetadata.name }}" class="up"
(click)="swapUp(i)" [ngClass]="{'disabled': i === 0}"></span>
- </td>
- <td>
- <span id="down-{{ columns.columnMetadata.name }}" class="down"
(click)="swapDown(i)" [ngClass]="{'disabled': i + 1 ===
allColumns.length}"></span>
- </td>
- </tr>
- </tbody>
- </table>
+ </div>
+
+ <div class="container-fluid pt-0 table-config d-flex flex-wrap
overflow-auto">
+
+ <div class="d-flex w-100 justify-content-center align-items-center"
*ngIf="!visibleColumns.length">
+ <div class="spinner-border text-info" role="status"></div>
+ <div class="pt-2"><small>Loading...</small></div>
</div>
+
+ <table data-qe-id="table-visible" class="table"
*ngIf="visibleColumns.length">
+ <thead>
+ <tr>
+ <th class="main-column" colspan=2> Visible </th>
+ <th> Short Name </th>
+ <th> Type </th>
+ <th> </th>
+ <th> </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr class="background-tiber">
+ <td>
+ <button class="btn btn-secondary btn-sm" disabled>remove</button>
+ </td>
+ <td>
+ <span> Score </span>
+ </td>
+ <td> </td>
+ <td> <span> STRING </span></td>
+ <td> - </td>
+ <td> - </td>
+ </tr>
+ <tr attr.data-qe-id="row-{{ i }}" *ngFor="let column of
visibleColumns; let i = index" [ngClass]="{'background-tiber':
column.selected}">
+ <td>
+ <button attr.data-qe-id="remove-btn-{{ i }}"
(click)="onColumnRemoved(column)" class="btn btn-secondary
btn-sm">remove</button>
+ </td>
+ <td #element>
+ <span attr.data-qe-id="field-label-{{ i }}"
[attr.title]="column.columnMetadata.name"> {{ column.columnMetadata.name |
centerEllipses }} </span>
+ </td>
+ <td>
+ <input class="input" placeholder="rename"
[(ngModel)]="column.displayName">
+ </td>
+ <td>
+ <span class="text-uppercase"> {{ column.columnMetadata.type }}
</span>
+ </td>
+ <td>
+ <span id="up-{{ column.columnMetadata.name }}" class="up"
(click)="swapUp(i)" [ngClass]="{'disabled': i === 0}"></span>
+ </td>
+ <td>
+ <span id="down-{{ column.columnMetadata.name }}" class="down"
(click)="swapDown(i)" [ngClass]="{'disabled': i + 1 ===
visibleColumns.length}"></span>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table data-qe-id="table-available" class="table"
*ngIf="availableColumns.length">
+ <thead>
+ <tr>
+ <th class="main-column" colspan=2> Available </th>
+ <th> Type </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr attr.data-qe-id="row-{{ i }}" *ngFor="let column of
filteredColumns; let i = index" [ngClass]="{'background-tiber':
column.selected}">
+ <td>
+ <button attr.data-qe-id="add-btn-{{ i }}"
(click)="onColumnAdded(column)" class="btn btn-primary btn-sm">add</button>
+ </td>
+ <td #element>
+ <span attr.data-qe-id="field-label-{{ i }}"
[attr.title]="column.columnMetadata.name"> {{ column.columnMetadata.name |
centerEllipses }} </span>
+ </td>
+ <td>
+ <span class="text-uppercase"> {{ column.columnMetadata.type }}
</span>
+ </td>
+ </tr>
+ </tbody>
+ </table>
</div>
- <div class="container-fluid metron-floating-button-bar-2x">
+
+ <div class="container-fluid custom-metron-button-bar-2x">
<button type="submit" data-qe-id="save-table-config" class="btn
btn-all_ports" (click)="save()">SAVE</button>
<button class="btn btn-mine_shaft_2" (click)="goBack()">CANCEL</button>
</div>
</div>
+
diff --git
a/metron-interface/metron-alerts/src/app/alerts/configure-table/configure-table.component.scss
b/metron-interface/metron-alerts/src/app/alerts/configure-table/configure-table.component.scss
index d17f3df..d590f0c 100644
---
a/metron-interface/metron-alerts/src/app/alerts/configure-table/configure-table.component.scss
+++
b/metron-interface/metron-alerts/src/app/alerts/configure-table/configure-table.component.scss
@@ -17,6 +17,18 @@
*/
@import "../../../slider";
@import "../../../variables";
+@import "../../../styles";
+
+@include keyframes("keyframe-dialog-rtl", $dialog-4x-width, "0px")
+
+.load-right-to-left {
+ @include animation("keyframe-dialog-rtl", "0.3s", "ease");
+}
+
+.custom-metron-button-bar-2x {
+ @extend .metron-button-bar-2x;
+ width: 100%;
+}
.container-fluid {
padding-top: 15px;
@@ -29,8 +41,29 @@
text-overflow: ellipsis;
}
+.custom-dialog2x {
+ width: 75%;
+
+ .table-config {
+
+ flex-grow: 1;
+ flex-wrap: wrap;
+
+ table {
+ flex: 1;
+ margin: 0 1rem 0 0;
+ }
+
+ .main-column {
+ text-transform: uppercase;
+ font-size: 1.1rem;
+ }
+ }
+}
+
.table th, .table td {
padding: 0.6rem 0.25rem;
+ text-overflow: ellipsis;
}
.up:before {
@@ -75,3 +108,9 @@
padding-left: 5px;
}
}
+
+.btn-sm {
+ padding: 0.1rem 0.25rem;
+ font-size: 0.75rem;
+ line-height: 1.1rem;
+}
diff --git
a/metron-interface/metron-alerts/src/app/alerts/configure-table/configure-table.component.spec.ts
b/metron-interface/metron-alerts/src/app/alerts/configure-table/configure-table.component.spec.ts
index bc3f21e..1dc1e3e 100644
---
a/metron-interface/metron-alerts/src/app/alerts/configure-table/configure-table.component.spec.ts
+++
b/metron-interface/metron-alerts/src/app/alerts/configure-table/configure-table.component.spec.ts
@@ -20,13 +20,14 @@ import { FormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { of } from 'rxjs';
-import { ConfigureTableComponent } from './configure-table.component';
+import { ConfigureTableComponent, ColumnMetadataWrapper } from
'./configure-table.component';
import { ConfigureTableService } from '../../service/configure-table.service';
import { SwitchComponent } from '../../shared/switch/switch.component';
import { CenterEllipsesPipe } from '../../shared/pipes/center-ellipses.pipe';
import { ClusterMetaDataService } from 'app/service/cluster-metadata.service';
import { SearchService } from 'app/service/search.service';
import { ColumnNamesService } from 'app/service/column-names.service';
+import { By } from '@angular/platform-browser';
class FakeClusterMetaDataService {
getDefaultColumns() {
@@ -113,20 +114,20 @@ describe('ConfigureTableComponent', () => {
component.ngOnInit();
component.ngAfterViewInit();
- expect(component.filteredColumns.length).toBe(18);
+ expect(component.filteredColumns.length).toBe(10);
- filter.value = 'guid';
+ filter.value = 'timestamp';
filter.dispatchEvent(new Event('keyup'));
tick(300);
fixture.detectChanges();
expect(component.filteredColumns.length).toBe(1);
- expect(component.filteredColumns[0].columnMetadata.name).toBe('guid');
+
expect(component.filteredColumns[0].columnMetadata.name).toBe('bro_timestamp');
filter.value = '';
filter.dispatchEvent(new Event('keyup'));
tick(300);
fixture.detectChanges();
- expect(component.filteredColumns.length).toBe(18);
+ expect(component.filteredColumns.length).toBe(10);
}));
it('should reset filter input and available columns when clear button is
clicked', fakeAsync(() => {
@@ -136,7 +137,7 @@ describe('ConfigureTableComponent', () => {
component.ngOnInit();
component.ngAfterViewInit();
- filter.value = 'guid';
+ filter.value = 'timestamp';
filter.dispatchEvent(new Event('keyup'));
tick(300);
fixture.detectChanges();
@@ -145,23 +146,131 @@ describe('ConfigureTableComponent', () => {
filterReset.dispatchEvent(new Event('click'));
fixture.detectChanges();
expect(filter.value).toBe('');
- expect(component.filteredColumns.length).toBe(18);
+ expect(component.filteredColumns.length).toBe(10);
}));
- it('should filter by display name if display name is present', fakeAsync(()
=> {
- const filter =
fixture.nativeElement.querySelector('[data-qe-id="filter-input"]');
+ it('should mark default columns as visible', () => {
+ expect(component.visibleColumns.length).toBe(8);
+ });
- component.ngOnInit();
- component.ngAfterViewInit();
- expect(component.filteredColumns.length).toBe(18);
+ it('should mark other columns as available', () => {
+ expect(component.availableColumns.length).toBe(10);
+ });
- component.filteredColumns[0].displayName = 'Test Display Name';
+ it('should mark added element as selected', () => {
+ const itemToAdd: ColumnMetadataWrapper = component.availableColumns[0];
- filter.value = 'test';
- filter.dispatchEvent(new Event('keyup'));
- tick(300);
- fixture.detectChanges();
- expect(component.filteredColumns.length).toBe(1);
- }));
+ expect(itemToAdd.selected).toEqual(false);
+
+ component.onColumnAdded(itemToAdd);
+
+ expect(itemToAdd.selected).toEqual(true);
+ });
+
+ it('should mark removed element as deselected', () => {
+ const itemToRemove: ColumnMetadataWrapper = component.visibleColumns[0];
+
+ expect(itemToRemove.selected).toEqual(true);
+
+ component.onColumnRemoved(itemToRemove);
+
+ expect(itemToRemove.selected).toEqual(false);
+ });
+
+ it('should update list of visible items on add', () => {
+ const itemToAdd: ColumnMetadataWrapper = component.availableColumns[0];
+
+ expect(component.visibleColumns.includes(itemToAdd)).toEqual(false);
+
+ component.onColumnAdded(itemToAdd);
+
+ expect(component.visibleColumns.includes(itemToAdd)).toEqual(true);
+ });
+
+ it('should update list of available items on add', () => {
+ const itemToAdd: ColumnMetadataWrapper = component.availableColumns[0];
+
+ expect(component.availableColumns.includes(itemToAdd)).toEqual(true);
+
+ component.onColumnAdded(itemToAdd);
+
+ expect(component.availableColumns.includes(itemToAdd)).toEqual(false);
+ });
+ it('should update list of visible items on remove', () => {
+ const itemToRemove: ColumnMetadataWrapper = component.visibleColumns[0];
+
+ expect(component.visibleColumns.includes(itemToRemove)).toEqual(true);
+
+ component.onColumnRemoved(itemToRemove);
+
+ expect(component.visibleColumns.includes(itemToRemove)).toEqual(false);
+ });
+
+ it('should update list of available items on remove', () => {
+ const itemToRemove: ColumnMetadataWrapper = component.visibleColumns[0];
+
+ expect(component.availableColumns.includes(itemToRemove)).toEqual(false);
+
+ component.onColumnRemoved(itemToRemove);
+
+ expect(component.availableColumns.includes(itemToRemove)).toEqual(true);
+ });
+
+ it('should sort available items on change', () => {
+ spyOn(component.availableColumns, 'sort');
+
+ const itemToRemove: ColumnMetadataWrapper = component.visibleColumns[0];
+ component.onColumnRemoved(itemToRemove);
+
+ expect(component.availableColumns.sort).toHaveBeenCalled();
+ });
+
+ describe('Config Pane Rendering', () => {
+ it('should render visible and available items separately', () => {
+ expect(fixture.debugElement.queryAll(By.css('table')).length).toBe(2);
+
expect(fixture.debugElement.queryAll(By.css('table'))[0].queryAll(By.css('tr')).length).toBe(10);
+
expect(fixture.debugElement.queryAll(By.css('table'))[1].queryAll(By.css('tr')).length).toBe(11);
+ });
+
+ it('should refresh both list on remove', () => {
+
fixture.debugElement.query(By.css('[data-qe-id="remove-btn-1"]')).nativeElement.click();
+ fixture.detectChanges();
+
expect(fixture.debugElement.queryAll(By.css('table'))[0].queryAll(By.css('tr')).length).toBe(9);
+
expect(fixture.debugElement.queryAll(By.css('table'))[1].queryAll(By.css('tr')).length).toBe(12);
+ });
+
+ it('should refresh both list on add', () => {
+
fixture.debugElement.query(By.css('[data-qe-id="add-btn-4"]')).nativeElement.click();
+ fixture.detectChanges();
+
expect(fixture.debugElement.queryAll(By.css('table'))[0].queryAll(By.css('tr')).length).toBe(11);
+
expect(fixture.debugElement.queryAll(By.css('table'))[1].queryAll(By.css('tr')).length).toBe(10);
+ });
+
+ it('should be able to move visible item DOWN in order', () => {
+ const origIndex = 2;
+ const newIndex = 3;
+ let tableOfVisible =
fixture.debugElement.query(By.css('[data-qe-id="table-visible"]'));
+ const rowId =
tableOfVisible.query(By.css(`[data-qe-id="field-label-${origIndex}"]`)).nativeElement.innerText;
+
+
tableOfVisible.query(By.css(`[data-qe-id="row-${origIndex}"]`)).query(By.css('span[id^="down-"]')).nativeElement.click();
+ fixture.detectChanges();
+
+ tableOfVisible =
fixture.debugElement.query(By.css('[data-qe-id="table-visible"]'));
+
expect(tableOfVisible.query(By.css(`[data-qe-id="field-label-${newIndex}"]`)).nativeElement.innerText).toBe(rowId);
+ });
+
+ it('should be able to move visible item UP in order', () => {
+ const origIndex = 3;
+ const newIndex = 2;
+ let tableOfVisible =
fixture.debugElement.query(By.css('[data-qe-id="table-visible"]'));
+ const rowId =
tableOfVisible.query(By.css(`[data-qe-id="field-label-${origIndex}"]`)).nativeElement.innerText;
+
+
tableOfVisible.query(By.css(`[data-qe-id="row-${origIndex}"]`)).query(By.css('span[id^="up-"]')).nativeElement.click();
+ fixture.detectChanges();
+
+ tableOfVisible = fixture.debugElement.queryAll(By.css('table'))[0];
+
expect(tableOfVisible.query(By.css(`[data-qe-id="field-label-${newIndex}"]`)).nativeElement.innerText).toBe(rowId);
+ });
+ });
});
diff --git
a/metron-interface/metron-alerts/src/app/alerts/configure-table/configure-table.component.ts
b/metron-interface/metron-alerts/src/app/alerts/configure-table/configure-table.component.ts
index 800e94f..8970624 100644
---
a/metron-interface/metron-alerts/src/app/alerts/configure-table/configure-table.component.ts
+++
b/metron-interface/metron-alerts/src/app/alerts/configure-table/configure-table.component.ts
@@ -16,8 +16,8 @@
* limitations under the License.
*/
import { Component, OnInit, ViewChild, ElementRef, AfterViewInit } from
'@angular/core';
-import {Router, ActivatedRoute} from '@angular/router';
-import {forkJoin as observableForkJoin, fromEvent} from 'rxjs';
+import { Router, ActivatedRoute } from '@angular/router';
+import { forkJoin as observableForkJoin, fromEvent, Observable, Subject } from
'rxjs';
import {ConfigureTableService} from '../../service/configure-table.service';
import {ClusterMetaDataService} from '../../service/cluster-metadata.service';
@@ -50,11 +50,15 @@ export class ColumnMetadataWrapper {
})
export class ConfigureTableComponent implements OnInit, AfterViewInit {
- @ViewChild('filterColResults') filterColResults: ElementRef;
+ @ViewChild('columnFilterInput') columnFilterInput: ElementRef;
- allColumns: ColumnMetadataWrapper[] = [];
- filteredColumns: ColumnMetadataWrapper[] = [];
columnHeaders: string;
+ allColumns$: Subject<ColumnMetadataWrapper[]> = new
Subject<ColumnMetadataWrapper[]>();
+ visibleColumns$: Observable<ColumnMetadataWrapper[]>;
+ availableColumns$: Observable<ColumnMetadataWrapper[]>;
+ visibleColumns: ColumnMetadataWrapper[] = [];
+ availableColumns: ColumnMetadataWrapper[] = [];
+ filteredColumns: ColumnMetadataWrapper[] = [];
constructor(private router: Router, private activatedRoute: ActivatedRoute,
private configureTableService: ConfigureTableService,
@@ -91,12 +95,16 @@ export class ConfigureTableComponent implements OnInit,
AfterViewInit {
this.searchService.getColumnMetaData(),
this.configureTableService.getTableMetadata()
).subscribe((response: any) => {
- this.prepareData(response[0], response[1], response[2].tableColumns);
+ const allColumns = this.prepareData(response[0], response[1],
response[2].tableColumns);
+
+ this.visibleColumns = allColumns.filter(column => column.selected);
+ this.availableColumns = allColumns.filter(column => !column.selected);
+ this.filteredColumns = this.availableColumns;
});
}
ngAfterViewInit() {
- fromEvent(this.filterColResults.nativeElement, 'keyup')
+ fromEvent(this.columnFilterInput.nativeElement, 'keyup')
.pipe(debounceTime(250))
.subscribe(e => {
this.filterColumns(e['target'].value);
@@ -105,36 +113,27 @@ export class ConfigureTableComponent implements OnInit,
AfterViewInit {
filterColumns(val: string) {
const words = val.trim().split(' ');
- this.filteredColumns = this.allColumns.filter(col => {
- return !this.isColMissingFilterKeyword(words, col, col.displayName);
+ this.filteredColumns = this.availableColumns.filter(col => {
+ return !this.isColMissingFilterKeyword(words, col);
});
}
- isColMissingFilterKeyword(words: string[], col: ColumnMetadataWrapper,
displayName?: string) {
- if (displayName) {
- return !words.every(word =>
col.displayName.toLowerCase().includes(word.toLowerCase()));
- } else {
- return !words.every(word =>
col.columnMetadata.name.toLowerCase().includes(word.toLowerCase()));
- }
+ isColMissingFilterKeyword(words: string[], col: ColumnMetadataWrapper) {
+ return !words.every(word =>
col.columnMetadata.name.toLowerCase().includes(word.toLowerCase()));
}
clearFilter() {
- this.filterColResults.nativeElement.value = '';
- this.filteredColumns = this.allColumns;
- }
-
- onSelectDeselectAll($event) {
- let checked = $event.target.checked;
- this.allColumns.forEach(colMetaData => colMetaData.selected = checked);
+ this.columnFilterInput.nativeElement.value = '';
+ this.filteredColumns = this.availableColumns;
}
/* Slight variation of insertion sort with bucketing the items in the
display order*/
- prepareData(defaultColumns: ColumnMetadata[], allColumns: ColumnMetadata[],
savedColumns: ColumnMetadata[]) {
+ prepareData(defaultColumns: ColumnMetadata[], allColumns: ColumnMetadata[],
savedColumns: ColumnMetadata[]): ColumnMetadataWrapper[] {
let configuredColumns: ColumnMetadata[] = (savedColumns &&
savedColumns.length > 0) ? savedColumns : defaultColumns;
let configuredColumnNames: string[] = configuredColumns.map((mData:
ColumnMetadata) => mData.name);
allColumns = allColumns.filter((mData: ColumnMetadata) =>
configuredColumnNames.indexOf(mData.name) === -1);
- allColumns = allColumns.sort((mData1: ColumnMetadata, mData2:
ColumnMetadata) => { return mData1.name.localeCompare(mData2.name); });
+ allColumns = allColumns.sort(this.defaultColumnSorter);
let sortedConfiguredColumns =
JSON.parse(JSON.stringify(configuredColumns));
sortedConfiguredColumns = sortedConfiguredColumns.sort((mData1:
ColumnMetadata, mData2: ColumnMetadata) => {
@@ -152,11 +151,15 @@ export class ConfigureTableComponent implements OnInit,
AfterViewInit {
allColumns.splice.apply(allColumns, [indexInAll,
0].concat(itemsToInsert));
}
- this.allColumns = allColumns.map(mData => {
+ return allColumns.map(mData => {
return new ColumnMetadataWrapper(mData,
configuredColumnNames.indexOf(mData.name) > -1,
ColumnNamesService.columnNameToDisplayValueMap[mData.name]);
});
- this.filteredColumns = this.allColumns;
+ this.filteredColumns = this.availableColumns;
+ }
+
+ private defaultColumnSorter(col1: ColumnMetadata, col2: ColumnMetadata):
number {
+ return col1.name.localeCompare(col2.name);
}
postSave() {
@@ -165,21 +168,18 @@ export class ConfigureTableComponent implements OnInit,
AfterViewInit {
}
save() {
- let selectedColumns = this.allColumns.filter((mDataWrapper:
ColumnMetadataWrapper) => mDataWrapper.selected)
- .map((mDataWrapper: ColumnMetadataWrapper) =>
mDataWrapper.columnMetadata);
-
-
this.configureTableService.saveColumnMetaData(selectedColumns).subscribe(() => {
- this.saveColumnNames();
- }, error => {
- console.log('Unable to save column preferences ...');
- this.saveColumnNames();
- });
-
-
+ this.configureTableService.saveColumnMetaData(
+ this.visibleColumns.map(columnMetaWrapper =>
columnMetaWrapper.columnMetadata))
+ .subscribe(() => {
+ this.saveColumnNames();
+ }, error => {
+ console.log('Unable to save column preferences ...');
+ this.saveColumnNames();
+ });
}
saveColumnNames() {
- let columnNames = this.allColumns.map(mDataWrapper => {
+ let columnNames = this.visibleColumns.map(mDataWrapper => {
return new ColumnNames(mDataWrapper.columnMetadata.name,
mDataWrapper.displayName);
});
@@ -191,19 +191,43 @@ export class ConfigureTableComponent implements OnInit,
AfterViewInit {
});
}
- selectColumn(columns: ColumnMetadataWrapper) {
- columns.selected = !columns.selected;
+ onColumnAdded(column: ColumnMetadataWrapper) {
+ this.markColumn(column);
+ this.swapList(column, this.availableColumns, this.visibleColumns);
+ this.filterColumns(this.columnFilterInput.nativeElement.value);
+ }
+
+ onColumnRemoved(column: ColumnMetadataWrapper) {
+ this.markColumn(column);
+ this.swapList(column, this.visibleColumns, this.availableColumns);
+ this.filterColumns(this.columnFilterInput.nativeElement.value);
+ }
+
+ private markColumn(column: ColumnMetadataWrapper) {
+ column.selected = !column.selected;
+ }
+
+ private swapList(column: ColumnMetadataWrapper,
+ source: ColumnMetadataWrapper[],
+ target: ColumnMetadataWrapper[]) {
+
+ target.push(column);
+ source.splice(source.indexOf(column), 1);
+
+ this.availableColumns.sort((colWrapper1: ColumnMetadataWrapper,
colWrapper2: ColumnMetadataWrapper) => {
+ return this.defaultColumnSorter(colWrapper1.columnMetadata,
colWrapper2.columnMetadata)
+ });
}
swapUp(index: number) {
if (index > 0) {
- [this.allColumns[index], this.allColumns[index - 1]] =
[this.allColumns[index - 1], this.allColumns[index]];
+ [this.visibleColumns[index], this.visibleColumns[index - 1]] =
[this.visibleColumns[index - 1], this.visibleColumns[index]];
}
}
swapDown(index: number) {
- if (index + 1 < this.allColumns.length) {
- [this.allColumns[index], this.allColumns[index + 1]] =
[this.allColumns[index + 1], this.allColumns[index]];
+ if (index + 1 < this.visibleColumns.length) {
+ [this.visibleColumns[index], this.visibleColumns[index + 1]] =
[this.visibleColumns[index + 1], this.visibleColumns[index]];
}
}
}
diff --git a/metron-interface/metron-alerts/src/slider.scss
b/metron-interface/metron-alerts/src/slider.scss
index 5168db0..ccb139f 100644
--- a/metron-interface/metron-alerts/src/slider.scss
+++ b/metron-interface/metron-alerts/src/slider.scss
@@ -22,21 +22,18 @@ $edit-background-border: #5C5C5C;
$dialog-1x-width: 340px;
$dialog-2x-width: 680px;
-$dialog-4x-width: 1380px;
+$dialog-4x-width: 1340px;
.metron-slider-pane-details
{
- display: inline-block;
- float: right;
- word-wrap: break-word;
- height: auto;
- min-height: 100%;
- position: absolute;
-
+ position: fixed;
top: 0;
+ right: 0;
+ height: 100%;
+ max-height: 100%;
z-index: 9;
+
background: $edit-child-background;
- border: 1px solid $edit-background-border;
.close-button
{
@@ -48,10 +45,8 @@ $dialog-4x-width: 1380px;
.metron-slider-pane-editable
{
@extend .metron-slider-pane-details;
-
- height: auto;
background: $eden;
- padding-bottom: 70px;
+ padding-bottom: 80px;
border-left: 1px solid $blue-mine;
}
@@ -64,7 +59,7 @@ $dialog-4x-width: 1380px;
}
}
-@media only screen and (min-width: 2020px) {
+@media only screen and (min-width: 1900px) {
.dialog1x {
width: $dialog-2x-width;
}
@@ -125,15 +120,12 @@ $dialog-4x-width: 1380px;
@include keyframes("keyframe-dialog-rtl", "320px", "0px")
-.load-right-to-left{
+.load-right-to-left {
@include animation("keyframe-dialog-rtl", "0.5s", "linear");
-
- right: 0px;
- float: right;
}
@include keyframes("keyframe-dialog-ltr", "-320px", "0px")
-.load-left-to-right{
+.load-left-to-right {
@include animation("keyframe-dialog-ltr", "0.5s", "linear")
}
diff --git a/metron-interface/metron-alerts/src/styles.scss
b/metron-interface/metron-alerts/src/styles.scss
index 50ea6ab..4b1ce8a 100644
--- a/metron-interface/metron-alerts/src/styles.scss
+++ b/metron-interface/metron-alerts/src/styles.scss
@@ -218,6 +218,23 @@ form
border-top: 1px solid $blue-mine;
}
+.metron-button-bar-1x {
+ @extend .pb-3;
+ @extend .dialog1x;
+
+ background: $eden;
+ border-top: 1px solid $blue-mine;
+}
+
+.metron-button-bar-2x {
+ @extend .pb-3;
+ @extend .dialog2x;
+
+ background: $eden;
+ border-top: 1px solid $blue-mine;
+}
+
+
.btn-all_ports {
background-color: $all-ports;
border-color: $all-ports;
diff --git a/metron-interface/metron-alerts/src/vendor.scss
b/metron-interface/metron-alerts/src/vendor.scss
index e94169e..b5fa74c 100644
--- a/metron-interface/metron-alerts/src/vendor.scss
+++ b/metron-interface/metron-alerts/src/vendor.scss
@@ -62,6 +62,7 @@
@import "../node_modules/bootstrap/scss/media";
@import "../node_modules/bootstrap/scss/list-group";
@import "../node_modules/bootstrap/scss/close";
+@import "../node_modules/bootstrap/scss/spinners";
// Components w/ JavaScript
@import "../node_modules/bootstrap/scss/modal";