Skip to content

Commit

Permalink
[ACS-8312] Display warning about losing response (#10059)
Browse files Browse the repository at this point in the history
* [ACS-8312] Display warning about losing response

* [ACS-8312] Display warning about losing response - fixes
  • Loading branch information
jacekpluta authored Aug 12, 2024
1 parent 20c4bfe commit 135cf10
Show file tree
Hide file tree
Showing 10 changed files with 231 additions and 89 deletions.
42 changes: 18 additions & 24 deletions lib/core/src/lib/app-config/app-config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ export enum AppConfigValues {
STORAGE_PREFIX = 'application.storagePrefix',
NOTIFY_DURATION = 'notificationDefaultDuration',
CONTENT_TICKET_STORAGE_LABEL = 'ticket-ECM',
PROCESS_TICKET_STORAGE_LABEL = 'ticket-BPM'
PROCESS_TICKET_STORAGE_LABEL = 'ticket-BPM',
UNSAVED_CHANGES_MODAL_HIDDEN = 'unsaved_changes__modal_hidden'
}

// eslint-disable-next-line no-shadow
Expand All @@ -63,7 +64,6 @@ export enum Status {
providedIn: 'root'
})
export class AppConfigService {

config: any = {
application: {
name: 'Alfresco ADF Application'
Expand Down Expand Up @@ -97,11 +97,10 @@ export class AppConfigService {
* @returns Property value, when loaded
*/
select(property: string): Observable<any> {
return this.onLoadSubject
.pipe(
map((config) => ObjectUtils.getValue(config, property)),
distinctUntilChanged()
);
return this.onLoadSubject.pipe(
map((config) => ObjectUtils.getValue(config, property)),
distinctUntilChanged()
);
}

/**
Expand Down Expand Up @@ -170,9 +169,7 @@ export class AppConfigService {
protected onDataLoaded() {
this.onLoadSubject.next(this.config);

this.extensionService.setup$
.pipe(take(1))
.subscribe((config) => this.onExtensionsLoaded(config));
this.extensionService.setup$.pipe(take(1)).subscribe((config) => this.onExtensionsLoaded(config));
}

protected onExtensionsLoaded(config: ExtensionConfig) {
Expand Down Expand Up @@ -227,20 +224,18 @@ export class AppConfigService {
* @param hostIdp host address
* @returns Discovery configuration
*/
loadWellKnown(hostIdp: string): Promise<OpenidConfiguration> {
loadWellKnown(hostIdp: string): Promise<OpenidConfiguration> {
return new Promise((resolve, reject) => {
this.http
.get<OpenidConfiguration>(`${hostIdp}/.well-known/openid-configuration`)
.subscribe({
next: (res: OpenidConfiguration) => {
resolve(res);
},
error: (err: any) => {
// eslint-disable-next-line no-console
console.error('hostIdp not correctly configured or unreachable');
reject(err);
}
});
this.http.get<OpenidConfiguration>(`${hostIdp}/.well-known/openid-configuration`).subscribe({
next: (res: OpenidConfiguration) => {
resolve(res);
},
error: (err: any) => {
// eslint-disable-next-line no-console
console.error('hostIdp not correctly configured or unreachable');
reject(err);
}
});
});
}

Expand Down Expand Up @@ -273,5 +268,4 @@ export class AppConfigService {

return result;
}

}
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
<h1 mat-dialog-title class="adf-unsaved-changes-dialog-title">
{{ 'CORE.DIALOG.UNSAVED_CHANGES.TITLE' | translate }}
<h1 mat-dialog-title class="adf-unsaved-changes-dialog-header">
{{ dialogData.headerText | translate }}
<button
data-automation-id="adf-unsaved-changes-dialog-close-button"
class="adf-unsaved-changes-dialog-header-close-button"
mat-icon-button
[title]="'CLOSE' | translate"
[mat-dialog-close]="false">
<mat-icon>close</mat-icon>
</button>
</h1>
<mat-dialog-content>
{{ 'CORE.DIALOG.UNSAVED_CHANGES.DESCRIPTION' | translate }}
<mat-dialog-content class="adf-unsaved-changes-dialog-content">
{{ dialogData.descriptionText | translate }}
<div class="adf-unsaved-changes-dialog-content-checkbox" *ngIf="dialogData.checkboxText.length">
<mat-checkbox data-automation-id="adf-unsaved-changes-dialog-content-checkbox"
(change)="onToggleCheckboxPreferences($event)"
>{{ dialogData.checkboxText | translate }}</mat-checkbox>
</div>
</mat-dialog-content>
<mat-dialog-actions align="end">
<mat-dialog-actions align="end" class="adf-unsaved-changes-dialog-actions">
<button
data-automation-id="adf-unsaved-changes-dialog-cancel-button"
mat-button
[mat-dialog-close]="false"
class="adf-unsaved-changes-dialog-cancel-button">
class="adf-unsaved-changes-dialog-actions-cancel-button">
{{ 'CANCEL' | translate | titlecase }}
</button>
<button
data-automation-id="adf-unsaved-changes-dialog-discard-changes-button"
mat-button
[mat-dialog-close]="true"
class="adf-unsaved-changes-dialog-discard-changes-button">
{{ 'CORE.DIALOG.UNSAVED_CHANGES.DISCARD_CHANGES_BUTTON' | translate }}
class="adf-unsaved-changes-dialog-actions-discard-changes-button">
{{ dialogData.confirmButtonText | translate }}
</button>
</mat-dialog-actions>
Original file line number Diff line number Diff line change
@@ -1,33 +1,60 @@
adf-unsaved-changes-dialog {
margin-top: -4px;
display: block;

.adf-unsaved-changes-dialog {
.adf-unsaved-changes-dialog {
&-title {
&-header {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 16px;
font-weight: bold;
}
font-size: 18px;
font-weight: 700;
margin-bottom: 16px;
height: 24px;

&-close-button {
margin-right: -16px;
margin-bottom: 2px;
}

&-cancel-button {
background-color: var(--adf-secondary-button-background);
margin-right: 4px;
&::before {
display: none;
}
}

&-discard-changes-button {
color: var(--theme-warn-color-default-contrast);
background-color: var(--adf-error-color);
min-width: 143px;
&-content {
padding: 0 8px 0 0;
overflow: unset;
color: var(--adf-secondary-modal-text-color);

&-checkbox {
margin-top: 20px;

label {
color: var(--adf-secondary-modal-text-color);
}
}
}

&-cancel-button,
&-discard-changes-button {
padding: 4px 14px;
height: 32px;
display: flex;
align-items: center;
&-actions {
margin-top: 18px;
margin-bottom: 4px;
padding: 0;

&-cancel-button {
background-color: var(--adf-secondary-button-background);
margin-right: 4px;
}

&-discard-changes-button {
color: white;
background-color: var(--adf-error-color);
}

&-cancel-button,
&-discard-changes-button {
padding: 4px 12px;
height: 32px;
display: flex;
align-items: center;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,39 +16,66 @@
*/

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CoreTestingModule, UnsavedChangesDialogComponent } from '@alfresco/adf-core';
import { CoreTestingModule, StorageService, UnsavedChangesDialogComponent } from '@alfresco/adf-core';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { MatDialogClose } from '@angular/material/dialog';
import { MAT_DIALOG_DATA, MatDialogClose } from '@angular/material/dialog';
import { UnsavedChangesDialogData } from './unsaved-changes-dialog.model';

describe('UnsavedChangesDialog', () => {
let fixture: ComponentFixture<UnsavedChangesDialogComponent>;
let storageServiceMock: any;
let savePreferenceCheckbox: DebugElement;

const setupBeforeEach = (unsavedChangesDialogData?: UnsavedChangesDialogData) => {
storageServiceMock = {
getItem: jasmine.createSpy('getItem'),
setItem: jasmine.createSpy('setItem')
};

beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreTestingModule]
imports: [CoreTestingModule],
providers: [
{ provide: StorageService, useValue: storageServiceMock },
{
provide: MAT_DIALOG_DATA,
useValue: unsavedChangesDialogData ?? {}
}
]
});

fixture = TestBed.createComponent(UnsavedChangesDialogComponent);
fixture.detectChanges();
});
savePreferenceCheckbox = fixture.debugElement.query(By.css('[data-automation-id="adf-unsaved-changes-dialog-content-checkbox"]'));
};

describe('Close icon button', () => {
let closeIconButton: DebugElement;
const getElements = (): { header: HTMLElement; content: HTMLElement; discardChangesButton: HTMLElement } => {
const header = fixture.nativeElement.querySelector('.adf-unsaved-changes-dialog-header');
const content = fixture.nativeElement.querySelector('.adf-unsaved-changes-dialog-content');
const discardChangesButton = fixture.nativeElement.querySelector('.adf-unsaved-changes-dialog-actions-discard-changes-button');
return { header, content, discardChangesButton };
};

describe('when data is not present in dialog', () => {
beforeEach(() => {
closeIconButton = fixture.debugElement.query(By.css('[data-automation-id="adf-unsaved-changes-dialog-close-button"]'));
setupBeforeEach();
});

it('should have assigned dialog close button with false as result', () => {
expect(closeIconButton.injector.get(MatDialogClose).dialogResult).toBeFalse();
it('should display correct text if there is no data object', () => {
const { header, content, discardChangesButton } = getElements();
expect(header.textContent).toContain('CORE.DIALOG.UNSAVED_CHANGES.TITLE');
expect(content.textContent).toContain('CORE.DIALOG.UNSAVED_CHANGES.DESCRIPTION');
expect(discardChangesButton.textContent).toContain('CORE.DIALOG.UNSAVED_CHANGES.DISCARD_CHANGES_BUTTON');
});

it('should have displayed correct icon', () => {
expect(closeIconButton.nativeElement.textContent).toBe('close');
it('should have assigned dialog close button with true as result', () => {
expect(
fixture.debugElement
.query(By.css('[data-automation-id="adf-unsaved-changes-dialog-discard-changes-button"]'))
.injector.get(MatDialogClose).dialogResult
).toBeTrue();
});
});

describe('Cancel button', () => {
it('should have assigned dialog close button with false as result', () => {
expect(
fixture.debugElement.query(By.css('[data-automation-id="adf-unsaved-changes-dialog-cancel-button"]')).injector.get(MatDialogClose)
Expand All @@ -57,13 +84,35 @@ describe('UnsavedChangesDialog', () => {
});
});

describe('Discard changes button', () => {
it('should have assigned dialog close button with true as result', () => {
expect(
fixture.debugElement
.query(By.css('[data-automation-id="adf-unsaved-changes-dialog-discard-changes-button"]'))
.injector.get(MatDialogClose).dialogResult
).toBeTrue();
describe('when data is present in dialog', () => {
beforeEach(() => {
setupBeforeEach({
headerText: 'headerText',
descriptionText: 'descriptionText',
confirmButtonText: 'confirmButtonText',
checkboxText: 'checkboxText'
});
fixture.detectChanges();
});

it('should display correct text if there is data object', () => {
const { header, content, discardChangesButton } = getElements();

expect(header.textContent).toContain('headerText');
expect(content.textContent).toContain('descriptionText checkboxText');
expect(discardChangesButton.textContent).toContain('confirmButtonText');
});

it('should update storageService to true when checkbox is checked', () => {
const event = { checked: true };
savePreferenceCheckbox.triggerEventHandler('change', event);
expect(storageServiceMock.setItem).toHaveBeenCalledWith(UnsavedChangesDialogComponent.UNSAVED_CHANGES_MODAL_HIDDEN, 'true');
});

it('should update storageService to false when checkbox is unchecked', () => {
const event = { checked: false };
savePreferenceCheckbox.triggerEventHandler('change', event);
expect(storageServiceMock.setItem).toHaveBeenCalledWith(UnsavedChangesDialogComponent.UNSAVED_CHANGES_MODAL_HIDDEN, 'false');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,55 @@
* limitations under the License.
*/

import { Component, ViewEncapsulation } from '@angular/core';
import { Component, Inject, OnInit, ViewEncapsulation } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import { UnsavedChangesDialogData } from './unsaved-changes-dialog.model';
import { MatCheckboxChange, MatCheckboxModule } from '@angular/material/checkbox';
import { ReactiveFormsModule } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { CommonModule } from '@angular/common';
import { StorageService } from '../../common';
import { AppConfigValues } from '../../app-config';

/**
* Dialog which informs about unsaved changes. Allows discard them and proceed or close dialog and stop proceeding.
* Can be customized with data object - UnsavedChangesDialogData.
* If data.checkboxText is provided, checkbox will be displayed with the checkbox description.
* If data.confirmButtonText is provided, it will be displayed on the confirm button.
* If data.headerText is provided, it will be displayed as the header.
* If data.descriptionText is provided, it will be displayed as dialog content.
*/
@Component({
standalone: true,
selector: 'adf-unsaved-changes-dialog',
encapsulation: ViewEncapsulation.None,
templateUrl: './unsaved-changes-dialog.component.html',
styleUrls: ['./unsaved-changes-dialog.component.scss']
styleUrls: ['./unsaved-changes-dialog.component.scss'],
host: { class: 'adf-unsaved-changes-dialog' },
imports: [MatDialogModule, TranslateModule, MatButtonModule, MatIconModule, CommonModule, MatCheckboxModule, ReactiveFormsModule]
})
export class UnsavedChangesDialogComponent {}
export class UnsavedChangesDialogComponent implements OnInit {
dialogData: UnsavedChangesDialogData;

constructor(@Inject(MAT_DIALOG_DATA) public data: UnsavedChangesDialogData, private storageService: StorageService) {}

ngOnInit() {
this.dialogData = {
headerText: this.data?.headerText ?? 'CORE.DIALOG.UNSAVED_CHANGES.TITLE',
descriptionText: this.data?.descriptionText ?? 'CORE.DIALOG.UNSAVED_CHANGES.DESCRIPTION',
confirmButtonText: this.data?.confirmButtonText ?? 'CORE.DIALOG.UNSAVED_CHANGES.DISCARD_CHANGES_BUTTON',
checkboxText: this.data?.checkboxText ?? ''
};
}

/**
* Sets 'unsaved_ai_changes__modal_visible' checked state (true or false string) as new item in local storage.
*
* @param savePreferences - MatCheckboxChange object with information about checkbox state.
*/
onToggleCheckboxPreferences(savePreferences: MatCheckboxChange) {
this.storageService.setItem(AppConfigValues.UNSAVED_CHANGES_MODAL_HIDDEN, savePreferences.checked.toString());
}
}
Loading

0 comments on commit 135cf10

Please sign in to comment.