diff --git a/src/app/modules/alerts/components/alert/alert.component.html b/src/app/modules/alerts/components/alert/alert.component.html
index 5ca6d065d7c..f27c2116be4 100644
--- a/src/app/modules/alerts/components/alert/alert.component.html
+++ b/src/app/modules/alerts/components/alert/alert.component.html
@@ -12,7 +12,23 @@
{{ levelLabel() }}
}
-
+
+ @if (isExpandable()) {
+
+ }
@if (isHaLicensed()) {
{{ alert().node }}
}
diff --git a/src/app/modules/alerts/components/alert/alert.component.scss b/src/app/modules/alerts/components/alert/alert.component.scss
index 8351037f578..72ef8a39d51 100644
--- a/src/app/modules/alerts/components/alert/alert.component.scss
+++ b/src/app/modules/alerts/components/alert/alert.component.scss
@@ -1,3 +1,5 @@
+@import 'mixins/text';
+
:host {
align-items: center;
display: flex;
@@ -48,6 +50,29 @@
line-height: inherit;
margin-bottom: 6px;
margin-top: 3px;
+
+ &.collapsed {
+ @include line-clamp(5);
+ }
+}
+
+.expand-collapse-button {
+ background-color: var(--alt-bg1);
+ cursor: pointer;
+ display: flex;
+ font-size: inherit;
+ font-weight: normal;
+ height: inherit;
+ justify-content: center;
+ line-height: inherit;
+ margin: 0 0 5px;
+ opacity: 0.9;
+ padding: 0;
+ width: max-content;
+
+ &.collapsed {
+ margin-top: -8px;
+ }
}
.alert-node {
diff --git a/src/app/modules/alerts/components/alert/alert.component.spec.ts b/src/app/modules/alerts/components/alert/alert.component.spec.ts
index bfae7af8c5b..3bc99bd3638 100644
--- a/src/app/modules/alerts/components/alert/alert.component.spec.ts
+++ b/src/app/modules/alerts/components/alert/alert.component.spec.ts
@@ -1,3 +1,7 @@
+import { HarnessLoader } from '@angular/cdk/testing';
+import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
+import { MatButtonHarness } from '@angular/material/button/testing';
+import { By } from '@angular/platform-browser';
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { EffectsModule } from '@ngrx/effects';
import { Store, StoreModule } from '@ngrx/store';
@@ -34,6 +38,8 @@ describe('AlertComponent', () => {
let spectator: Spectator;
let api: ApiService;
let alert: AlertPageObject;
+ let loader: HarnessLoader;
+
const createComponent = createComponentFactory({
component: AlertComponent,
imports: [
@@ -73,6 +79,7 @@ describe('AlertComponent', () => {
api = spectator.inject(ApiService);
alert = new AlertPageObject(spectator);
+ loader = TestbedHarnessEnvironment.loader(spectator.fixture);
});
it('shows alert level', () => {
@@ -127,4 +134,23 @@ describe('AlertComponent', () => {
const state = await firstValueFrom(spectator.inject(Store).pipe(map(selectAlerts)));
expect(state).toEqual([dummyAlert]);
});
+
+ it('shows expand/collapse button when alert message is too long', async () => {
+ const longMessage = 'This is a very long alert message '.repeat(10);
+ spectator.setInput('alert', { ...dummyAlert, formatted: longMessage } as Alert);
+
+ const alertMessageElement = spectator.debugElement.query(By.css('.alert-message')).nativeElement as HTMLElement;
+ jest.spyOn(alertMessageElement, 'scrollHeight', 'get').mockReturnValue(300);
+ jest.spyOn(alertMessageElement, 'offsetHeight', 'get').mockReturnValue(100);
+
+ spectator.component.ngAfterViewInit();
+
+ const expandButton = await loader.getHarness(MatButtonHarness.with({ text: 'Expand' }));
+ expect(expandButton).toExist();
+
+ await expandButton.click();
+
+ const collapseButton = await loader.getHarness(MatButtonHarness.with({ text: 'Collapse' }));
+ expect(collapseButton).toExist();
+ });
});
diff --git a/src/app/modules/alerts/components/alert/alert.component.ts b/src/app/modules/alerts/components/alert/alert.component.ts
index ac95cebcb59..933ebfe773d 100644
--- a/src/app/modules/alerts/components/alert/alert.component.ts
+++ b/src/app/modules/alerts/components/alert/alert.component.ts
@@ -1,7 +1,11 @@
import { AsyncPipe } from '@angular/common';
import {
- ChangeDetectionStrategy, Component, computed, HostBinding, input, OnChanges,
+ AfterViewInit,
+ ChangeDetectionStrategy, Component, computed, ElementRef, HostBinding, input, OnChanges,
+ signal,
+ ViewChild,
} from '@angular/core';
+import { MatButton } from '@angular/material/button';
import { MatTooltip } from '@angular/material/tooltip';
import { UntilDestroy } from '@ngneat/until-destroy';
import { Store } from '@ngrx/store';
@@ -43,6 +47,7 @@ enum AlertLevelColor {
imports: [
IxIconComponent,
MatTooltip,
+ MatButton,
TestDirective,
TranslateModule,
FormatDateTimePipe,
@@ -50,10 +55,16 @@ enum AlertLevelColor {
RequiresRolesDirective,
],
})
-export class AlertComponent implements OnChanges {
+export class AlertComponent implements OnChanges, AfterViewInit {
readonly alert = input.required();
readonly isHaLicensed = input();
- readonly requiredRoles = [Role.AlertListWrite];
+
+ @ViewChild('alertMessage', { static: true }) alertMessage: ElementRef;
+
+ protected isCollapsed = signal(true);
+ protected isExpandable = signal(false);
+
+ protected readonly requiredRoles = [Role.AlertListWrite];
alertLevelColor: AlertLevelColor | undefined;
icon: string;
@@ -80,6 +91,15 @@ export class AlertComponent implements OnChanges {
this.setStyles();
}
+ ngAfterViewInit(): void {
+ const alertMessageElement = this.alertMessage.nativeElement;
+ this.isExpandable.set(alertMessageElement.scrollHeight > alertMessageElement.offsetHeight);
+ }
+
+ toggleCollapse(): void {
+ this.isCollapsed.set(!this.isCollapsed());
+ }
+
onDismiss(): void {
this.store$.dispatch(dismissAlertPressed({ id: this.alert().id }));
}
diff --git a/src/assets/i18n/uk.json b/src/assets/i18n/uk.json
index 8f14b1a4188..38cdad413ef 100644
--- a/src/assets/i18n/uk.json
+++ b/src/assets/i18n/uk.json
@@ -4966,8 +4966,8 @@
"Title": "Заголовок",
"To activate this periodic snapshot schedule, set this option. To disable this task without deleting it, unset this option.": "Щоб активувати цей періодичний розклад знімків, установіть цей параметр. Щоб вимкнути це завдання, не видаляючи його, зніміть цей параметр.",
"To configure Isolated GPU Device(s), click the \"Configure\" button.": "Щоб налаштувати ізольований пристрій (пристрої GPU), натисніть кнопку \"Налаштувати\".",
- "Toggle Collapse": "Переключити Стиснення",
- "Toggle {row}": "Перемикач {row}",
+ "Toggle Collapse": "Переключити Згортання",
+ "Toggle {row}": "Переключити {row}",
"Token": "Токен",
"Token created with Google Drive.": "Токен створений за допомогою Диска Google.",
"Token created with Google Drive. Access Tokens expire periodically and must be refreshed.": "Токен, створений за допомогою Google Диска. Токени доступу періодично закінчуються і повинні оновлюватися.",