diff --git a/cypress.config.ts b/cypress.config.ts index b2ed656b..417d616b 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -12,6 +12,8 @@ export default defineConfig({ '**/cypress/e2e/1-getting-started/**', '**/cypress/e2e/2-advanced-examples/**', ], + viewportWidth: 1920, + viewportHeight: 1080, experimentalRunAllSpecs: true, }, }); diff --git a/cypress/e2e/debug_aboutOpenedReports.cy.js b/cypress/e2e/debug_aboutOpenedReports.cy.js index 4e3bd827..0533e6dc 100644 --- a/cypress/e2e/debug_aboutOpenedReports.cy.js +++ b/cypress/e2e/debug_aboutOpenedReports.cy.js @@ -7,6 +7,7 @@ describe("About opened reports", function () { }); it("Close one", function () { + cy.enableShowMultipleInDebugTree(); cy.get("[data-cy-select-all-reports]").click(); cy.get('button[id="OpenSelectedReportsButton"]').click(); // Each of the two reports has three lines. @@ -31,6 +32,7 @@ describe("About opened reports", function () { }); it("Close all", function () { + cy.enableShowMultipleInDebugTree(); cy.get(".table-responsive tbody tr td:contains(Simple report)") .first() .click(); diff --git a/cypress/e2e/testtab.cy.js b/cypress/e2e/testtab.cy.js index dabfe3a6..f62ceec2 100644 --- a/cypress/e2e/testtab.cy.js +++ b/cypress/e2e/testtab.cy.js @@ -25,7 +25,7 @@ describe("About the Test tab", function () { // }); }); - it("Test deleting a report", function () { + it("Test deleting a report", () => { cy.get("li#testTab").click(); cy.get("#testReports").find("tr").should("have.length", 2); cy.functions.testTabDeselectReportNamed("/Another simple report"); @@ -167,6 +167,7 @@ describe("About the Test tab", function () { }); function copyTheReportsToTestTab() { + cy.enableShowMultipleInDebugTree(); cy.get("[data-cy-select-all-reports]").click(); cy.get('button[id="OpenSelectedReportsButton"]').click(); // We test many times already that opening two reports yields six nodes. diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 4e3980f7..7fed24d9 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -146,3 +146,9 @@ Cypress.Commands.add( { prevSubject: 'element' }, (node) => selectIfNotSelected(node) ); + +Cypress.Commands.add('enableShowMultipleInDebugTree' as keyof Chainable, () => { + cy.get('[data-cy-open-settings-modal]').click(); + cy.get('input[data-cy-toggle-show-amount]').click(); + cy.get('[data-cy-settings-modal-save-changes]').click(); +}); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index d6d825e1..4b68db46 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -8,6 +8,7 @@ import { Report } from './shared/interfaces/report'; import { TestComponent } from './test/test.component'; import { DynamicService } from './shared/services/dynamic.service'; import { CompareData } from './compare/compare-data'; +import { SettingsService } from './shared/services/settings.service'; declare var require: any; const { version: appVersion } = require('../../package.json'); @@ -31,7 +32,9 @@ export class AppComponent implements AfterViewInit { private inj: Injector, private titleService: Title, private httpService: HttpService, - private dynamicService: DynamicService + private dynamicService: DynamicService, + //make sure settings are retrieved from localstorage on startup by initializing the service on startup + private settingsService: SettingsService ) { this.appVersion = appVersion; this.titleService.setTitle('Ladybug - v' + this.appVersion); diff --git a/src/app/debug/debug-tree/debug-tree.component.css b/src/app/debug/debug-tree/debug-tree.component.css index 2f31c15f..033b1f2f 100644 --- a/src/app/debug/debug-tree/debug-tree.component.css +++ b/src/app/debug/debug-tree/debug-tree.component.css @@ -23,3 +23,4 @@ input[type=text] { input[type=text]:focus { width: 30%; } + diff --git a/src/app/debug/debug-tree/debug-tree.component.html b/src/app/debug/debug-tree/debug-tree.component.html index f51e6496..87433890 100644 --- a/src/app/debug/debug-tree/debug-tree.component.html +++ b/src/app/debug/debug-tree/debug-tree.component.html @@ -13,7 +13,6 @@ <button ngbDropdownItem (click)="downloadReports(true, false)">Binary</button> </div> </div> - <app-button [text]="showOneAtATime ? 'Show multiple' : 'Show one'" (click)="toggleShowAmount()"></app-button> <input class="my-2 mx-1 pl-1" type="text" name="search" placeholder="" (keyup)="changeSearchTerm($event)"/> </div> <jqxTree id="debug-tree" #treeReference (onSelect)="selectReport($event)" [auto-create]="false"></jqxTree> diff --git a/src/app/debug/debug-tree/debug-tree.component.ts b/src/app/debug/debug-tree/debug-tree.component.ts index 25d0cd90..95113b11 100644 --- a/src/app/debug/debug-tree/debug-tree.component.ts +++ b/src/app/debug/debug-tree/debug-tree.component.ts @@ -1,23 +1,27 @@ -import { AfterViewInit, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, EventEmitter, Input, OnDestroy, Output, ViewChild } from '@angular/core'; import { Report } from '../../shared/interfaces/report'; import { jqxTreeComponent } from 'jqwidgets-ng/jqxtree'; import { HelperService } from '../../shared/services/helper.service'; -import { Observable } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; import { HttpService } from '../../shared/services/http.service'; +import { SettingsService } from '../../shared/services/settings.service'; @Component({ selector: 'app-debug-tree', templateUrl: './debug-tree.component.html', styleUrls: ['./debug-tree.component.css'], }) -export class DebugTreeComponent implements AfterViewInit { +export class DebugTreeComponent implements AfterViewInit, OnDestroy { @ViewChild('treeReference') treeReference!: jqxTreeComponent; @Output() selectReportEvent = new EventEmitter<any>(); @Output() closeEntireTreeEvent = new EventEmitter<any>(); loaded: boolean = false; - showOneAtATime: boolean = false; + showMultipleAtATime!: boolean; + showMultipleAtATimeSubscription!: Subscription; private _currentView: any; + private lastReport?: Report; + @Input() set currentView(value: any) { if (this.loaded && this._currentView !== value) { // TODO: Check if the current reports are part of the view @@ -29,13 +33,40 @@ export class DebugTreeComponent implements AfterViewInit { get currentView(): any { return this._currentView; } + @Input() adjustWidth: Observable<void> = {} as Observable<void>; - constructor(private helperService: HelperService, private httpService: HttpService) {} + constructor( + private helperService: HelperService, + private httpService: HttpService, + private settingsService: SettingsService + ) { + this.subscribeToSettingsServiceObservables(); + } + + ngOnDestroy() { + this.showMultipleAtATimeSubscription.unsubscribe(); + } + + subscribeToSettingsServiceObservables(): void { + this.showMultipleAtATimeSubscription = this.settingsService.showMultipleAtATimeObservable.subscribe( + (value: boolean) => { + this.showMultipleAtATime = value; + if (!this.showMultipleAtATime) { + this.removeAllReportsButOne(); + } + } + ); + } ngAfterViewInit(): void { setTimeout(() => { - this.treeReference.createComponent({ source: [], height: '90%', width: '100%', allowDrag: false }); + this.treeReference.createComponent({ + source: [], + height: '90%', + width: '100%', + allowDrag: false, + }); this.loaded = true; this.adjustWidth.subscribe(() => { this.adjustTreeWidth(); @@ -43,7 +74,7 @@ export class DebugTreeComponent implements AfterViewInit { }); } - hideOrShowCheckpointsBasedOnView(currentView: any) { + hideOrShowCheckpointsBasedOnView(currentView: any): void { this.getTreeReports().forEach((report) => { if (report.value.storageName === currentView.storageName) { this.httpService @@ -60,13 +91,13 @@ export class DebugTreeComponent implements AfterViewInit { }); } - prepareNextSelect(unmatched: string[], selectedReport: any) { + prepareNextSelect(unmatched: string[], selectedReport: any): void { if (unmatched.includes(selectedReport.value.uid)) { this.recursivelyFindParentThatWontBeDeleted(selectedReport, unmatched); } } - hideCheckpoints(unmatched: string[], children: any[]) { + hideCheckpoints(unmatched: string[], children: any[]): void { if (unmatched.length > 0) { children.forEach((node: any) => { let oki: any = this.treeReference.getItem(node); @@ -86,17 +117,20 @@ export class DebugTreeComponent implements AfterViewInit { } } - adjustTreeWidth() { + adjustTreeWidth(): void { this.treeReference.width('100%'); } - toggleShowAmount(): void { - this.showOneAtATime = !this.showOneAtATime; + removeAllReportsButOne(): void { + if (this.lastReport) { + this.addReportToTree(this.lastReport); + } } addReportToTree(report: Report): void { + this.lastReport = report; let tree = this.helperService.convertReportToJqxTree(report); - if (this.showOneAtATime) { + if (!this.showMultipleAtATime) { this.treeReference.clear(); } @@ -153,10 +187,12 @@ export class DebugTreeComponent implements AfterViewInit { this.helperService.download(queryString, this.currentView.storageName, exportBinary, exportXML); } - changeSearchTerm(event: KeyboardEvent) { + changeSearchTerm(event: KeyboardEvent): void { const term: string = (event.target as HTMLInputElement).value.toLowerCase(); this.treeReference.getItems().forEach((item: jqwidgets.TreeItem) => { - const report = item.value as unknown as Report & { message?: string | null }; + const report = item.value as unknown as Report & { + message?: string | null; + }; if (term !== '' && report) { const matching = item.label?.toLowerCase() === term || @@ -171,7 +207,7 @@ export class DebugTreeComponent implements AfterViewInit { }); } - getTreeReports() { + getTreeReports(): any[] { let reports: any[] = []; this.treeReference.getItems().forEach((item: any) => { if (item.value?.storageId != undefined) { diff --git a/src/app/debug/table/table-settings-modal/table-settings-modal.component.css b/src/app/debug/table/table-settings-modal/table-settings-modal.component.css index 787c744e..4e2eaa2f 100644 --- a/src/app/debug/table/table-settings-modal/table-settings-modal.component.css +++ b/src/app/debug/table/table-settings-modal/table-settings-modal.component.css @@ -1,3 +1,21 @@ .fa-info-circle:hover { cursor: pointer; } + +.checkbox { + display: flex; + align-items: center; + justify-content: space-between; + /*margin: 0 1rem 0 0;*/ + /*font-size: 10pt;*/ + cursor: default !important; + height: fit-content; + + > input { + margin-left: 0.25rem; + } + + > label { + margin-bottom: 0; + } +} diff --git a/src/app/debug/table/table-settings-modal/table-settings-modal.component.html b/src/app/debug/table/table-settings-modal/table-settings-modal.component.html index d3a62d9c..cf59fdaa 100644 --- a/src/app/debug/table/table-settings-modal/table-settings-modal.component.html +++ b/src/app/debug/table/table-settings-modal/table-settings-modal.component.html @@ -1,4 +1,4 @@ -<ng-template #modal let-modal > +<ng-template #modal let-modal> <div class="modal-header"> <h5 class="modal-title" id="settingsModal">Options</h5> <button id="debugSettingsModalClose" type="button" class="close" (click)="modal.dismiss()"> @@ -6,8 +6,21 @@ <h5 class="modal-title" id="settingsModal">Options</h5> </button> </div> - <form [formGroup]="settingsForm" (ngSubmit)="saveSettings()" > + <form [formGroup]="settingsForm" (ngSubmit)="saveSettings()"> + <div class="modal-body container"> + <div class="form-group"> + <h5>Debug tree</h5> + <div class="checkbox " title="When checked, file tree shows multiple files"> + <label for="show-multiple">Show multiple files in debug file-tree</label> + <input class="btn btn-info my-2 p-1 w-fit" data-cy-toggle-show-amount id="show-multiple" + [checked]="showMultipleAtATime" type="checkbox" + formControlName="showMultipleFilesAtATime" + [value]="showMultipleAtATime" + (change)="setShowMultipleAtATime()"> + </div> + </div> + <hr> <div class="form-group"> <label>Report generator</label> <select class="form-control custom-select mr-sm-2" formControlName="generatorEnabled"> @@ -15,6 +28,8 @@ <h5 class="modal-title" id="settingsModal">Options</h5> <option>Disabled</option> </select> </div> + + <div class="form-group"> <label for="openLatestReports">Open latest reports</label> <div class="input-group"> @@ -41,7 +56,7 @@ <h5 class="modal-title" id="settingsModal">Options</h5> Example 3 (only store report when name doesn't start with Hello World): ^(?!Hello World).*" - class="form-control" formControlName="regexFilter"/> + class="form-control" formControlName="regexFilter"/> </div> </div> <div class="dropdown-divider"></div> @@ -52,14 +67,18 @@ <h5 class="modal-title" id="settingsModal">Options</h5> </label> </div> <div class="form-group"> - <label>Transformation <i class="fa fa-info-circle" title="To see the change of the transformation, please reopen the current report or open a new one."></i></label> + <label>Transformation <i class="fa fa-info-circle" + title="To see the change of the transformation, please reopen the current report or open a new one."></i></label> <textarea class="form-control" rows="10" formControlName="transformation"></textarea> </div> </div> <div class="modal-footer"> - <button title="Reset and save factory settings" type="button" class="btn btn-info" (click)="factoryReset()"><i class="fa fa-refresh"></i></button> - <button title="Revert changes" type="button" class="btn btn-info" (click)="loadSettings()"><i class="fa fa-undo"></i></button> - <button title="Save changes" type="submit" id="saveTableSettings" class="btn btn-info" (click)="modal.dismiss()"><i class="fa fa-save"></i></button> + <button title="Reset and save factory settings" type="button" class="btn btn-info" (click)="factoryReset()"><i + class="fa fa-refresh"></i></button> + <button title="Revert changes" type="button" class="btn btn-info" (click)="loadSettings()"><i + class="fa fa-undo"></i></button> + <button data-cy-settings-modal-save-changes title="Save changes" type="submit" id="saveTableSettings" + class="btn btn-info" (click)="modal.dismiss()"><i class="fa fa-save"></i></button> </div> </form> </ng-template> diff --git a/src/app/debug/table/table-settings-modal/table-settings-modal.component.ts b/src/app/debug/table/table-settings-modal/table-settings-modal.component.ts index 772bb793..119af963 100644 --- a/src/app/debug/table/table-settings-modal/table-settings-modal.component.ts +++ b/src/app/debug/table/table-settings-modal/table-settings-modal.component.ts @@ -1,29 +1,57 @@ -import { Component, ElementRef, EventEmitter, Output, ViewChild } from '@angular/core'; +import { Component, ElementRef, EventEmitter, OnDestroy, Output, ViewChild } from '@angular/core'; import { UntypedFormControl, UntypedFormGroup } from '@angular/forms'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { HttpService } from '../../../shared/services/http.service'; import { CookieService } from 'ngx-cookie-service'; import { ToastComponent } from '../../../shared/components/toast/toast.component'; +import { SettingsService } from '../../../shared/services/settings.service'; +import { Subscription } from 'rxjs'; @Component({ selector: 'app-table-settings-modal', templateUrl: './table-settings-modal.component.html', styleUrls: ['./table-settings-modal.component.css'], }) -export class TableSettingsModalComponent { +export class TableSettingsModalComponent implements OnDestroy { @ViewChild('modal') modal!: ElementRef; + showMultipleAtATime!: boolean; + showMultipleAtATimeSubscription!: Subscription; settingsForm = new UntypedFormGroup({ + showMultipleFilesAtATime: new UntypedFormControl(false), generatorEnabled: new UntypedFormControl('Enabled'), regexFilter: new UntypedFormControl(''), transformationEnabled: new UntypedFormControl(true), transformation: new UntypedFormControl(''), }); - @Output() openLatestReportsEvent = new EventEmitter<any>(); @ViewChild(ToastComponent) toastComponent!: ToastComponent; saving: boolean = false; - constructor(private modalService: NgbModal, private httpService: HttpService, private cookieService: CookieService) {} + constructor( + private modalService: NgbModal, + private httpService: HttpService, + private cookieService: CookieService, + private settingsService: SettingsService + ) { + this.subscribeToSettingsServiceObservables(); + } + + ngOnDestroy() { + this.showMultipleAtATimeSubscription.unsubscribe(); + } + + subscribeToSettingsServiceObservables(): void { + this.showMultipleAtATimeSubscription = this.settingsService.showMultipleAtATimeObservable.subscribe( + (value: boolean) => { + this.showMultipleAtATime = value; + this.settingsForm.get('showMultipleFilesAtATime')?.setValue(this.showMultipleAtATime); + } + ); + } + + setShowMultipleAtATime() { + this.settingsService.setShowMultipleAtATime(!this.showMultipleAtATime); + } open(): void { this.loadSettings(); @@ -66,6 +94,8 @@ export class TableSettingsModalComponent { } factoryReset(): void { + this.settingsForm.reset(); + this.settingsService.setShowMultipleAtATime(); this.httpService.resetSettings().subscribe((response) => this.saveResponseSetting(response)); this.httpService.getTransformation(true).subscribe((resp) => { this.settingsForm.get('transformation')?.setValue(resp.transformation); diff --git a/src/app/debug/table/table.component.html b/src/app/debug/table/table.component.html index 37f264eb..5be15503 100644 --- a/src/app/debug/table/table.component.html +++ b/src/app/debug/table/table.component.html @@ -1,7 +1,7 @@ <div class="row" id="tableContent" *ngIf="tableSettings.tableLoaded"> <div class="row"> <app-button [icon]="'fa fa-refresh'" [title]="'Refresh'" (click)="refresh()"></app-button> - <app-button [icon]="'fa fa-cog'" [title]="'Settings'" (click)="openSettingsModal()"></app-button> + <app-button data-cy-open-settings-modal [icon]="'fa fa-cog'" [title]="'Settings'" (click)="openSettingsModal()"></app-button> <app-button [icon]="'fa fa-filter'" [title]="'Filter'" (click)="toggleFilter()"></app-button> <div ngbDropdown title="Download"> <button class="btn btn-info my-2 mx-1" id="dropdownDownloadTable" ngbDropdownToggle> @@ -87,6 +87,7 @@ (click)="highLightRow(i);" [ngClass]="{'highlight': selectedRow === i}" style="background-color: {{getStatusColor(row)}}" + [attr.data-cy-record-table-index]=i > <td class="table-row-checkbox"> <input type="checkbox" [checked]="row.checked" (click)="toggleCheck(row)"> diff --git a/src/app/shared/services/helper.service.ts b/src/app/shared/services/helper.service.ts index c60ac6ed..ab57fab0 100644 --- a/src/app/shared/services/helper.service.ts +++ b/src/app/shared/services/helper.service.ts @@ -141,7 +141,7 @@ export class HelperService { expanded: expanded, id: Math.random(), index: index, - items: [], + items: [] as any[], level: level, }; } diff --git a/src/app/shared/services/settings.service.spec.ts b/src/app/shared/services/settings.service.spec.ts new file mode 100644 index 00000000..359cb6b7 --- /dev/null +++ b/src/app/shared/services/settings.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { SettingsService } from './settings.service'; + +describe('SettingsService', () => { + let service: SettingsService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(SettingsService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/shared/services/settings.service.ts b/src/app/shared/services/settings.service.ts new file mode 100644 index 00000000..0249a9f1 --- /dev/null +++ b/src/app/shared/services/settings.service.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@angular/core'; +import { Observable, ReplaySubject, Subject } from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class SettingsService { + constructor() { + this.loadSettingsFromLocalStorage(); + } + + //Show multiple files in debug tree + private showMultipleAtATimeKey: string = 'showMultipleFilesAtATime'; + private showMultipleAtATime: boolean = false; + private showMultipleAtATimeSubject: Subject<boolean> = new ReplaySubject(1); + public showMultipleAtATimeObservable: Observable<boolean> = this.showMultipleAtATimeSubject.asObservable(); + + public setShowMultipleAtATime(value: boolean = false): void { + this.showMultipleAtATime = value; + this.showMultipleAtATimeSubject.next(this.showMultipleAtATime); + localStorage.setItem(this.showMultipleAtATimeKey, String(this.showMultipleAtATime)); + } + + private loadSettingsFromLocalStorage(): void { + this.setShowMultipleAtATime(localStorage.getItem(this.showMultipleAtATimeKey) === 'true'); + } +} diff --git a/src/styles.css b/src/styles.css index 1b6b3705..0f1c9e7d 100644 --- a/src/styles.css +++ b/src/styles.css @@ -134,3 +134,11 @@ html, body { .modal-backdrop { z-index: 1 !important; } + +.w-fit { + width: fit-content; +} + +.h-fit { + height: fit-content; +}