From 8e79d8f9a21237a1f004db99f3396726d4b8a255 Mon Sep 17 00:00:00 2001 From: rusikv Date: Sun, 15 Jan 2023 13:14:48 +0200 Subject: [PATCH 1/5] Alarm comments UI implementation, alarm details redesign --- .../app/core/http/alarm-comment.service.ts | 46 +++ ui-ngx/src/app/core/services/utils.service.ts | 9 +- .../alarm/alarm-comment-dialog.component.html | 51 ++++ .../alarm/alarm-comment-dialog.component.ts | 60 ++++ .../alarm/alarm-comment.component.html | 144 +++++++++ .../alarm/alarm-comment.component.scss | 106 +++++++ .../alarm/alarm-comment.component.ts | 277 ++++++++++++++++++ .../alarm/alarm-details-dialog.component.html | 92 +++--- .../alarm/alarm-details-dialog.component.scss | 46 +++ .../alarm/alarm-details-dialog.component.ts | 9 +- .../shared-home-components.module.ts | 10 +- .../lib/alarms-table-widget.component.ts | 38 +++ ...larms-table-widget-settings.component.html | 3 + .../alarms-table-widget-settings.component.ts | 1 + ui-ngx/src/app/shared/models/alarm.models.ts | 24 ++ .../app/shared/models/id/alarm-comment-id.ts | 24 ++ ui-ngx/src/app/shared/pipe/date-ago.pipe.ts | 86 ++++++ ui-ngx/src/app/shared/shared.module.ts | 8 +- .../assets/locale/locale.constant-en_US.json | 26 +- 19 files changed, 1012 insertions(+), 48 deletions(-) create mode 100644 ui-ngx/src/app/core/http/alarm-comment.service.ts create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.html create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.scss create mode 100644 ui-ngx/src/app/shared/models/id/alarm-comment-id.ts create mode 100644 ui-ngx/src/app/shared/pipe/date-ago.pipe.ts diff --git a/ui-ngx/src/app/core/http/alarm-comment.service.ts b/ui-ngx/src/app/core/http/alarm-comment.service.ts new file mode 100644 index 0000000000..f593f83235 --- /dev/null +++ b/ui-ngx/src/app/core/http/alarm-comment.service.ts @@ -0,0 +1,46 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Injectable } from '@angular/core'; +import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; +import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { PageLink } from '@shared/models/page/page-link'; +import { PageData } from '@shared/models/page/page-data'; +import { AlarmComment, AlarmCommentInfo } from '@shared/models/alarm.models'; + +@Injectable({ + providedIn: 'root' +}) +export class AlarmCommentService { + + constructor( + private http: HttpClient + ) { } + + public saveAlarmComment(alarmId: string, alarmComment: AlarmComment, config?: RequestConfig): Observable { + return this.http.post(`/api/alarm/${alarmId}/comment`, alarmComment, defaultHttpOptionsFromConfig(config)); + } + + public getAlarmComments(alarmId: string, pageLink: PageLink, config?: RequestConfig): Observable> { + return this.http.get>(`/api/alarm/${alarmId}/comment${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); + } + + public deleteAlarmComments(alarmId: string, commentId: string, config?: RequestConfig): Observable { + return this.http.delete(`/api/alarm/${alarmId}/comment/${commentId}`, defaultHttpOptionsFromConfig(config)); + } + +} diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index 66eefa9e13..1d135c4a6a 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -25,7 +25,7 @@ import { createLabelFromDatasource, deepClone, deleteNullProperties, - guid, + guid, hashCode, isDefined, isDefinedAndNotNull, isString, @@ -405,6 +405,13 @@ export class UtilsService { }); } + public stringToHslColor(str: string, saturationPercentage: number, lightnessPercentage: number): string { + if (str && str.length) { + let hue = hashCode(str) % 360; + return `hsl(${hue}, ${saturationPercentage}%, ${lightnessPercentage}%)`; + } + } + public currentPerfTime(): number { return this.window.performance && this.window.performance.now ? this.window.performance.now() : Date.now(); diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html new file mode 100644 index 0000000000..e38e3da558 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.html @@ -0,0 +1,51 @@ + + +
+ +

{{ 'alarm.comments' | translate }}

+ + +
+ + +
+
+ + +
+
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.ts new file mode 100644 index 0000000000..f0a4e3c448 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment-dialog.component.ts @@ -0,0 +1,60 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, ViewChild } from '@angular/core'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Router } from '@angular/router'; +import { AlarmInfo } from '@shared/models/alarm.models'; +import { AlarmCommentComponent } from '@home/components/alarm/alarm-comment.component'; + +export interface AlarmCommentDialogData { + alarmId?: string; + alarm?: AlarmInfo; + commentsHeaderEnabled: boolean; +} + +@Component({ + selector: 'tb-alarm-comment-dialog', + templateUrl: './alarm-comment-dialog.component.html', + styleUrls: [] +}) +export class AlarmCommentDialogComponent extends DialogComponent { + + alarmId: string; + commentsHeaderEnabled: boolean = false; + + @ViewChild('alarmCommentComponent', { static: true }) alarmCommentComponent: AlarmCommentComponent; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: AlarmCommentDialogData, + public dialogRef: MatDialogRef) { + super(store, router, dialogRef); + this.commentsHeaderEnabled = this.data.commentsHeaderEnabled + this.alarmId = this.data.alarmId; + } + + refresh() { + this.alarmCommentComponent.loadAlarmComments(); + } + + close(): void { + this.dialogRef.close(); + } +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.html new file mode 100644 index 0000000000..c0d40e744d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.html @@ -0,0 +1,144 @@ + + +
+
+ {{ 'alarm-comment.comments' | translate }} + +
+
+
+
+ + {{ displayDataElement.commentText }} + + + {{ displayDataElement.createdDateAgo }} + +
+ +
+
+ {{ getUserInitials(displayDataElement.displayName) }} +
+
+
+ {{ displayDataElement.displayName }} + + edited {{ displayDataElement.editedDateAgo }} + + + {{ displayDataElement.createdDateAgo }} + +
+ {{ displayDataElement.commentText }} +
+
+ + +
+
+ +
+
+ {{ getUserInitials(displayDataElement.displayName) }} +
+ + +
+ + +
+
+
+
+
+
+
+
+
+ {{ getUserInitials(userDisplayName) }} +
+ + + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.scss new file mode 100644 index 0000000000..bdb00d8808 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.scss @@ -0,0 +1,106 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +:host { + .tb-alarm-comments { + padding: 16px 24px 24px 24px; + background-color: #fafafa; + max-width: 600px; + + &-header { + background-color: #fafafa; + position: sticky; + top: -25px; + z-index: 1; + margin-bottom: 10px; + + &-title { + color: rgba(0, 0, 0, 0.76); + letter-spacing: 0.25px; + font-weight: 500; + } + + .mat-icon { + color: rgba(0, 0, 0, 0.38); + } + } + + &-user-avatar { + width: 30px; + height: 30px; + border-radius: 50%; + font-weight: 700; + color: #FFFFFF; + font-size: 13px; + } + + &-user-name { + font-size: 16px; + color: rgba(0, 0, 0, 0.76); + font-weight: 500; + letter-spacing: 0.25px; + } + + &-time { + font-size: 14px; + font-weight: 400; + color: rgba(0, 0, 0, 0.38); + letter-spacing: 0.2px + } + + &-system-text { + color: rgba(0, 0, 0, 0.38); + font-weight: 500; + letter-spacing: 0.25px; + } + + &-text { + white-space: pre-line; + word-break: break-word; + color: rgba(0, 0, 0, 0.54); + letter-spacing: 0.15px; + } + + &-action-buttons { + visibility: hidden; + .mat-icon { + color: rgba(0, 0, 0, 0.38); + } + } + + .show-buttons { + visibility: visible; + } + + .green-button { + color: #00695C; + } + + .red-button { + color: #D12730; + } + + .mat-form-field { + font-size: 16px; + letter-spacing: 0.15px; + color: rgba(0, 0, 0, 0.76); + } + + textarea { + letter-spacing: 0.15px; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts new file mode 100644 index 0000000000..e117eaffc4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts @@ -0,0 +1,277 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Input, OnInit } from '@angular/core'; +import { select, Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; +import { AlarmCommentService } from '@core/http/alarm-comment.service'; +import { AbstractControl, FormBuilder, FormGroup } from '@angular/forms'; +import { DialogService } from '@core/services/dialog.service'; +import { AuthUser, User } from '@shared/models/user.model'; +import { getCurrentAuthUser, selectUserDetails } from '@core/auth/auth.selectors'; +import { Direction, SortOrder } from '@shared/models/page/sort-order'; +import { MAX_SAFE_PAGE_SIZE, PageLink } from '@shared/models/page/page-link'; +import { DateAgoPipe } from '@shared/pipe/date-ago.pipe'; +import { map } from 'rxjs/operators'; +import { AlarmComment, AlarmCommentInfo, AlarmCommentType } from '@shared/models/alarm.models'; +import { UtilsService } from '@core/services/utils.service'; +import { EntityType } from '@shared/models/entity-type.models'; + +interface AlarmCommentsDisplayData { + commentId?: string, + displayName?: string, + createdDateAgo?: string, + edit?: boolean, + isEdited?: boolean, + editedDateAgo?: string, + showActions?: boolean, + commentText?: string, + isSystemComment?: boolean, + avatarBgColor?: string +} + +@Component({ + selector: 'tb-alarm-comment', + templateUrl: './alarm-comment.component.html', + styleUrls: ['./alarm-comment.component.scss'] +}) +export class AlarmCommentComponent implements OnInit { + @Input() + alarmId: string; + + @Input() + commentsHeaderEnabled: boolean = true; + + authUser: AuthUser; + + alarmCommentFormGroup: FormGroup; + + alarmComments: Array; + + displayData: Array = new Array(); + + alarmCommentSortOrder: SortOrder = { + property: 'createdTime', + direction: Direction.ASC + }; + + editMode: boolean = false; + + userDisplayName$ = this.store.pipe( + select(selectUserDetails), + map((user) => this.getUserDisplayName(user)) + ); + + currentUserDisplayName: string; + currentUserAvatarColor: string; + + constructor(protected store: Store, + private translate: TranslateService, + private alarmCommentService: AlarmCommentService, + public fb: FormBuilder, + private dialogService: DialogService, + public dateAgoPipe: DateAgoPipe, + private utilsService: UtilsService) { + + this.authUser = getCurrentAuthUser(store); + + this.alarmCommentFormGroup = this.fb.group( + { + alarmCommentEdit: [''], + alarmComment: [''] + } + ); + } + + ngOnInit() { + this.loadAlarmComments(); + this.currentUserAvatarColor = this.utilsService.stringToHslColor(this.currentUserDisplayName, + 60, 40); + } + + loadAlarmComments(): void { + this.alarmCommentService.getAlarmComments(this.alarmId, new PageLink(MAX_SAFE_PAGE_SIZE, 0, null, + this.alarmCommentSortOrder)).subscribe( + (pagedData) => { + this.alarmComments = pagedData.data; + this.displayData.length = 0; + for (let alarmComment of pagedData.data) { + let displayDataElement: AlarmCommentsDisplayData = {}; + displayDataElement.createdDateAgo = this.dateAgoPipe.transform(alarmComment.createdTime); + displayDataElement.commentText = alarmComment.comment.text; + displayDataElement.isSystemComment = alarmComment.type === AlarmCommentType.SYSTEM; + if (alarmComment.type === AlarmCommentType.OTHER) { + displayDataElement.commentId = alarmComment.id.id; + displayDataElement.displayName = this.getUserDisplayName(alarmComment); + displayDataElement.edit = false; + displayDataElement.isEdited = alarmComment.comment.edited; + displayDataElement.editedDateAgo = this.dateAgoPipe.transform(alarmComment.comment.editedOn); + displayDataElement.showActions = false; + displayDataElement.isSystemComment = false; + displayDataElement.avatarBgColor = this.utilsService.stringToHslColor(displayDataElement.displayName, + 40, 60); + } + this.displayData.push(displayDataElement); + } + } + ) + } + + saveComment(): void { + const commentInputValue: string = this.getAlarmCommentFormControl().value; + if (commentInputValue) { + const comment: AlarmComment = { + alarmId: { + id: this.alarmId, + entityType: EntityType.ALARM + }, + type: AlarmCommentType.OTHER, + comment: { + text: commentInputValue + } + } + this.doSave(comment); + this.clearCommentInput(); + } + } + + saveEditedComment(commentId: string): void { + const commentEditInputValue: string = this.getAlarmCommentEditFormControl().value; + if (commentEditInputValue) { + const editedComment: AlarmComment = this.getAlarmCommentById(commentId); + editedComment.comment.text = commentEditInputValue; + this.doSave(editedComment); + this.clearCommentEditInput(); + this.editMode = false; + this.getAlarmCommentFormControl().enable({emitEvent: false}); + } + } + + private doSave(comment: AlarmComment): void { + this.alarmCommentService.saveAlarmComment(this.alarmId, comment).subscribe( + () => { + this.loadAlarmComments(); + } + ) + } + + editComment(commentId: string): void { + const commentDisplayData = this.getDataElementByCommentId(commentId); + commentDisplayData.edit = true; + this.editMode = true; + this.getAlarmCommentEditFormControl().patchValue(commentDisplayData.commentText); + this.getAlarmCommentFormControl().disable({emitEvent: false}); + } + + cancelEdit(commentId: string): void { + const commentDisplayData = this.getDataElementByCommentId(commentId); + commentDisplayData.edit = false; + this.editMode = false; + this.getAlarmCommentFormControl().enable({emitEvent: false}); + } + + deleteComment(commentId: string): void { + const alarmCommentInfo: AlarmComment = this.getAlarmCommentById(commentId); + const commentText: string = alarmCommentInfo.comment.text; + this.dialogService.confirm( + this.translate.instant('alarm-comment.delete-alarm-comment-text'), + commentText, + this.translate.instant('action.cancel'), + this.translate.instant('action.delete')).subscribe( + (result) => { + if (result) { + this.alarmCommentService.deleteAlarmComments(this.alarmId, commentId).subscribe( + () => { + this.loadAlarmComments(); + } + ) + } + } + ) + } + + onCommentMouseEnter(commentId: string, displayDataIndex: number): void { + if (!this.editMode) { + const alarmUserId = this.getAlarmCommentById(commentId).userId.id; + if (this.authUser.userId === alarmUserId) { + this.displayData[displayDataIndex].showActions = true; + } + } + } + + onCommentMouseLeave(displayDataIndex: number): void { + this.displayData[displayDataIndex].showActions = false; + } + + getUserInitials(userName: string): string { + let initials = ''; + const userNameSplit = userName.split(' '); + initials += userNameSplit[0].charAt(0).toUpperCase(); + if (userNameSplit.length > 1) { + initials += userNameSplit[userNameSplit.length - 1].charAt(0).toUpperCase(); + } + return initials; + } + + getCurrentUserBgColor(userDisplayName: string) { + return this.utilsService.stringToHslColor(userDisplayName, 40, 60); + } + + private getUserDisplayName(alarmCommentInfo: AlarmCommentInfo | User): string { + let name = ''; + if ((alarmCommentInfo.firstName && alarmCommentInfo.firstName.length > 0) || + (alarmCommentInfo.lastName && alarmCommentInfo.lastName.length > 0)) { + if (alarmCommentInfo.firstName) { + name += alarmCommentInfo.firstName; + } + if (alarmCommentInfo.lastName) { + if (name.length > 0) { + name += ' '; + } + name += alarmCommentInfo.lastName; + } + } else { + name = alarmCommentInfo.email; + } + return name; + } + + getAlarmCommentFormControl(): AbstractControl { + return this.alarmCommentFormGroup.get('alarmComment'); + } + + getAlarmCommentEditFormControl(): AbstractControl { + return this.alarmCommentFormGroup.get('alarmCommentEdit'); + } + + private clearCommentInput(): void { + this.getAlarmCommentFormControl().patchValue(''); + } + + private clearCommentEditInput(): void { + this.getAlarmCommentEditFormControl().patchValue(''); + } + + private getAlarmCommentById(id: string): AlarmComment { + return this.alarmComments.find(comment => comment.id.id === id); + } + + private getDataElementByCommentId(commentId: string): AlarmCommentsDisplayData { + return this.displayData.find(commentDisplayData => commentDisplayData.commentId === commentId); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html index 78f72f55f4..9249137443 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html @@ -28,64 +28,78 @@
-
-
+
+
- - alarm.created-time - - - + alarm.originator -
-
- - alarm.start-time - - - - alarm.end-time - - - -
-
- - alarm.ack-time - - - - alarm.clear-time - + + alarm.created-time + -
- + alarm.type - + alarm.severity - +
+
+ alarm.status
- - + + + + + + alarm.advanced-info + + + +
+ + alarm.start-time + + + + alarm.end-time + + + +
+
+ + alarm.ack-time + + + + alarm.clear-time + + + +
+ + +
+
+
+ +
+ + +
+ + +
@@ -110,15 +122,19 @@
+ + +
-
-
- {{ getUserInitials(userDisplayName) }} -
- + +
+
+ {{ getUserInitials(userDisplayName) }} +
+ - - -
+ +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.scss index bdb00d8808..3cd97767a2 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.scss +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - :host { .tb-alarm-comments { padding: 16px 24px 24px 24px; @@ -39,8 +38,10 @@ } &-user-avatar { - width: 30px; - height: 30px; + width: 28px; + min-width: 28px; + height: 28px; + min-height: 28px; border-radius: 50%; font-weight: 700; color: #FFFFFF; diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts index e117eaffc4..ef6e782b1e 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-comment.component.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -66,7 +66,7 @@ export class AlarmCommentComponent implements OnInit { alarmCommentSortOrder: SortOrder = { property: 'createdTime', - direction: Direction.ASC + direction: Direction.DESC }; editMode: boolean = false; @@ -119,7 +119,7 @@ export class AlarmCommentComponent implements OnInit { displayDataElement.displayName = this.getUserDisplayName(alarmComment); displayDataElement.edit = false; displayDataElement.isEdited = alarmComment.comment.edited; - displayDataElement.editedDateAgo = this.dateAgoPipe.transform(alarmComment.comment.editedOn); + displayDataElement.editedDateAgo = this.dateAgoPipe.transform(alarmComment.comment.editedOn).toLowerCase(); displayDataElement.showActions = false; displayDataElement.isSystemComment = false; displayDataElement.avatarBgColor = this.utilsService.stringToHslColor(displayDataElement.displayName, @@ -131,6 +131,12 @@ export class AlarmCommentComponent implements OnInit { ) } + changeSortDirection() { + let currentDirection = this.alarmCommentSortOrder.direction; + this.alarmCommentSortOrder.direction = currentDirection === Direction.DESC ? Direction.ASC : Direction.DESC; + this.loadAlarmComments(); + } + saveComment(): void { const commentInputValue: string = this.getAlarmCommentFormControl().value; if (commentInputValue) { @@ -188,7 +194,7 @@ export class AlarmCommentComponent implements OnInit { const alarmCommentInfo: AlarmComment = this.getAlarmCommentById(commentId); const commentText: string = alarmCommentInfo.comment.text; this.dialogService.confirm( - this.translate.instant('alarm-comment.delete-alarm-comment-text'), + this.translate.instant('alarm-comment.delete-alarm-comment'), commentText, this.translate.instant('action.cancel'), this.translate.instant('action.delete')).subscribe( @@ -204,6 +210,18 @@ export class AlarmCommentComponent implements OnInit { ) } + getSortDirectionIcon() { + return this.alarmCommentSortOrder.direction === Direction.DESC ? 'arrow_downward' : 'arrow_upward' + } + + isDirectionAscending() { + return this.alarmCommentSortOrder.direction === Direction.ASC; + } + + isDirectionDescending() { + return this.alarmCommentSortOrder.direction === Direction.DESC; + } + onCommentMouseEnter(commentId: string, displayDataIndex: number): void { if (!this.editMode) { const alarmUserId = this.getAlarmCommentById(commentId).userId.id; diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.scss index e9592a0683..5af36ac0c0 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2022 The Thingsboard Authors + * Copyright © 2016-2023 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/models/id/alarm-comment-id.ts b/ui-ngx/src/app/shared/models/id/alarm-comment-id.ts index 2c6256fa11..c161e3095c 100644 --- a/ui-ngx/src/app/shared/models/id/alarm-comment-id.ts +++ b/ui-ngx/src/app/shared/models/id/alarm-comment-id.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. diff --git a/ui-ngx/src/app/shared/pipe/date-ago.pipe.ts b/ui-ngx/src/app/shared/pipe/date-ago.pipe.ts index 52fb66eb63..c0f4b5c026 100644 --- a/ui-ngx/src/app/shared/pipe/date-ago.pipe.ts +++ b/ui-ngx/src/app/shared/pipe/date-ago.pipe.ts @@ -1,5 +1,5 @@ /// -/// Copyright © 2016-2022 The Thingsboard Authors +/// Copyright © 2016-2023 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. @@ -14,70 +14,42 @@ /// limitations under the License. /// -import {Pipe, PipeTransform} from '@angular/core'; +import { Inject, Pipe, PipeTransform } from '@angular/core'; import { DAY, HOUR, MINUTE, SECOND, WEEK, YEAR } from '@shared/models/time/time.models'; import { TranslateService } from '@ngx-translate/core'; -interface DateAgoInterval { - singular: string; - plural: string; - value: number; -} +const intervals = { + years: YEAR, + months: DAY * 30, + weeks: WEEK, + days: DAY, + hr: HOUR, + min: MINUTE, + sec: SECOND +}; @Pipe({ name: 'dateAgo' }) export class DateAgoPipe implements PipeTransform { - constructor(private translate: TranslateService) { + constructor(@Inject(TranslateService) private translate: TranslateService) { + } - transform(timeStamp: number): string { - if (timeStamp) { - const secondsPassed = Math.floor((+new Date() - +new Date(timeStamp)) / 1000); - if (secondsPassed < 60) - return 'recently'; - const intervalsInSeconds: Array = [ - { - singular: 'alarm-comment.year', - plural: 'alarm-comment.years', - value: YEAR / SECOND - }, - { - singular: 'alarm-comment.month', - plural: 'alarm-comment.months', - value: DAY / SECOND * 30 - }, - { - singular: 'alarm-comment.week', - plural: 'alarm-comment.weeks', - value: WEEK / SECOND - }, - { - singular: 'alarm-comment.day', - plural: 'alarm-comment.days', - value: DAY / SECOND - }, - { - singular: 'alarm-comment.hour', - plural: 'alarm-comment.hours', - value: HOUR / SECOND - }, - { - singular: 'alarm-comment.minute', - plural: 'alarm-comment.minutes', - value: MINUTE / SECOND - }, - ] - let counter: number; - for (const interval of intervalsInSeconds) { - counter = Math.floor(secondsPassed / interval.value); - if (counter > 0) - if (counter === 1) { - return counter + ' ' + this.translate.instant(interval.singular); - } else { - return counter + ' ' + this.translate.instant(interval.plural); - } + transform(value: number): string { + if (value) { + const ms = Math.floor((+new Date() - +new Date(value))); + if (ms < 29 * SECOND) { // less than 30 seconds ago will show as 'Just now' + return this.translate.instant('timewindow.just-now'); + } + let counter; + // tslint:disable-next-line:forin + for (const i in intervals) { + counter = Math.floor(ms / intervals[i]); + if (counter > 0) { + return this.translate.instant(`timewindow.${i}`, {[i]: counter}); + } } } return ''; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 033fe7dfd2..78e3ca51c9 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -482,20 +482,9 @@ "add": "Add a comment...", "alarm-comment": "Alarm comment", "comments": "Comments", - "delete-alarm-comment-text": "Do you want to delete this comment?", - "refresh": "Refresh comments", - "minute": "minute", - "minutes": "minutes", - "hour": "hour", - "hours": "hours", - "day": "day", - "days": "days", - "week": "week", - "weeks": "weeks", - "month": "month", - "months": "months", - "year": "year", - "years": "years" + "delete-alarm-comment": "Do you want to delete this comment?", + "refresh": "Refresh", + "sort-direction": "Sort direction" }, "alias": { "add": "Add alias", @@ -3404,16 +3393,16 @@ "days": "Days" }, "timewindow": { + "years": "{ years, plural, 1 { year } other {# years } }", + "months": "{ months, plural, 1 { month } other {# months } }", + "weeks": "{ weeks, plural, 1 { week } other {# weeks } }", "days": "{ days, plural, 1 { day } other {# days } }", "hours": "{ hours, plural, 0 { hour } 1 {1 hour } other {# hours } }", + "hr": "{{ hr }} hr", "minutes": "{ minutes, plural, 0 { minute } 1 {1 minute } other {# minutes } }", + "min": "{{ min }} min", "seconds": "{ seconds, plural, 0 { second } 1 {1 second } other {# seconds } }", - "short": { - "days": "{ days, plural, 1 {1 day } other {# days } }", - "hours": "{ hours, plural, 1 {1 hour } other {# hours } }", - "minutes": "{{minutes}} min ", - "seconds": "{{seconds}} sec " - }, + "sec": "{{ sec }} sec", "realtime": "Realtime", "history": "History", "last-prefix": "last", @@ -3423,7 +3412,8 @@ "last": "Last", "time-period": "Time period", "hide": "Hide", - "interval": "Interval" + "interval": "Interval", + "just-now": "Just now" }, "user": { "user": "User", From bae2cbf78a9e7d25dd2a4cd0586eb5c749d2f76e Mon Sep 17 00:00:00 2001 From: rusikv Date: Sat, 25 Feb 2023 14:02:12 +0200 Subject: [PATCH 3/5] User or unassigned option is not displayed in available options if it is in use --- .../components/alarm/alarm-assignee-panel.component.html | 8 ++++---- .../components/alarm/alarm-assignee-panel.component.scss | 9 ++------- .../components/alarm/alarm-assignee-panel.component.ts | 2 +- .../modules/home/components/alarm/alarm-table-config.ts | 2 +- .../widget/lib/alarms-table-widget.component.ts | 2 +- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 6 files changed, 10 insertions(+), 15 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.html index 2213be1835..9829e8c436 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.html @@ -27,12 +27,12 @@ [displayWith]="displayUserFn" (optionSelected)="selected($event)" panelWidth="260px"> - + account_circle - alarm.unassigned + alarm.unassigned - + diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.scss index c76aedbb18..971e0abd55 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.scss @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + :host { width: 100%; overflow: auto; @@ -26,7 +27,6 @@ padding-top: 8px; height: 340px; font-size: 14px; - background-color: #fff; .mat-form-field-wrapper { margin-right: 8px; @@ -44,17 +44,12 @@ } .tb-assignee-autocomplete { - &.tb-assignee-autocomplete.mat-autocomplete-visible { position: relative; left: -8px; margin-top: 8px; box-shadow: none !important; } - .assigned { - background-color: rgba(0, 0, 0, 0.06); - } - .mat-option { font-size: 14px; border: none; @@ -84,8 +79,8 @@ } .user-display-name { max-width: 180px; + overflow: hidden; span { - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts index 32d6c2a1da..2cf382afcf 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts @@ -119,7 +119,7 @@ export class AlarmAssigneePanelComponent implements OnInit, AfterViewInit, OnDe } selected(event: MatAutocompleteSelectedEvent): void { - this.selectUserFormGroup.get('user').patchValue(''); + this.clear(); const user: User = event.option.value; if (user) { this.assign(user); diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts index 645841f188..dc7f990f23 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts @@ -249,7 +249,7 @@ export class AlarmTableConfig extends EntityTableConfig const connectedPosition: ConnectedPosition = { originX: 'end', originY: 'bottom', - overlayX: 'center', + overlayX: 'end', overlayY: 'top' }; config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts index 32f5c5b014..6d6d27e6e7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts @@ -1115,7 +1115,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, const connectedPosition: ConnectedPosition = { originX: 'end', originY: 'bottom', - overlayX: 'center', + overlayX: 'end', overlayY: 'top' }; config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index f5d790b618..0595e4b2b2 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -4757,7 +4757,7 @@ "display-alarm-details": "Display alarm details", "allow-alarms-ack": "Allow alarms acknowledgment", "allow-alarms-clear": "Allow alarms clear", - "display-alarm-comments": "Display alarm comments" + "display-alarm-comments": "Display alarm comments", "allow-alarms-assign": "Allow alarms assignment" }, "value-source": { From 36b987cb5f1e59497a1686b6cae94a0ec6bed2bd Mon Sep 17 00:00:00 2001 From: rusikv Date: Mon, 27 Feb 2023 13:05:05 +0200 Subject: [PATCH 4/5] Added ability to reassing alarm in alarm details dialog --- .../alarm/alarm-assignee-panel.component.scss | 3 +- .../alarm/alarm-assignee.component.html | 41 +++++ .../alarm/alarm-assignee.component.scss | 51 +++++++ .../alarm/alarm-assignee.component.ts | 141 ++++++++++++++++++ .../alarm/alarm-details-dialog.component.html | 12 +- .../alarm/alarm-details-dialog.component.ts | 64 +------- .../shared-home-components.module.ts | 7 +- 7 files changed, 248 insertions(+), 71 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.html create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.ts diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.scss index 971e0abd55..8db3bb111f 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.scss @@ -65,7 +65,6 @@ display: inline-flex; justify-content: center; align-items: center; - align-self: center; margin-right: 8px; border-radius: 50%; background-color: #5cb445; @@ -73,7 +72,7 @@ height: 28px; min-width: 28px; min-height: 28px; - color: white; + color: #fff; font-size: 13px; font-weight: 700 } diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.html new file mode 100644 index 0000000000..6e50082e8b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.html @@ -0,0 +1,41 @@ + + +
+ + + {{ getUserInitials(alarm.assignee) }} + + {{ getUserDisplayName(alarm.assignee) }} + + + account_circle + alarm.unassigned + + +
diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.scss new file mode 100644 index 0000000000..5736aa8ef1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.scss @@ -0,0 +1,51 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +:host { + .tb-assignee { + cursor: pointer; + + .assigned-container { + max-width: 233px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + .user-avatar { + display: inline-flex; + justify-content: center; + align-items: center; + margin-right: 8px; + border-radius: 50%; + width: 28px; + height: 28px; + min-width: 28px; + min-height: 28px; + color: white; + font-size: 13px; + font-weight: 700; + } + } + .material-icons.unassigned-icon { + width: 28px; + height: 28px; + font-size: 28px; + margin-right: 8px; + color: rgba(0, 0, 0, 0.38); + overflow: visible; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.ts new file mode 100644 index 0000000000..5d7f29d55b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.ts @@ -0,0 +1,141 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + Component, EventEmitter, Injector, Input, Output, StaticProvider, ViewContainerRef +} from '@angular/core'; +import { UtilsService } from '@core/services/utils.service'; +import { AlarmAssignee, AlarmInfo } from '@shared/models/alarm.models'; +import { + ALARM_ASSIGNEE_PANEL_DATA, AlarmAssigneePanelComponent, + AlarmAssigneePanelData +} from '@home/components/alarm/alarm-assignee-panel.component'; +import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; +import { ComponentPortal } from '@angular/cdk/portal'; + +@Component({ + selector: 'tb-alarm-assignee', + templateUrl: './alarm-assignee.component.html', + styleUrls: ['./alarm-assignee.component.scss'] +}) +export class AlarmAssigneeComponent { + @Input() + alarm: AlarmInfo; + + @Output() + alarmReassigned = new EventEmitter(); + + constructor(private utilsService: UtilsService, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef) { + } + + getUserDisplayName(entity: AlarmAssignee) { + let displayName = ''; + if ((entity.firstName && entity.firstName.length > 0) || + (entity.lastName && entity.lastName.length > 0)) { + if (entity.firstName) { + displayName += entity.firstName; + } + if (entity.lastName) { + if (displayName.length > 0) { + displayName += ' '; + } + displayName += entity.lastName; + } + } else { + displayName = entity.email; + } + return displayName; + } + + getUserInitials(entity: AlarmAssignee): string { + let initials = ''; + if (entity.firstName && entity.firstName.length || + entity.lastName && entity.lastName.length) { + if (entity.firstName) { + initials += entity.firstName.charAt(0); + } + if (entity.lastName) { + initials += entity.lastName.charAt(0); + } + } else { + initials += entity.email.charAt(0); + } + return initials.toUpperCase(); + } + + getFullName(entity: AlarmAssignee): string { + let fullName = ''; + if ((entity.firstName && entity.firstName.length > 0) || + (entity.lastName && entity.lastName.length > 0)) { + if (entity.firstName) { + fullName += entity.firstName; + } + if (entity.lastName) { + if (fullName.length > 0) { + fullName += ' '; + } + fullName += entity.lastName; + } + } + return fullName; + } + + getAvatarBgColor(entity: AlarmAssignee) { + return this.utilsService.stringToHslColor(this.getUserDisplayName(entity), 40, 60); + } + + openAlarmAssigneePanel($event: Event, alarm: AlarmInfo) { + if ($event) { + $event.stopPropagation(); + } + const target = $event.target || $event.srcElement || $event.currentTarget; + const config = new OverlayConfig(); + config.backdropClass = 'cdk-overlay-transparent-backdrop'; + config.hasBackdrop = true; + const connectedPosition: ConnectedPosition = { + originX: 'end', + originY: 'bottom', + overlayX: 'end', + overlayY: 'top' + }; + config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) + .withPositions([connectedPosition]); + config.minWidth = '260px'; + const overlayRef = this.overlay.create(config); + overlayRef.backdropClick().subscribe(() => { + overlayRef.dispose(); + }); + const providers: StaticProvider[] = [ + { + provide: ALARM_ASSIGNEE_PANEL_DATA, + useValue: { + alarmId: alarm.id.id, + assigneeId: alarm.assigneeId?.id + } as AlarmAssigneePanelData + }, + { + provide: OverlayRef, + useValue: overlayRef + } + ]; + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); + overlayRef.attach(new ComponentPortal(AlarmAssigneePanelComponent, + this.viewContainerRef, injector)).onDestroy(() => this.alarmReassigned.emit(true)); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html index c8bbbe5537..c1be93431d 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html @@ -56,14 +56,10 @@ alarm.status - - alarm.assignee - - {{ assigneeInitials }} - - - + +
diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts index a8d24bdafa..e86b470566 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts @@ -34,7 +34,6 @@ import { tap } from 'rxjs/operators'; import { DatePipe } from '@angular/common'; import { TranslateService } from '@ngx-translate/core'; import { AlarmCommentComponent } from '@home/components/alarm/alarm-comment.component'; -import { UtilsService } from '@core/services/utils.service'; export interface AlarmDetailsDialogData { alarmId?: string; @@ -70,8 +69,6 @@ export class AlarmDetailsDialogComponent extends DialogComponent, protected router: Router, private datePipe: DatePipe, @@ -79,8 +76,7 @@ export class AlarmDetailsDialogComponent extends DialogComponent, - public fb: UntypedFormBuilder, - private utilsService: UtilsService) { + public fb: UntypedFormBuilder) { super(store, router, dialogRef); this.allowAcknowledgment = data.allowAcknowledgment; @@ -91,15 +87,12 @@ export class AlarmDetailsDialogComponent extends DialogComponent 0) || - (entity.assignee.lastName && entity.assignee.lastName.length > 0)) { - if (entity.assignee.firstName) { - displayName += entity.assignee.firstName; - } - if (entity.assignee.lastName) { - if (displayName.length > 0) { - displayName += ' '; - } - displayName += entity.assignee.lastName; - } - } else { - displayName = entity.assignee.email; - } - return displayName; - } - - getUserInitials(entity: AlarmInfo): string { - let initials = ''; - if (entity.assignee.firstName && entity.assignee.firstName.length || - entity.assignee.lastName && entity.assignee.lastName.length) { - if (entity.assignee.firstName) { - initials += entity.assignee.firstName.charAt(0); - } - if (entity.assignee.lastName) { - initials += entity.assignee.lastName.charAt(0); - } - } else { - initials += entity.assignee.email.charAt(0); - } - return initials.toUpperCase(); + onReassign(): void { + this.alarmUpdated = true; + this.loadAlarm() + this.alarmCommentComponent.loadAlarmComments(); } - } diff --git a/ui-ngx/src/app/modules/home/components/shared-home-components.module.ts b/ui-ngx/src/app/modules/home/components/shared-home-components.module.ts index fbfc297439..865e7cf224 100644 --- a/ui-ngx/src/app/modules/home/components/shared-home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/shared-home-components.module.ts @@ -21,6 +21,7 @@ import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-detail import { SHARED_HOME_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens'; import { AlarmCommentComponent } from '@home/components/alarm/alarm-comment.component'; import { AlarmCommentDialogComponent } from '@home/components/alarm/alarm-comment-dialog.component'; +import { AlarmAssigneeComponent } from '@home/components/alarm/alarm-assignee.component'; @NgModule({ providers: [ @@ -30,7 +31,8 @@ import { AlarmCommentDialogComponent } from '@home/components/alarm/alarm-commen [ AlarmDetailsDialogComponent, AlarmCommentComponent, - AlarmCommentDialogComponent + AlarmCommentDialogComponent, + AlarmAssigneeComponent ], imports: [ CommonModule, @@ -39,7 +41,8 @@ import { AlarmCommentDialogComponent } from '@home/components/alarm/alarm-commen exports: [ AlarmDetailsDialogComponent, AlarmCommentComponent, - AlarmCommentDialogComponent + AlarmCommentDialogComponent, + AlarmAssigneeComponent ] }) export class SharedHomeComponentsModule { } From b1e33b4be02ffa92ee7830f4de052084343d7419 Mon Sep 17 00:00:00 2001 From: rusikv Date: Mon, 27 Feb 2023 15:56:14 +0200 Subject: [PATCH 5/5] Added assignee coulumn and comments action button to load demo and default alarm table widgets --- .../main/data/json/demo/dashboards/thermostats.json | 10 ++++++++++ .../data/json/system/widget_bundles/alarm_widgets.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/application/src/main/data/json/demo/dashboards/thermostats.json b/application/src/main/data/json/demo/dashboards/thermostats.json index 4f486effdf..c014d8875a 100644 --- a/application/src/main/data/json/demo/dashboards/thermostats.json +++ b/application/src/main/data/json/demo/dashboards/thermostats.json @@ -215,6 +215,8 @@ "displayDetails": true, "allowAcknowledgment": true, "allowClear": true, + "allowAssign": true, + "displayComments": true, "displayPagination": true, "defaultPageSize": 10, "defaultSortOrder": "-createdTime", @@ -277,6 +279,14 @@ "color": "#607d8b", "settings": {}, "_hash": 0.7977920750136249 + }, + { + "name": "assignee", + "type": "alarm", + "label": "Assignee", + "color": "#9c27b0", + "settings": {}, + "_hash": 0.8678751039018493 } ] }, diff --git a/application/src/main/data/json/system/widget_bundles/alarm_widgets.json b/application/src/main/data/json/system/widget_bundles/alarm_widgets.json index 448fcaf52c..e7a97b36f8 100644 --- a/application/src/main/data/json/system/widget_bundles/alarm_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/alarm_widgets.json @@ -23,7 +23,7 @@ "dataKeySettingsSchema": "", "settingsDirective": "tb-alarms-table-widget-settings", "dataKeySettingsDirective": "tb-alarms-table-key-settings", - "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"allowAcknowledgment\":true,\"allowClear\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\",\"enableSelectColumnDisplay\":true,\"enableStickyAction\":false,\"enableFilter\":true},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"alarmSource\":{\"type\":\"function\",\"dataKeys\":[{\"name\":\"createdTime\",\"type\":\"alarm\",\"label\":\"Created time\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.021092237451093787},{\"name\":\"originator\",\"type\":\"alarm\",\"label\":\"Originator\",\"color\":\"#4caf50\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.2780007688856758},{\"name\":\"type\",\"type\":\"alarm\",\"label\":\"Type\",\"color\":\"#f44336\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.7323586880398418},{\"name\":\"severity\",\"type\":\"alarm\",\"label\":\"Severity\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":false,\"useCellContentFunction\":false},\"_hash\":0.09927019860088193},{\"name\":\"status\",\"type\":\"alarm\",\"label\":\"Status\",\"color\":\"#607d8b\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6588418951443418}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{},\"alarmStatusList\":[],\"alarmSeverityList\":[],\"alarmTypeList\":[],\"searchPropagatedAlarms\":false}" + "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"allowAcknowledgment\":true,\"allowClear\":true,\"allowAssign\":true,\"displayComments\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\",\"enableSelectColumnDisplay\":true,\"enableStickyAction\":false,\"enableFilter\":true},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"alarmSource\":{\"type\":\"function\",\"dataKeys\":[{\"name\":\"createdTime\",\"type\":\"alarm\",\"label\":\"Created time\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.021092237451093787},{\"name\":\"originator\",\"type\":\"alarm\",\"label\":\"Originator\",\"color\":\"#4caf50\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.2780007688856758},{\"name\":\"type\",\"type\":\"alarm\",\"label\":\"Type\",\"color\":\"#f44336\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.7323586880398418},{\"name\":\"severity\",\"type\":\"alarm\",\"label\":\"Severity\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":false,\"useCellContentFunction\":false},\"_hash\":0.09927019860088193},{\"name\":\"status\",\"type\":\"alarm\",\"label\":\"Status\",\"color\":\"#607d8b\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6588418951443418},{\"name\":\"assignee\",\"type\":\"alarm\",\"label\":\"Assignee\",\"color\":\"#9c27b0\",\"settings\":{},\"_hash\":0.5008441077416634}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{},\"alarmStatusList\":[],\"alarmSeverityList\":[],\"alarmTypeList\":[],\"searchPropagatedAlarms\":false}" } } ]