From e5ea37cd62fea684f5cb2ad23d9fdac3875aa2b9 Mon Sep 17 00:00:00 2001 From: "swapnil.verma" Date: Mon, 16 Sep 2024 11:40:04 +0530 Subject: [PATCH 01/10] [MNT-24575] Created folder information dialog --- .../aca-content/assets/app.extensions.json | 12 ++++ projects/aca-content/assets/i18n/en.json | 15 ++++- .../folder-information.component.html | 38 ++++++++++++ .../folder-information.component.scss | 45 ++++++++++++++ .../folder-information.component.spec.ts | 45 ++++++++++++++ .../folder-information.component.ts | 62 +++++++++++++++++++ .../services/content-management.service.ts | 18 +++++- .../src/lib/store/effects/node.effects.ts | 25 +++++++- .../store/src/actions/node.actions.ts | 7 +++ 9 files changed, 263 insertions(+), 4 deletions(-) create mode 100644 projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.html create mode 100644 projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.scss create mode 100644 projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts create mode 100644 projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts diff --git a/projects/aca-content/assets/app.extensions.json b/projects/aca-content/assets/app.extensions.json index 288a453032..3cd29abf3e 100644 --- a/projects/aca-content/assets/app.extensions.json +++ b/projects/aca-content/assets/app.extensions.json @@ -877,6 +877,18 @@ "visible": "app.selection.canDelete" } }, + { + "id": "app.context.menu.folder-info", + "title": "APP.ACTIONS.FOLDER_INFO", + "order": 1200, + "icon": "info", + "actions": { + "click": "FOLDER_INFORMATION" + }, + "rules": { + "visible": "app.selection.folder" + } + }, { "id": "app.create.separator.3", "type": "separator", diff --git a/projects/aca-content/assets/i18n/en.json b/projects/aca-content/assets/i18n/en.json index 51d5e22cc4..0d69e0a51d 100644 --- a/projects/aca-content/assets/i18n/en.json +++ b/projects/aca-content/assets/i18n/en.json @@ -299,7 +299,8 @@ "EDIT_OFFLINE": "Edit Offline", "EDIT_OFFLINE_CANCEL": "Cancel Editing", "CHANGE_ASPECT": "Edit Aspects", - "ADD_ASPECTS": "Add Aspects" + "ADD_ASPECTS": "Add Aspects", + "FOLDER_INFO": "Folder Information" }, "DIALOGS": { "CONFIRM_PURGE": { @@ -451,7 +452,17 @@ "OPTIONS_SETTINGS": "Options and settings", "MY_PROFILE": "My profile", "EXPAND_NAVIGATION": "Expand navigation menu" - } + }, + "FOLDER_INFO": { + "ICON": "Folder Icon", + "TITLE": "Folder Information", + "SIZE" : "Size", + "CALCULATING": "Calculating...", + "LOCATION": "Location", + "CREATED": "Created", + "MODIFIED": "Modified", + "DONE": "Done" + } }, "NODE_SELECTOR": { "COPY_ITEM": "Copy '{{ name }}' to...", diff --git a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.html b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.html new file mode 100644 index 0000000000..6818d341c5 --- /dev/null +++ b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.html @@ -0,0 +1,38 @@ +
+
+
+ {{ 'APP.FOLDER_INFO.ICON' | translate }} +
+
+ {{ name }} +
+
+ +
+
+
{{ 'APP.FOLDER_INFO.SIZE' | translate }}
+
{{ size }}
+
+ +
+
{{ 'APP.FOLDER_INFO.LOCATION' | translate }}
+
{{ location }}
+
+ +
+
{{ 'APP.FOLDER_INFO.CREATED' | translate }}
+
+ {{ created | adfTimeAgo }} +
+
+ +
+
{{ 'APP.FOLDER_INFO.MODIFIED' | translate }}
+
+ {{ modified | adfTimeAgo }} +
+
+
+
diff --git a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.scss b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.scss new file mode 100644 index 0000000000..faab957cfd --- /dev/null +++ b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.scss @@ -0,0 +1,45 @@ +.app-folder-info { + .aca-folder-info { + &-container { + display: flex; + flex-direction: column; + border: 1px solid var(--theme-border-color); + border-radius: 12px; + } + + &-header { + display: flex; + flex-direction: row; + align-items: center; + column-gap: 10px; + padding: 20px; + + .aca-folder-title { + flex: 1; + font-weight: bold; + } + } + + &-body { + display: flex; + flex-direction: column; + flex: 1; + padding: 10px 20px; + + .aca-folder-info-item { + display: flex; + flex-direction: row; + padding: 20px 0; + + &-label { + width: 30%; + color: var(--theme-text-color); + } + + &-value { + width: 70%; + } + } + } + } +} diff --git a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts new file mode 100644 index 0000000000..ba35a767f2 --- /dev/null +++ b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts @@ -0,0 +1,45 @@ +/*! + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Alfresco Example Content Application + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * from Hyland Software. If not, see . + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FolderInformationComponent } from './folder-information.component'; + +describe('FolderInformationComponent', () => { + let component: FolderInformationComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FolderInformationComponent] + }).compileComponents(); + + fixture = TestBed.createComponent(FolderInformationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts new file mode 100644 index 0000000000..c158f9cf7a --- /dev/null +++ b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts @@ -0,0 +1,62 @@ +/*! + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Alfresco Example Content Application + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * from Hyland Software. If not, see . + */ + +import { Component, inject, OnInit, ViewEncapsulation } from '@angular/core'; +import { CommonModule, NgIf } from '@angular/common'; +import { DIALOG_COMPONENT_DATA, LocalizedDatePipe, TimeAgoPipe } from '@alfresco/adf-core'; +import { Node } from '@alfresco/js-api'; +import { MatDividerModule } from '@angular/material/divider'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { ContentService } from '@alfresco/adf-content-services'; + +@Component({ + selector: 'app-folder-info', + standalone: true, + imports: [CommonModule, MatDividerModule, TimeAgoPipe, NgIf, TranslateModule, LocalizedDatePipe, LocalizedDatePipe], + templateUrl: './folder-information.component.html', + styleUrls: ['./folder-information.component.scss'], + encapsulation: ViewEncapsulation.None, + host: { class: 'app-folder-info' } +}) +export class FolderInformationComponent implements OnInit { + readonly contentService = inject(ContentService); + readonly translateService = inject(TranslateService); + + data: Node = inject(DIALOG_COMPONENT_DATA); + name: string; + size: string; + location: string; + created: Date; + modified: Date; + icon: string; + + ngOnInit() { + this.name = this.data.name; + this.size = this.translateService.instant('APP.FOLDER_INFO.CALCULATING'); + this.location = this.data.path.name; + this.created = this.data.createdAt; + this.modified = this.data.modifiedAt; + this.icon = this.contentService.getNodeIcon(this.data); + } +} diff --git a/projects/aca-content/src/lib/services/content-management.service.ts b/projects/aca-content/src/lib/services/content-management.service.ts index 1d4142d50d..10b6cd8f89 100644 --- a/projects/aca-content/src/lib/services/content-management.service.ts +++ b/projects/aca-content/src/lib/services/content-management.service.ts @@ -54,7 +54,7 @@ import { NodesApiService, ShareDialogComponent } from '@alfresco/adf-content-services'; -import { NotificationService, TranslationService, ConfirmDialogComponent } from '@alfresco/adf-core'; +import { NotificationService, TranslationService, ConfirmDialogComponent, DialogComponent, DialogSize } from '@alfresco/adf-core'; import { DeletedNodesPaging, Node, NodeEntry, PathInfo, SiteBodyCreate, SiteEntry } from '@alfresco/js-api'; import { inject, Injectable } from '@angular/core'; import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; @@ -63,6 +63,7 @@ import { forkJoin, Observable, of, zip } from 'rxjs'; import { catchError, map, mergeMap, take, tap } from 'rxjs/operators'; import { NodeActionsService } from './node-actions.service'; import { Router } from '@angular/router'; +import { FolderInformationComponent } from '../dialogs/folder-details/folder-information.component'; interface RestoredNode { status: number; @@ -1089,4 +1090,19 @@ export class ContentManagementService { document.querySelector(focusedElementSelector)?.focus(); } } + + showFolderInformation(node: NodeEntry) { + this.dialogRef.open(DialogComponent, { + data: { + title: 'APP.FOLDER_INFO.TITLE', + confirmButtonTitle: 'APP.FOLDER_INFO.DONE', + isCancelButtonHidden: true, + isCloseButtonHidden: false, + dialogSize: DialogSize.Large, + contentComponent: FolderInformationComponent, + componentData: node.entry + }, + width: '700px' + }); + } } diff --git a/projects/aca-content/src/lib/store/effects/node.effects.ts b/projects/aca-content/src/lib/store/effects/node.effects.ts index a2378beae9..c699cddaec 100644 --- a/projects/aca-content/src/lib/store/effects/node.effects.ts +++ b/projects/aca-content/src/lib/store/effects/node.effects.ts @@ -51,7 +51,8 @@ import { ShowLoaderAction, UndoDeleteNodesAction, UnlockWriteAction, - UnshareNodesAction + UnshareNodesAction, + FolderInformationAction } from '@alfresco/aca-shared/store'; import { ContentManagementService } from '../../services/content-management.service'; import { RenditionService } from '@alfresco/adf-content-services'; @@ -460,4 +461,26 @@ export class NodeEffects { ), { dispatch: false } ); + + folderInformation$ = createEffect( + () => + this.actions$.pipe( + ofType(NodeActionTypes.FolderInformation), + map((action) => { + if (action?.payload) { + this.contentService.showFolderInformation(action.payload); + } else { + this.store + .select(getAppSelection) + .pipe(take(1)) + .subscribe((selection) => { + if (selection && !selection.isEmpty && selection.folder.entry) { + this.contentService.showFolderInformation(selection.folder); + } + }); + } + }) + ), + { dispatch: false } + ); } diff --git a/projects/aca-shared/store/src/actions/node.actions.ts b/projects/aca-shared/store/src/actions/node.actions.ts index ca39694761..da1174103d 100644 --- a/projects/aca-shared/store/src/actions/node.actions.ts +++ b/projects/aca-shared/store/src/actions/node.actions.ts @@ -39,6 +39,7 @@ export enum NodeActionTypes { Unshare = 'UNSHARE_NODES', Copy = 'COPY_NODES', Move = 'MOVE_NODES', + FolderInformation = 'FOLDER_INFORMATION', ManagePermissions = 'MANAGE_PERMISSIONS', PrintFile = 'PRINT_FILE', ManageVersions = 'MANAGE_VERSIONS', @@ -180,3 +181,9 @@ export class ManageRulesAction implements Action { constructor(public payload: NodeEntry) {} } + +export class FolderInformationAction implements Action { + readonly type = NodeActionTypes.FolderInformation; + + constructor(public payload: NodeEntry) {} +} From 4b99eed99b67e26df3e63827895e18ea4de28812 Mon Sep 17 00:00:00 2001 From: "swapnil.verma" Date: Mon, 16 Sep 2024 14:39:44 +0530 Subject: [PATCH 02/10] [MNT-24575] Added unit tests --- .../folder-information.component.html | 20 +++++----- .../folder-information.component.spec.ts | 39 +++++++++++++++---- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.html b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.html index 6818d341c5..b84ecc6333 100644 --- a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.html +++ b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.html @@ -3,36 +3,34 @@
{{ 'APP.FOLDER_INFO.ICON' | translate }}
-
- {{ name }} -
+
{{ name }}
{{ 'APP.FOLDER_INFO.SIZE' | translate }}
-
{{ size }}
+
{{ size }}
{{ 'APP.FOLDER_INFO.LOCATION' | translate }}
-
{{ location }}
+
{{ location }}
{{ 'APP.FOLDER_INFO.CREATED' | translate }}
- {{ created | adfTimeAgo }} -
+ data-automation-id="folder-info-creation-date" + [title]="created | adfLocalizedDate">{{ created | adfTimeAgo }}
{{ 'APP.FOLDER_INFO.MODIFIED' | translate }}
- {{ modified | adfTimeAgo }} -
+ data-automation-id="folder-info-modify-date" + [title]="modified | adfLocalizedDate">{{ modified | adfTimeAgo }}
diff --git a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts index ba35a767f2..89f695235f 100644 --- a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts +++ b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts @@ -24,22 +24,45 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FolderInformationComponent } from './folder-information.component'; +import { DIALOG_COMPONENT_DATA, RedirectAuthService } from '@alfresco/adf-core'; +import { ContentService } from '@alfresco/adf-content-services'; +import { By } from '@angular/platform-browser'; +import { EMPTY } from 'rxjs'; +import { LibTestingModule } from '@alfresco/aca-shared'; describe('FolderInformationComponent', () => { - let component: FolderInformationComponent; let fixture: ComponentFixture; - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [FolderInformationComponent] - }).compileComponents(); + const dialogData = { + name: 'mock-folder', + path: { + name: 'mock-folder-path' + }, + createdAt: new Date(2024, 1, 1, 11, 11), + modifiedAt: new Date(2024, 2, 2, 22, 22) + }; + + const getValueFromElement = (id: string) => fixture.debugElement.query(By.css(`[data-automation-id="${id}"]`)).nativeElement.textContent; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [FolderInformationComponent, LibTestingModule], + providers: [ + { provide: DIALOG_COMPONENT_DATA, useValue: dialogData }, + { provide: RedirectAuthService, useValue: { onLogin: EMPTY, onTokenReceived: EMPTY } } + ] + }); fixture = TestBed.createComponent(FolderInformationComponent); - component = fixture.componentInstance; + spyOn(TestBed.inject(ContentService), 'getNodeIcon').and.returnValue('./assets/images/ft_ic_folder.svg'); fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); + it('should render all information in init', () => { + expect(getValueFromElement('folder-info-name')).toBe('mock-folder'); + expect(getValueFromElement('folder-info-size')).toBe('APP.FOLDER_INFO.CALCULATING'); + expect(getValueFromElement('folder-info-location')).toBe('mock-folder-path'); + expect(getValueFromElement('folder-info-creation-date')).toBe('01/02/2024 11:11'); + expect(getValueFromElement('folder-info-modify-date')).toBe('02/03/2024 22:22'); }); }); From b3a0cf59fb314c3dd3add8993c7f86a96742c41e Mon Sep 17 00:00:00 2001 From: "swapnil.verma" Date: Tue, 3 Dec 2024 17:03:46 +0530 Subject: [PATCH 03/10] [MNT-24575] Integrated API calls into folder information --- projects/aca-content/assets/i18n/en.json | 2 + .../folder-information.component.spec.ts | 29 +++++++++-- .../folder-information.component.ts | 48 +++++++++++++++++-- 3 files changed, 71 insertions(+), 8 deletions(-) diff --git a/projects/aca-content/assets/i18n/en.json b/projects/aca-content/assets/i18n/en.json index 0d69e0a51d..9d71c194b7 100644 --- a/projects/aca-content/assets/i18n/en.json +++ b/projects/aca-content/assets/i18n/en.json @@ -458,6 +458,8 @@ "TITLE": "Folder Information", "SIZE" : "Size", "CALCULATING": "Calculating...", + "CALCULATED_SIZE_LARGE": "{{sizeInBytes}} bytes ({{sizeInLargeUnit}} {{unit}} on disk) for {{count}} files", + "CALCULATED_SIZE_NORMAL": "{{sizeInBytes}} bytes for {{count}} files", "LOCATION": "Location", "CREATED": "Created", "MODIFIED": "Modified", diff --git a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts index 89f695235f..b1406a3ae7 100644 --- a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts +++ b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts @@ -25,16 +25,21 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FolderInformationComponent } from './folder-information.component'; import { DIALOG_COMPONENT_DATA, RedirectAuthService } from '@alfresco/adf-core'; -import { ContentService } from '@alfresco/adf-content-services'; +import { ContentService, NodesApiService } from '@alfresco/adf-content-services'; import { By } from '@angular/platform-browser'; -import { EMPTY } from 'rxjs'; +import { EMPTY, Subject } from 'rxjs'; import { LibTestingModule } from '@alfresco/aca-shared'; describe('FolderInformationComponent', () => { let fixture: ComponentFixture; + let nodeService: NodesApiService; + let initiateFolderSizeCalculationSpy: jasmine.Spy; + let getFolderSizeInfoSpy: jasmine.Spy; + const mockSub = new Subject<{ entry: { jobId: string } }>(); const dialogData = { name: 'mock-folder', + id: 'mock-folder-id', path: { name: 'mock-folder-path' }, @@ -43,7 +48,6 @@ describe('FolderInformationComponent', () => { }; const getValueFromElement = (id: string) => fixture.debugElement.query(By.css(`[data-automation-id="${id}"]`)).nativeElement.textContent; - beforeEach(() => { TestBed.configureTestingModule({ imports: [FolderInformationComponent, LibTestingModule], @@ -52,17 +56,32 @@ describe('FolderInformationComponent', () => { { provide: RedirectAuthService, useValue: { onLogin: EMPTY, onTokenReceived: EMPTY } } ] }); - fixture = TestBed.createComponent(FolderInformationComponent); + nodeService = TestBed.inject(NodesApiService); spyOn(TestBed.inject(ContentService), 'getNodeIcon').and.returnValue('./assets/images/ft_ic_folder.svg'); - fixture.detectChanges(); + initiateFolderSizeCalculationSpy = spyOn(nodeService, 'initiateFolderSizeCalculation').and.returnValue(mockSub.asObservable()); + getFolderSizeInfoSpy = spyOn(nodeService, 'getFolderSizeInfo').and.returnValue(EMPTY); }); it('should render all information in init', () => { + fixture.detectChanges(); expect(getValueFromElement('folder-info-name')).toBe('mock-folder'); expect(getValueFromElement('folder-info-size')).toBe('APP.FOLDER_INFO.CALCULATING'); expect(getValueFromElement('folder-info-location')).toBe('mock-folder-path'); expect(getValueFromElement('folder-info-creation-date')).toBe('01/02/2024 11:11'); expect(getValueFromElement('folder-info-modify-date')).toBe('02/03/2024 22:22'); }); + + it('should make API call on init to start folder size calculation', () => { + fixture.detectChanges(); + expect(initiateFolderSizeCalculationSpy).toHaveBeenCalledWith('mock-folder-id'); + }); + + it('should fetch folder size only when the initial folder size calculation request is completed', () => { + fixture.detectChanges(); + expect(initiateFolderSizeCalculationSpy).toHaveBeenCalledWith('mock-folder-id'); + expect(getFolderSizeInfoSpy).not.toHaveBeenCalled(); + mockSub.next({ entry: { jobId: 'mock-job-id' } }); + expect(getFolderSizeInfoSpy).toHaveBeenCalled(); + }); }); diff --git a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts index c158f9cf7a..c5f8f7d932 100644 --- a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts +++ b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts @@ -22,13 +22,18 @@ * from Hyland Software. If not, see . */ -import { Component, inject, OnInit, ViewEncapsulation } from '@angular/core'; +import { Component, DestroyRef, inject, OnInit, ViewEncapsulation } from '@angular/core'; import { CommonModule, NgIf } from '@angular/common'; import { DIALOG_COMPONENT_DATA, LocalizedDatePipe, TimeAgoPipe } from '@alfresco/adf-core'; import { Node } from '@alfresco/js-api'; import { MatDividerModule } from '@angular/material/divider'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; -import { ContentService } from '@alfresco/adf-content-services'; +import { ContentService, NodesApiService } from '@alfresco/adf-content-services'; +import { expand, first } from 'rxjs/operators'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { EMPTY } from 'rxjs'; + +const MEMORY_UNIT_LIST = ['bytes', 'KB', 'MB', 'GB', 'TB']; @Component({ selector: 'app-folder-info', @@ -41,8 +46,11 @@ import { ContentService } from '@alfresco/adf-content-services'; }) export class FolderInformationComponent implements OnInit { readonly contentService = inject(ContentService); + readonly nodesService = inject(NodesApiService); readonly translateService = inject(TranslateService); + private readonly destroyRef = inject(DestroyRef); + data: Node = inject(DIALOG_COMPONENT_DATA); name: string; size: string; @@ -53,10 +61,44 @@ export class FolderInformationComponent implements OnInit { ngOnInit() { this.name = this.data.name; - this.size = this.translateService.instant('APP.FOLDER_INFO.CALCULATING'); this.location = this.data.path.name; this.created = this.data.createdAt; this.modified = this.data.modifiedAt; this.icon = this.contentService.getNodeIcon(this.data); + this.size = this.translateService.instant('APP.FOLDER_INFO.CALCULATING'); + + this.nodesService + .initiateFolderSizeCalculation(this.data.id) + .pipe(first()) + .subscribe((jobIdEntry: { entry: { jobId: string } }) => { + this.nodesService + .getFolderSizeInfo(this.data.id, jobIdEntry.entry.jobId) + .pipe( + expand((result: any) => + result.entry.status !== 'COMPLETED' ? this.nodesService.getFolderSizeInfo(this.data.id, jobIdEntry.entry.jobId) : EMPTY + ), + takeUntilDestroyed(this.destroyRef) + ) + .subscribe((folderInfo) => { + let size = parseFloat(folderInfo.entry.sizeInBytes); + let unitIndex = 0; + let isMoreThanBytes = false; + while (size > 1000) { + isMoreThanBytes = true; + size = size / 1000; + unitIndex++; + } + const params = { + sizeInBytes: parseFloat(folderInfo.entry.sizeInBytes).toLocaleString('en'), + sizeInLargeUnit: size.toFixed(2), + unit: MEMORY_UNIT_LIST[unitIndex], + count: folderInfo.entry.numberOfFiles + }; + this.size = this.translateService.instant( + isMoreThanBytes ? 'APP.FOLDER_INFO.CALCULATED_SIZE_LARGE' : 'APP.FOLDER_INFO.CALCULATED_SIZE_NORMAL', + params + ); + }); + }); } } From 206384cbbeb9672450aed85fb99e2fc952d1262e Mon Sep 17 00:00:00 2001 From: "swapnil.verma" Date: Thu, 5 Dec 2024 12:26:23 +0530 Subject: [PATCH 04/10] [MNT-24575] Added models --- .../dialogs/folder-details/folder-information.component.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts index c5f8f7d932..cc665313fa 100644 --- a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts +++ b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts @@ -25,7 +25,7 @@ import { Component, DestroyRef, inject, OnInit, ViewEncapsulation } from '@angular/core'; import { CommonModule, NgIf } from '@angular/common'; import { DIALOG_COMPONENT_DATA, LocalizedDatePipe, TimeAgoPipe } from '@alfresco/adf-core'; -import { Node } from '@alfresco/js-api'; +import { JobIdBodyEntry, Node, SizeDetailsEntry } from '@alfresco/js-api'; import { MatDividerModule } from '@angular/material/divider'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { ContentService, NodesApiService } from '@alfresco/adf-content-services'; @@ -70,7 +70,7 @@ export class FolderInformationComponent implements OnInit { this.nodesService .initiateFolderSizeCalculation(this.data.id) .pipe(first()) - .subscribe((jobIdEntry: { entry: { jobId: string } }) => { + .subscribe((jobIdEntry: JobIdBodyEntry) => { this.nodesService .getFolderSizeInfo(this.data.id, jobIdEntry.entry.jobId) .pipe( @@ -79,7 +79,7 @@ export class FolderInformationComponent implements OnInit { ), takeUntilDestroyed(this.destroyRef) ) - .subscribe((folderInfo) => { + .subscribe((folderInfo: SizeDetailsEntry) => { let size = parseFloat(folderInfo.entry.sizeInBytes); let unitIndex = 0; let isMoreThanBytes = false; From ef304ebcd16b4fb1bf2a33cdace0cdef6d7d1603 Mon Sep 17 00:00:00 2001 From: "swapnil.verma" Date: Thu, 5 Dec 2024 13:04:52 +0530 Subject: [PATCH 05/10] [MNT-24575] Added delay to API retry call. Added unit test for API retry functionality --- .../folder-information.component.spec.ts | 21 +++++++++++++++++-- .../folder-information.component.ts | 8 ++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts index b1406a3ae7..b0bb23b904 100644 --- a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts +++ b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts @@ -22,12 +22,12 @@ * from Hyland Software. If not, see . */ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { FolderInformationComponent } from './folder-information.component'; import { DIALOG_COMPONENT_DATA, RedirectAuthService } from '@alfresco/adf-core'; import { ContentService, NodesApiService } from '@alfresco/adf-content-services'; import { By } from '@angular/platform-browser'; -import { EMPTY, Subject } from 'rxjs'; +import { EMPTY, of, Subject } from 'rxjs'; import { LibTestingModule } from '@alfresco/aca-shared'; describe('FolderInformationComponent', () => { @@ -84,4 +84,21 @@ describe('FolderInformationComponent', () => { mockSub.next({ entry: { jobId: 'mock-job-id' } }); expect(getFolderSizeInfoSpy).toHaveBeenCalled(); }); + + it('should make repeated calls to get folder size info, if the response returned from the API is IN_PROGRESS', fakeAsync(() => { + getFolderSizeInfoSpy.and.returnValue(of({ entry: { status: 'IN_PROGRESS' } })); + fixture.detectChanges(); + expect(getFolderSizeInfoSpy).not.toHaveBeenCalled(); + mockSub.next({ entry: { jobId: 'mock-job-id' } }); + expect(getFolderSizeInfoSpy).toHaveBeenCalledTimes(1); + tick(5000); + expect(getFolderSizeInfoSpy).toHaveBeenCalledTimes(2); + tick(5000); + expect(getFolderSizeInfoSpy).toHaveBeenCalledTimes(3); + getFolderSizeInfoSpy.and.returnValue(of({ entry: { status: 'COMPLETE' } })); + tick(5000); + expect(getFolderSizeInfoSpy).toHaveBeenCalledTimes(4); + tick(5000); + expect(getFolderSizeInfoSpy).not.toHaveBeenCalledTimes(5); + })); }); diff --git a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts index cc665313fa..7550beb2eb 100644 --- a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts +++ b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts @@ -29,9 +29,9 @@ import { JobIdBodyEntry, Node, SizeDetailsEntry } from '@alfresco/js-api'; import { MatDividerModule } from '@angular/material/divider'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { ContentService, NodesApiService } from '@alfresco/adf-content-services'; -import { expand, first } from 'rxjs/operators'; +import { concatMap, expand, first } from 'rxjs/operators'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { EMPTY } from 'rxjs'; +import { EMPTY, timer } from 'rxjs'; const MEMORY_UNIT_LIST = ['bytes', 'KB', 'MB', 'GB', 'TB']; @@ -75,7 +75,9 @@ export class FolderInformationComponent implements OnInit { .getFolderSizeInfo(this.data.id, jobIdEntry.entry.jobId) .pipe( expand((result: any) => - result.entry.status !== 'COMPLETED' ? this.nodesService.getFolderSizeInfo(this.data.id, jobIdEntry.entry.jobId) : EMPTY + result.entry.status === 'IN_PROGRESS' + ? timer(5000).pipe(concatMap(() => this.nodesService.getFolderSizeInfo(this.data.id, jobIdEntry.entry.jobId))) + : EMPTY ), takeUntilDestroyed(this.destroyRef) ) From 6dd14b4c020363a106d2ed2e0da11fa71c8056b5 Mon Sep 17 00:00:00 2001 From: "swapnil.verma" Date: Thu, 5 Dec 2024 17:12:59 +0530 Subject: [PATCH 06/10] [MNT-24575] Folder information is no longer shown in trashcan --- .../aca-content/assets/app.extensions.json | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/projects/aca-content/assets/app.extensions.json b/projects/aca-content/assets/app.extensions.json index 3cd29abf3e..5544044dad 100644 --- a/projects/aca-content/assets/app.extensions.json +++ b/projects/aca-content/assets/app.extensions.json @@ -632,6 +632,20 @@ "visible": "app.selection.canDelete" } }, + { + "id": "app.context.menu.folder-info", + "title": "APP.ACTIONS.FOLDER_INFO", + "order": 800, + "icon": "info", + "actions": { + "click": "FOLDER_INFORMATION" + }, + "rules": { + "visible": [ + "app.selection.folder", + "!app.navigation.isTrashcan", + } + }, { "id": "app.create.separator.3", "type": "separator", @@ -886,7 +900,10 @@ "click": "FOLDER_INFORMATION" }, "rules": { - "visible": "app.selection.folder" + "visible": [ + "app.selection.folder", + "!app.navigation.isTrashcan" + ] } }, { From 8877fada9798e99bf2176bf2cfac1663b997bf4d Mon Sep 17 00:00:00 2001 From: "swapnil.verma" Date: Thu, 5 Dec 2024 17:27:09 +0530 Subject: [PATCH 07/10] [MNT-24575] Folder information is no longer shown in trashcan --- projects/aca-content/assets/app.extensions.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/projects/aca-content/assets/app.extensions.json b/projects/aca-content/assets/app.extensions.json index 5544044dad..670e8f3df9 100644 --- a/projects/aca-content/assets/app.extensions.json +++ b/projects/aca-content/assets/app.extensions.json @@ -643,7 +643,8 @@ "rules": { "visible": [ "app.selection.folder", - "!app.navigation.isTrashcan", + "!app.navigation.isTrashcan" + ] } }, { From 40d3eee6dbc12cd660db0d255ccdabae42c14095 Mon Sep 17 00:00:00 2001 From: "swapnil.verma" Date: Fri, 6 Dec 2024 16:50:06 +0530 Subject: [PATCH 08/10] [MNT-24575] Added return type to function. Consolidated different properties into a single type --- .../folder-information.component.html | 20 ++++++------ .../folder-information.component.spec.ts | 2 +- .../folder-information.component.ts | 32 +++++++++++-------- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.html b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.html index b84ecc6333..5387fa3512 100644 --- a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.html +++ b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.html @@ -1,36 +1,36 @@
- {{ 'APP.FOLDER_INFO.ICON' | translate }} + {{ 'APP.FOLDER_INFO.ICON' | translate }}
-
{{ name }}
+
{{ folderDetails.name }}
- +
{{ 'APP.FOLDER_INFO.SIZE' | translate }}
{{ size }}
+ data-automation-id="folder-info-size">{{ folderDetails.size }}
- +
{{ 'APP.FOLDER_INFO.LOCATION' | translate }}
{{ location }}
+ data-automation-id="folder-info-location">{{ folderDetails.location }}
- +
{{ 'APP.FOLDER_INFO.CREATED' | translate }}
{{ created | adfTimeAgo }}
+ [title]="folderDetails.created | adfLocalizedDate">{{ folderDetails.created | adfTimeAgo }}
- +
{{ 'APP.FOLDER_INFO.MODIFIED' | translate }}
{{ modified | adfTimeAgo }}
+ [title]="folderDetails.modified | adfLocalizedDate">{{ folderDetails.modified | adfTimeAgo }}
diff --git a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts index b0bb23b904..448dfcf09f 100644 --- a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts +++ b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts @@ -47,7 +47,7 @@ describe('FolderInformationComponent', () => { modifiedAt: new Date(2024, 2, 2, 22, 22) }; - const getValueFromElement = (id: string) => fixture.debugElement.query(By.css(`[data-automation-id="${id}"]`)).nativeElement.textContent; + const getValueFromElement = (id: string): string => fixture.debugElement.query(By.css(`[data-automation-id="${id}"]`)).nativeElement.textContent; beforeEach(() => { TestBed.configureTestingModule({ imports: [FolderInformationComponent, LibTestingModule], diff --git a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts index 7550beb2eb..1a65e73d2d 100644 --- a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts +++ b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts @@ -35,6 +35,15 @@ import { EMPTY, timer } from 'rxjs'; const MEMORY_UNIT_LIST = ['bytes', 'KB', 'MB', 'GB', 'TB']; +interface FolderDetails { + name: string; + size: string; + location: string; + created: Date; + modified: Date; + icon: string; +} + @Component({ selector: 'app-folder-info', standalone: true, @@ -52,20 +61,15 @@ export class FolderInformationComponent implements OnInit { private readonly destroyRef = inject(DestroyRef); data: Node = inject(DIALOG_COMPONENT_DATA); - name: string; - size: string; - location: string; - created: Date; - modified: Date; - icon: string; + folderDetails: FolderDetails; ngOnInit() { - this.name = this.data.name; - this.location = this.data.path.name; - this.created = this.data.createdAt; - this.modified = this.data.modifiedAt; - this.icon = this.contentService.getNodeIcon(this.data); - this.size = this.translateService.instant('APP.FOLDER_INFO.CALCULATING'); + this.folderDetails.name = this.data.name; + this.folderDetails.location = this.data.path.name; + this.folderDetails.created = this.data.createdAt; + this.folderDetails.modified = this.data.modifiedAt; + this.folderDetails.icon = this.contentService.getNodeIcon(this.data); + this.folderDetails.size = this.translateService.instant('APP.FOLDER_INFO.CALCULATING'); this.nodesService .initiateFolderSizeCalculation(this.data.id) @@ -74,7 +78,7 @@ export class FolderInformationComponent implements OnInit { this.nodesService .getFolderSizeInfo(this.data.id, jobIdEntry.entry.jobId) .pipe( - expand((result: any) => + expand((result: SizeDetailsEntry) => result.entry.status === 'IN_PROGRESS' ? timer(5000).pipe(concatMap(() => this.nodesService.getFolderSizeInfo(this.data.id, jobIdEntry.entry.jobId))) : EMPTY @@ -96,7 +100,7 @@ export class FolderInformationComponent implements OnInit { unit: MEMORY_UNIT_LIST[unitIndex], count: folderInfo.entry.numberOfFiles }; - this.size = this.translateService.instant( + this.folderDetails.size = this.translateService.instant( isMoreThanBytes ? 'APP.FOLDER_INFO.CALCULATED_SIZE_LARGE' : 'APP.FOLDER_INFO.CALCULATED_SIZE_NORMAL', params ); From f26b34fa966f9d42a29400cf2ef4f2d2daa3ebe0 Mon Sep 17 00:00:00 2001 From: "swapnil.verma" Date: Wed, 18 Dec 2024 11:41:39 +0530 Subject: [PATCH 09/10] [MNT-24575] Addressed Code review comments --- .../folder-information.component.html | 60 ++++++++-------- .../folder-information.component.scss | 12 ++-- .../folder-information.component.spec.ts | 25 +++++-- .../folder-information.component.ts | 68 +++++++++---------- .../content-management.service.spec.ts | 39 ++++++++++- .../lib/store/effects/node.effects.spec.ts | 38 +++++++++++ 6 files changed, 161 insertions(+), 81 deletions(-) diff --git a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.html b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.html index 5387fa3512..08fdbe364c 100644 --- a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.html +++ b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.html @@ -1,36 +1,32 @@ -
-
-
- {{ 'APP.FOLDER_INFO.ICON' | translate }} -
-
{{ folderDetails.name }}
+
+ {{ 'APP.FOLDER_INFO.ICON' | translate }} +
{{ folderDetails.name }}
+
+ +
+
+
{{ 'APP.FOLDER_INFO.SIZE' | translate }}
+
{{ folderDetails.size }}
+
+ +
+
{{ 'APP.FOLDER_INFO.LOCATION' | translate }}
+
{{ folderDetails.location }}
+
+ +
+
{{ 'APP.FOLDER_INFO.CREATED' | translate }}
+
{{ folderDetails.created | adfTimeAgo }}
-
-
-
{{ 'APP.FOLDER_INFO.SIZE' | translate }}
-
{{ folderDetails.size }}
-
- -
-
{{ 'APP.FOLDER_INFO.LOCATION' | translate }}
-
{{ folderDetails.location }}
-
- -
-
{{ 'APP.FOLDER_INFO.CREATED' | translate }}
-
{{ folderDetails.created | adfTimeAgo }}
-
- -
-
{{ 'APP.FOLDER_INFO.MODIFIED' | translate }}
-
{{ folderDetails.modified | adfTimeAgo }}
-
+
+
{{ 'APP.FOLDER_INFO.MODIFIED' | translate }}
+
{{ folderDetails.modified | adfTimeAgo }}
diff --git a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.scss b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.scss index faab957cfd..b2173e9dcd 100644 --- a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.scss +++ b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.scss @@ -1,12 +1,10 @@ .app-folder-info { - .aca-folder-info { - &-container { - display: flex; - flex-direction: column; - border: 1px solid var(--theme-border-color); - border-radius: 12px; - } + display: flex; + flex-direction: column; + border: 1px solid var(--theme-border-color); + border-radius: 12px; + .aca-folder-info { &-header { display: flex; flex-direction: row; diff --git a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts index 448dfcf09f..00a5a3c60b 100644 --- a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts +++ b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.spec.ts @@ -27,16 +27,17 @@ import { FolderInformationComponent } from './folder-information.component'; import { DIALOG_COMPONENT_DATA, RedirectAuthService } from '@alfresco/adf-core'; import { ContentService, NodesApiService } from '@alfresco/adf-content-services'; import { By } from '@angular/platform-browser'; -import { EMPTY, of, Subject } from 'rxjs'; +import { EMPTY, Observable, of, Subject } from 'rxjs'; import { LibTestingModule } from '@alfresco/aca-shared'; +import { JobIdBodyEntry, SizeDetails, SizeDetailsEntry, Node } from '@alfresco/js-api'; describe('FolderInformationComponent', () => { let fixture: ComponentFixture; let nodeService: NodesApiService; - let initiateFolderSizeCalculationSpy: jasmine.Spy; - let getFolderSizeInfoSpy: jasmine.Spy; - const mockSub = new Subject<{ entry: { jobId: string } }>(); + let initiateFolderSizeCalculationSpy: jasmine.Spy<(nodeId: string) => Observable>; + let getFolderSizeInfoSpy: jasmine.Spy<(nodeId: string, jobId: string) => Observable>; + const mockSub = new Subject(); const dialogData = { name: 'mock-folder', id: 'mock-folder-id', @@ -45,9 +46,20 @@ describe('FolderInformationComponent', () => { }, createdAt: new Date(2024, 1, 1, 11, 11), modifiedAt: new Date(2024, 2, 2, 22, 22) + } as Node; + const mockSizeDetailsEntry: SizeDetailsEntry = { + entry: { + id: 'mock-id', + sizeInBytes: '1', + calculatedAt: 'mock-date', + numberOfFiles: 1, + status: SizeDetails.StatusEnum.COMPLETE, + jobId: 'mock-job-id' + } }; const getValueFromElement = (id: string): string => fixture.debugElement.query(By.css(`[data-automation-id="${id}"]`)).nativeElement.textContent; + beforeEach(() => { TestBed.configureTestingModule({ imports: [FolderInformationComponent, LibTestingModule], @@ -86,7 +98,8 @@ describe('FolderInformationComponent', () => { }); it('should make repeated calls to get folder size info, if the response returned from the API is IN_PROGRESS', fakeAsync(() => { - getFolderSizeInfoSpy.and.returnValue(of({ entry: { status: 'IN_PROGRESS' } })); + mockSizeDetailsEntry.entry.status = SizeDetails.StatusEnum.IN_PROGRESS; + getFolderSizeInfoSpy.and.returnValue(of(mockSizeDetailsEntry)); fixture.detectChanges(); expect(getFolderSizeInfoSpy).not.toHaveBeenCalled(); mockSub.next({ entry: { jobId: 'mock-job-id' } }); @@ -95,7 +108,7 @@ describe('FolderInformationComponent', () => { expect(getFolderSizeInfoSpy).toHaveBeenCalledTimes(2); tick(5000); expect(getFolderSizeInfoSpy).toHaveBeenCalledTimes(3); - getFolderSizeInfoSpy.and.returnValue(of({ entry: { status: 'COMPLETE' } })); + mockSizeDetailsEntry.entry.status = SizeDetails.StatusEnum.COMPLETE; tick(5000); expect(getFolderSizeInfoSpy).toHaveBeenCalledTimes(4); tick(5000); diff --git a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts index 1a65e73d2d..65dc223065 100644 --- a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts +++ b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts @@ -23,19 +23,19 @@ */ import { Component, DestroyRef, inject, OnInit, ViewEncapsulation } from '@angular/core'; -import { CommonModule, NgIf } from '@angular/common'; +import { CommonModule, NgOptimizedImage } from '@angular/common'; import { DIALOG_COMPONENT_DATA, LocalizedDatePipe, TimeAgoPipe } from '@alfresco/adf-core'; -import { JobIdBodyEntry, Node, SizeDetailsEntry } from '@alfresco/js-api'; +import { JobIdBodyEntry, Node, SizeDetails } from '@alfresco/js-api'; import { MatDividerModule } from '@angular/material/divider'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { ContentService, NodesApiService } from '@alfresco/adf-content-services'; -import { concatMap, expand, first } from 'rxjs/operators'; +import { concatMap, expand, first, switchMap } from 'rxjs/operators'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { EMPTY, timer } from 'rxjs'; const MEMORY_UNIT_LIST = ['bytes', 'KB', 'MB', 'GB', 'TB']; -interface FolderDetails { +class FolderDetails { name: string; size: string; location: string; @@ -47,7 +47,7 @@ interface FolderDetails { @Component({ selector: 'app-folder-info', standalone: true, - imports: [CommonModule, MatDividerModule, TimeAgoPipe, NgIf, TranslateModule, LocalizedDatePipe, LocalizedDatePipe], + imports: [CommonModule, MatDividerModule, TimeAgoPipe, TranslateModule, LocalizedDatePipe, NgOptimizedImage], templateUrl: './folder-information.component.html', styleUrls: ['./folder-information.component.scss'], encapsulation: ViewEncapsulation.None, @@ -61,7 +61,7 @@ export class FolderInformationComponent implements OnInit { private readonly destroyRef = inject(DestroyRef); data: Node = inject(DIALOG_COMPONENT_DATA); - folderDetails: FolderDetails; + folderDetails: FolderDetails = new FolderDetails(); ngOnInit() { this.folderDetails.name = this.data.name; @@ -73,38 +73,38 @@ export class FolderInformationComponent implements OnInit { this.nodesService .initiateFolderSizeCalculation(this.data.id) - .pipe(first()) - .subscribe((jobIdEntry: JobIdBodyEntry) => { - this.nodesService - .getFolderSizeInfo(this.data.id, jobIdEntry.entry.jobId) - .pipe( - expand((result: SizeDetailsEntry) => - result.entry.status === 'IN_PROGRESS' + .pipe( + first(), + switchMap((jobIdEntry: JobIdBodyEntry) => { + return this.nodesService.getFolderSizeInfo(this.data.id, jobIdEntry.entry.jobId).pipe( + expand((result) => + result.entry.status === SizeDetails.StatusEnum.IN_PROGRESS ? timer(5000).pipe(concatMap(() => this.nodesService.getFolderSizeInfo(this.data.id, jobIdEntry.entry.jobId))) : EMPTY ), takeUntilDestroyed(this.destroyRef) - ) - .subscribe((folderInfo: SizeDetailsEntry) => { - let size = parseFloat(folderInfo.entry.sizeInBytes); - let unitIndex = 0; - let isMoreThanBytes = false; - while (size > 1000) { - isMoreThanBytes = true; - size = size / 1000; - unitIndex++; - } - const params = { - sizeInBytes: parseFloat(folderInfo.entry.sizeInBytes).toLocaleString('en'), - sizeInLargeUnit: size.toFixed(2), - unit: MEMORY_UNIT_LIST[unitIndex], - count: folderInfo.entry.numberOfFiles - }; - this.folderDetails.size = this.translateService.instant( - isMoreThanBytes ? 'APP.FOLDER_INFO.CALCULATED_SIZE_LARGE' : 'APP.FOLDER_INFO.CALCULATED_SIZE_NORMAL', - params - ); - }); + ); + }) + ) + .subscribe((folderInfo) => { + let size = parseFloat(folderInfo.entry.sizeInBytes); + let unitIndex = 0; + let isMoreThanBytes = false; + while (size > 1000) { + isMoreThanBytes = true; + size = size / 1000; + unitIndex++; + } + const params = { + sizeInBytes: parseFloat(folderInfo.entry.sizeInBytes).toLocaleString('en'), + sizeInLargeUnit: size.toFixed(2), + unit: MEMORY_UNIT_LIST[unitIndex], + count: folderInfo.entry.numberOfFiles + }; + this.folderDetails.size = this.translateService.instant( + isMoreThanBytes ? 'APP.FOLDER_INFO.CALCULATED_SIZE_LARGE' : 'APP.FOLDER_INFO.CALCULATED_SIZE_NORMAL', + params + ); }); } } diff --git a/projects/aca-content/src/lib/services/content-management.service.spec.ts b/projects/aca-content/src/lib/services/content-management.service.spec.ts index c0e6f0a88d..6f29e7127a 100644 --- a/projects/aca-content/src/lib/services/content-management.service.spec.ts +++ b/projects/aca-content/src/lib/services/content-management.service.spec.ts @@ -53,10 +53,10 @@ import { AppHookService, AppSettingsService, ContentApiService } from '@alfresco import { Store } from '@ngrx/store'; import { ContentManagementService } from './content-management.service'; import { NodeActionsService } from './node-actions.service'; -import { NotificationService, TranslationService } from '@alfresco/adf-core'; +import { DialogComponent, DialogSize, NotificationService, TranslationService } from '@alfresco/adf-core'; import { MatDialog, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { MatSnackBarModule, MatSnackBarRef, SimpleSnackBar } from '@angular/material/snack-bar'; -import { Node, NodeEntry, VersionPaging } from '@alfresco/js-api'; +import { Node, NodeEntry, UserInfo, VersionPaging } from '@alfresco/js-api'; import { DocumentListService, FileModel, @@ -67,6 +67,7 @@ import { NodesApiService, ViewVersion } from '@alfresco/adf-content-services'; +import { FolderInformationComponent } from '../dialogs/folder-details/folder-information.component'; describe('ContentManagementService', () => { let dialog: MatDialog; @@ -1847,4 +1848,38 @@ describe('ContentManagementService', () => { expect(store.dispatch).toHaveBeenCalledWith(new NavigateRouteAction(['/libraries'])); })); }); + + describe('folderInformationDialog', () => { + it('should open folder information dialog', () => { + spyOn(dialog, 'open'); + + const fakeNode: NodeEntry = { + entry: { + id: 'folder-node-id', + name: 'mock-folder-name', + nodeType: 'fake-node-type', + isFolder: true, + isFile: false, + modifiedAt: new Date(), + modifiedByUser: new UserInfo(), + createdAt: new Date(), + createdByUser: new UserInfo() + } + }; + + contentManagementService.showFolderInformation(fakeNode); + expect(dialog.open).toHaveBeenCalledWith(DialogComponent, { + data: { + title: 'APP.FOLDER_INFO.TITLE', + confirmButtonTitle: 'APP.FOLDER_INFO.DONE', + isCancelButtonHidden: true, + isCloseButtonHidden: false, + dialogSize: DialogSize.Large, + contentComponent: FolderInformationComponent, + componentData: fakeNode.entry + }, + width: '700px' + }); + }); + }); }); diff --git a/projects/aca-content/src/lib/store/effects/node.effects.spec.ts b/projects/aca-content/src/lib/store/effects/node.effects.spec.ts index e2b8d38f75..e6d5696b37 100644 --- a/projects/aca-content/src/lib/store/effects/node.effects.spec.ts +++ b/projects/aca-content/src/lib/store/effects/node.effects.spec.ts @@ -34,6 +34,7 @@ import { DeleteNodesAction, EditFolderAction, ExpandInfoDrawerAction, + FolderInformationAction, FullscreenViewerAction, ManageAspectsAction, ManagePermissionsAction, @@ -59,6 +60,7 @@ import { NavigationEnd, Router, ActivatedRoute } from '@angular/router'; import { of } from 'rxjs'; import { MatDialogModule } from '@angular/material/dialog'; import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { NodeEntry, UserInfo } from '@alfresco/js-api'; describe('NodeEffects', () => { let store: Store; @@ -564,4 +566,40 @@ describe('NodeEffects', () => { expect(store.dispatch).toHaveBeenCalledWith(new NavigateUrlAction('personal-files/details/node-id?location=test-page')); }); }); + + describe('folderInformation$', () => { + it('should call folder information dialog', () => { + const node: any = { entry: { isFile: true } }; + spyOn(contentService, 'showFolderInformation').and.stub(); + + store.dispatch(new FolderInformationAction(node)); + + expect(contentService.showFolderInformation).toHaveBeenCalled(); + }); + + it('should call folder information dialog from the active folder selection', fakeAsync(() => { + spyOn(contentService, 'showFolderInformation').and.stub(); + + const node: NodeEntry = { + entry: { + id: 'folder-node-id', + name: 'mock-folder-name', + nodeType: 'fake-node-type', + isFolder: true, + isFile: false, + modifiedAt: new Date(), + modifiedByUser: new UserInfo(), + createdAt: new Date(), + createdByUser: new UserInfo() + } + }; + store.dispatch(new SetSelectedNodesAction([node])); + + tick(100); + + store.dispatch(new FolderInformationAction(null)); + + expect(contentService.showFolderInformation).toHaveBeenCalledWith(node); + })); + }); }); From bb7981b7037f3abb992bc05231e0614811c2533a Mon Sep 17 00:00:00 2001 From: "swapnil.verma" Date: Fri, 20 Dec 2024 14:33:38 +0530 Subject: [PATCH 10/10] [MNT-24575] Addressed code review findings. Added error handling and unit tests --- projects/aca-content/assets/i18n/en.json | 3 +- .../folder-information.component.ts | 58 +++++++++++-------- .../lib/store/effects/node.effects.spec.ts | 16 ++++- 3 files changed, 51 insertions(+), 26 deletions(-) diff --git a/projects/aca-content/assets/i18n/en.json b/projects/aca-content/assets/i18n/en.json index 9d71c194b7..da1707f58c 100644 --- a/projects/aca-content/assets/i18n/en.json +++ b/projects/aca-content/assets/i18n/en.json @@ -463,7 +463,8 @@ "LOCATION": "Location", "CREATED": "Created", "MODIFIED": "Modified", - "DONE": "Done" + "DONE": "Done", + "ERROR": "Something went wrong, please close this dialog and try again" } }, "NODE_SELECTOR": { diff --git a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts index 65dc223065..5ce8c0bdd9 100644 --- a/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts +++ b/projects/aca-content/src/lib/dialogs/folder-details/folder-information.component.ts @@ -25,13 +25,13 @@ import { Component, DestroyRef, inject, OnInit, ViewEncapsulation } from '@angular/core'; import { CommonModule, NgOptimizedImage } from '@angular/common'; import { DIALOG_COMPONENT_DATA, LocalizedDatePipe, TimeAgoPipe } from '@alfresco/adf-core'; -import { JobIdBodyEntry, Node, SizeDetails } from '@alfresco/js-api'; +import { Node, SizeDetails } from '@alfresco/js-api'; import { MatDividerModule } from '@angular/material/divider'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { ContentService, NodesApiService } from '@alfresco/adf-content-services'; -import { concatMap, expand, first, switchMap } from 'rxjs/operators'; +import { catchError, concatMap, expand, first, switchMap } from 'rxjs/operators'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { EMPTY, timer } from 'rxjs'; +import { EMPTY, of, timer } from 'rxjs'; const MEMORY_UNIT_LIST = ['bytes', 'KB', 'MB', 'GB', 'TB']; @@ -61,7 +61,7 @@ export class FolderInformationComponent implements OnInit { private readonly destroyRef = inject(DestroyRef); data: Node = inject(DIALOG_COMPONENT_DATA); - folderDetails: FolderDetails = new FolderDetails(); + folderDetails = new FolderDetails(); ngOnInit() { this.folderDetails.name = this.data.name; @@ -75,36 +75,48 @@ export class FolderInformationComponent implements OnInit { .initiateFolderSizeCalculation(this.data.id) .pipe( first(), - switchMap((jobIdEntry: JobIdBodyEntry) => { + switchMap((jobIdEntry) => { return this.nodesService.getFolderSizeInfo(this.data.id, jobIdEntry.entry.jobId).pipe( expand((result) => result.entry.status === SizeDetails.StatusEnum.IN_PROGRESS ? timer(5000).pipe(concatMap(() => this.nodesService.getFolderSizeInfo(this.data.id, jobIdEntry.entry.jobId))) : EMPTY ), - takeUntilDestroyed(this.destroyRef) + catchError(() => { + this.folderDetails.size = this.translateService.instant('APP.FOLDER_INFO.ERROR'); + return of(null); + }) ); + }), + takeUntilDestroyed(this.destroyRef), + catchError(() => { + this.folderDetails.size = this.translateService.instant('APP.FOLDER_INFO.ERROR'); + return of(null); }) ) .subscribe((folderInfo) => { - let size = parseFloat(folderInfo.entry.sizeInBytes); - let unitIndex = 0; - let isMoreThanBytes = false; - while (size > 1000) { - isMoreThanBytes = true; - size = size / 1000; - unitIndex++; + if (folderInfo?.entry?.status === SizeDetails.StatusEnum.COMPLETE) { + let size = parseFloat(folderInfo.entry.sizeInBytes); + let unitIndex = 0; + let isMoreThanBytes = false; + while (size > 1000) { + isMoreThanBytes = true; + size = size / 1000; + unitIndex++; + } + const params = { + sizeInBytes: parseFloat(folderInfo.entry.sizeInBytes).toLocaleString('en'), + sizeInLargeUnit: size.toFixed(2), + unit: MEMORY_UNIT_LIST[unitIndex], + count: folderInfo.entry.numberOfFiles + }; + this.folderDetails.size = this.translateService.instant( + isMoreThanBytes ? 'APP.FOLDER_INFO.CALCULATED_SIZE_LARGE' : 'APP.FOLDER_INFO.CALCULATED_SIZE_NORMAL', + params + ); + } else { + this.folderDetails.size = this.translateService.instant('APP.FOLDER_INFO.ERROR'); } - const params = { - sizeInBytes: parseFloat(folderInfo.entry.sizeInBytes).toLocaleString('en'), - sizeInLargeUnit: size.toFixed(2), - unit: MEMORY_UNIT_LIST[unitIndex], - count: folderInfo.entry.numberOfFiles - }; - this.folderDetails.size = this.translateService.instant( - isMoreThanBytes ? 'APP.FOLDER_INFO.CALCULATED_SIZE_LARGE' : 'APP.FOLDER_INFO.CALCULATED_SIZE_NORMAL', - params - ); }); } } diff --git a/projects/aca-content/src/lib/store/effects/node.effects.spec.ts b/projects/aca-content/src/lib/store/effects/node.effects.spec.ts index e6d5696b37..2d4f88aac1 100644 --- a/projects/aca-content/src/lib/store/effects/node.effects.spec.ts +++ b/projects/aca-content/src/lib/store/effects/node.effects.spec.ts @@ -569,12 +569,24 @@ describe('NodeEffects', () => { describe('folderInformation$', () => { it('should call folder information dialog', () => { - const node: any = { entry: { isFile: true } }; + const node: NodeEntry = { + entry: { + id: 'folder-node-id', + name: 'mock-folder-name', + nodeType: 'fake-node-type', + isFolder: true, + isFile: false, + modifiedAt: new Date(), + modifiedByUser: new UserInfo(), + createdAt: new Date(), + createdByUser: new UserInfo() + } + }; spyOn(contentService, 'showFolderInformation').and.stub(); store.dispatch(new FolderInformationAction(node)); - expect(contentService.showFolderInformation).toHaveBeenCalled(); + expect(contentService.showFolderInformation).toHaveBeenCalledWith(node); }); it('should call folder information dialog from the active folder selection', fakeAsync(() => {