Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AAE-4427] Embed upload progress dialog inside the upload from your d… #6575

Merged
merged 10 commits into from
Feb 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Shows a dialog listing all the files uploaded with the Upload Button or Drag Are
| Name | Type | Default value | Description |
| --- | --- | --- | --- |
| position | `string` | "right" | Dialog position. Can be 'left' or 'right'. |
| alwaysVisible | `boolean` | false | Dialog visibility. When true it makes the dialog visible even when there are no uploads. |

### Events

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
adf-highlight-selector=".adf-name-location-cell-name"
[showHeader]="false"
[node]="nodePaging"
[preselectNodes]="preselectNodes"
[preselectNodes]="preselectedNodes"
[maxItems]="pageSize"
[rowFilter]="_rowFilter"
[imageResolver]="imageResolver"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
*/

import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { tick, ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { NodeEntry, Node, SiteEntry, SitePaging, NodePaging, ResultSetPaging, RequestScope } from '@alfresco/js-api';
import { SitesService, setupTestBed, NodesApiService } from '@alfresco/adf-core';
import { Node, NodeEntry, NodePaging, RequestScope, ResultSetPaging, SiteEntry, SitePaging } from '@alfresco/js-api';
import { FileModel, FileUploadStatus, NodesApiService, setupTestBed, SitesService, UploadService } from '@alfresco/adf-core';
import { of, throwError } from 'rxjs';
import { DropdownBreadcrumbComponent } from '../breadcrumb';
import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel.component';
Expand Down Expand Up @@ -65,6 +65,7 @@ describe('ContentNodeSelectorPanelComponent', () => {
const nodeEntryEvent = new NodeEntryEvent(fakeNodeEntry);
let searchQueryBuilderService: SearchQueryBuilderService;
let contentNodeSelectorPanelService: ContentNodeSelectorPanelService;
let uploadService: UploadService;

function typeToSearchBox(searchTerm = 'string-to-search') {
const searchInput = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-search-input"]'));
Expand Down Expand Up @@ -95,6 +96,7 @@ describe('ContentNodeSelectorPanelComponent', () => {
nodeService = TestBed.inject(NodesApiService);
sitesService = TestBed.inject(SitesService);
contentNodeSelectorPanelService = TestBed.inject(ContentNodeSelectorPanelService);
uploadService = TestBed.inject(UploadService);
searchQueryBuilderService = component.queryBuilderService;
component.queryBuilderService.resetToDefaults();

Expand Down Expand Up @@ -1171,6 +1173,31 @@ describe('ContentNodeSelectorPanelComponent', () => {
});
});

describe('interaction with upload functionality', () => {
let documentListService: DocumentListService;

beforeEach(() => {
documentListService = TestBed.inject(DocumentListService);

spyOn(documentListService, 'getFolderNode');
spyOn(documentListService, 'getFolder');
});

it('should remove the node from the chosenNodes when an upload gets deleted', () => {
fixture.detectChanges();
const selectSpy = spyOn(component.select, 'next');
const fakeFileModel = new FileModel(<File> { name: 'fake-name', size: 10000000 });
const fakeNodes = [<Node> { id: 'fakeNodeId' }, <Node> { id: 'fakeNodeId2' }];
fakeFileModel.data = { entry: fakeNodes[0] };
fakeFileModel.status = FileUploadStatus.Deleted;
component._chosenNode = [...fakeNodes];
uploadService.cancelUpload(fakeFileModel);

expect(selectSpy).toHaveBeenCalledWith([fakeNodes[1]]);
expect(component._chosenNode).toEqual([fakeNodes[1]]);
});
});

});

describe('Search panel', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ import {
NodesApiService,
SitesService,
UploadService,
FileUploadCompleteEvent
FileUploadCompleteEvent,
FileUploadDeleteEvent,
FileModel
} from '@alfresco/adf-core';
import { FormControl } from '@angular/forms';
import { Node, NodePaging, Pagination, SiteEntry, SitePaging, NodeEntry, QueryBody, RequestScope } from '@alfresco/js-api';
Expand Down Expand Up @@ -247,7 +249,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
searchInput: FormControl = new FormControl();

target: PaginatedComponent;
preselectNodes: NodeEntry[] = [];
preselectedNodes: NodeEntry[] = [];

searchPanelExpanded: boolean = false;

Expand Down Expand Up @@ -320,6 +322,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
this.breadcrumbTransform = this.breadcrumbTransform ? this.breadcrumbTransform : null;
this.isSelectionValid = this.isSelectionValid ? this.isSelectionValid : defaultValidation;
this.onFileUploadEvent();
this.onFileUploadDeletedEvent();
this.resetPagination();
this.setSearchScopeToNodes();

Expand Down Expand Up @@ -351,11 +354,30 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
takeUntil(this.onDestroy$)
)
.subscribe((uploadedFiles: FileUploadCompleteEvent[]) => {
this.preselectNodes = this.getPreselectNodesBasedOnSelectionMode(uploadedFiles);
this.preselectedNodes = this.getPreselectNodesBasedOnSelectionMode(uploadedFiles);
this.documentList.reload();
});
}

private onFileUploadDeletedEvent() {
this.uploadService.fileUploadDeleted
.pipe(takeUntil(this.onDestroy$))
.subscribe((deletedFileEvent: FileUploadDeleteEvent) => {
this.removeFromChosenNodes(deletedFileEvent.file);
this.documentList.reload();
});
}

private removeFromChosenNodes(file: FileModel) {
if (this.chosenNode) {
const fileIndex = this.chosenNode.findIndex((chosenNode: Node) => chosenNode.id === file.data.entry.id);
if (fileIndex !== -1) {
this._chosenNode.splice(fileIndex, 1);
this.select.next(this._chosenNode);
}
}
}

private getStartSite() {
this.nodesApiService.getNode(this.currentFolderId).subscribe((startNodeEntry) => {
this.startSiteGuid = this.sitesService.getSiteNameFromNodePath(startNodeEntry);
Expand Down Expand Up @@ -511,7 +533,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
this.showingSearchResults = false;
this.infiniteScroll = false;
this.breadcrumbFolderTitle = null;
this.preselectNodes = [];
this.preselectedNodes = [];
this.clearSearch();
this.navigationChange.emit($event);
}
Expand Down Expand Up @@ -573,7 +595,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
*/
onCurrentSelection(nodesEntries: NodeEntry[]): void {
const validNodesEntity = nodesEntries.filter((node) => this.isSelectionValid(node.entry));
this.chosenNode = validNodesEntity.map((node) => node.entry );
this.chosenNode = validNodesEntity.map((node) => node.entry);
}

setTitleIfCustomSite(site: SiteEntry) {
Expand All @@ -589,7 +611,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
}

hasPreselectNodes(): boolean {
return this.preselectNodes && this.preselectNodes.length > 0;
return this.preselectedNodes?.length > 0;
}

isSingleSelectionMode(): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ <h2>{{title}}</h2>
mat-align-tabs="start"
(selectedIndexChange)="onTabSelectionChange($event)"
[class.adf-content-node-selector-headless-tabs]="!canPerformLocalUpload()">
<mat-tab label="{{ 'NODE_SELECTOR.FILE_SERVER' | translate }}">
<mat-tab label="{{ 'NODE_SELECTOR.REPOSITORY' | translate }}">
<adf-content-node-selector-panel
[currentFolderId]="data?.currentFolderId"
[restrictRootToCurrentFolderId]="data?.restrictRootToCurrentFolderId"
Expand All @@ -35,6 +35,11 @@ <h2>{{title}}</h2>
<mat-tab *ngIf="canPerformLocalUpload()"
label="{{ 'NODE_SELECTOR.UPLOAD_FROM_DEVICE' | translate }}"
[disabled]="isNotAllowedToUpload()">
<adf-upload-drag-area [rootFolderId]="currentDirectoryId">
<div class="adf-upload-dialog-container">
<adf-file-uploading-dialog [alwaysVisible]="true"></adf-file-uploading-dialog>
</div>
</adf-upload-drag-area>
</mat-tab>
</mat-tab-group>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,22 @@
display: none;
}
}

.adf-upload-dialog {

&__content {
max-height: 64%;
}

height: 100%;
width: 100%;
position: unset;
bottom: unset;
}

.adf-upload-dialog-container {
height: 456px;
}
}

.adf-content-node-selector-dialog {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1532,7 +1532,7 @@ describe('DocumentList', () => {
spyOn(thumbnailService, 'getMimeTypeIcon').and.returnValue(`assets/images/ft_ic_created.svg`);
});

it('should able to emit nodeSelected event with preselectNodes on the reload', async () => {
it('should able to emit nodeSelected event with preselectedNodes on the reload', async () => {
const nodeSelectedSpy = spyOn(documentList.nodeSelected, 'emit');

fixture.detectChanges();
Expand All @@ -1548,7 +1548,7 @@ describe('DocumentList', () => {
expect(nodeSelectedSpy).toHaveBeenCalled();
});

it('should be able to select first node from the preselectNodes when selectionMode set to single', async () => {
it('should be able to select first node from the preselectedNodes when selectionMode set to single', async () => {
documentList.selectionMode = 'single';
fixture.detectChanges();

Expand All @@ -1560,10 +1560,10 @@ describe('DocumentList', () => {
await fixture.whenStable();

expect(documentList.preselectNodes.length).toBe(2);
expect(documentList.getPreselectNodesBasedOnSelectionMode().length).toBe(1);
expect(documentList.getPreselectedNodesBasedOnSelectionMode().length).toBe(1);
});

it('should be able to select all preselectNodes when selectionMode set to multiple', async () => {
it('should be able to select all preselectedNodes when selectionMode set to multiple', async () => {
documentList.selectionMode = 'multiple';
fixture.detectChanges();

Expand All @@ -1575,13 +1575,14 @@ describe('DocumentList', () => {
await fixture.whenStable();

expect(documentList.preselectNodes.length).toBe(2);
expect(documentList.getPreselectNodesBasedOnSelectionMode().length).toBe(2);
expect(documentList.getPreselectedNodesBasedOnSelectionMode().length).toBe(2);
});

it('should call the datatable select row method for each preselected node', async () => {
const datatableSelectRowSpy = spyOn(documentList.dataTable, 'selectRow');
const fakeDatatableRows = [new ShareDataRow(mockPreselectedNodes[0], contentService, null), new ShareDataRow(mockPreselectedNodes[1], contentService, null)];
spyOn(documentList.data, 'getPreselectRows').and.returnValue(fakeDatatableRows);
spyOn(documentList.data, 'hasPreselectedRows').and.returnValue(true);
spyOn(documentList.data, 'getPreselectedRows').and.returnValue(fakeDatatableRows);

documentList.selectionMode = 'multiple';
documentList.preselectNodes = mockPreselectedNodes;
Expand All @@ -1593,7 +1594,7 @@ describe('DocumentList', () => {
expect(datatableSelectRowSpy.calls.count()).toEqual(fakeDatatableRows.length);
});

it('should not emit nodeSelected event when preselectNodes is undefined/empty', async () => {
it('should not emit nodeSelected event when preselectedNodes is undefined/empty', async () => {
const nodeSelectedSpy = spyOn(documentList.nodeSelected, 'emit');

fixture.detectChanges();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
if (this.data) {
if (changes.node && changes.node.currentValue) {
const merge = this._pagination ? this._pagination.merge : false;
this.data.loadPage(changes.node.currentValue, merge, null, this.getPreselectNodesBasedOnSelectionMode());
this.data.loadPage(changes.node.currentValue, merge, null, this.getPreselectedNodesBasedOnSelectionMode());
this.onPreselectNodes();
this.onDataReady(changes.node.currentValue);
} else if (changes.imageResolver) {
Expand All @@ -501,7 +501,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
this.resetSelection();
if (this.node) {
if (this.data) {
this.data.loadPage(this.node, this._pagination.merge, null, this.getPreselectNodesBasedOnSelectionMode());
this.data.loadPage(this.node, this._pagination.merge, null, this.getPreselectedNodesBasedOnSelectionMode());
}
this.onPreselectNodes();
this.syncPagination();
Expand Down Expand Up @@ -694,7 +694,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
onPageLoaded(nodePaging: NodePaging) {
if (nodePaging) {
if (this.data) {
this.data.loadPage(nodePaging, this._pagination.merge, this.allowDropFiles, this.getPreselectNodesBasedOnSelectionMode());
this.data.loadPage(nodePaging, this._pagination.merge, this.allowDropFiles, this.getPreselectedNodesBasedOnSelectionMode());
}
this.onPreselectNodes();
this.setLoadingState(false);
Expand Down Expand Up @@ -800,7 +800,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
}

onNodeSelect(event: { row: ShareDataRow, selection: Array<ShareDataRow> }) {
this.selection = event.selection.map((entry) => entry.node);
this.selection = event.selection.filter(entry => entry.node).map((entry) => entry.node);
const domEvent = new CustomEvent('node-select', {
detail: {
node: event.row ? event.row.node : null,
Expand Down Expand Up @@ -919,13 +919,13 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
this.error.emit(err);
}

getPreselectNodesBasedOnSelectionMode(): NodeEntry[] {
return this.hasPreselectNodes() ? (this.isSingleSelectionMode() ? [this.preselectNodes[0]] : this.preselectNodes) : [];
getPreselectedNodesBasedOnSelectionMode(): NodeEntry[] {
return this.hasPreselectedNodes() ? (this.isSingleSelectionMode() ? [this.preselectNodes[0]] : this.preselectNodes) : [];
}

onPreselectNodes() {
if (this.hasPreselectNodes()) {
const preselectedNodes = [...this.isSingleSelectionMode() ? [this.data.getPreselectRows()[0]] : this.data.getPreselectRows()];
if (this.data.hasPreselectedRows()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is changing the behaviour...compared to the previous code, are we sure ?

Copy link
Contributor Author

@arditdomi arditdomi Feb 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest I believe this is the right thing to do because in the next lines we access the this.data.getPreselectedRows()[0] or this.data.getPreselectedRows() based on if the selection is single or multiple as you can see in this code https://github.com/Alfresco/alfresco-ng2-components/blob/develop/lib/content-services/src/lib/document-list/components/document-list.component.ts#L928.
So to me seems correct we should check the if(this.data.hasPreselectedRows) as this is the one we are trying to access in the next line. I don't know what the author of the code wanted to achieve with this but I believe this is the correct thing.

If we keep the if (this.hasPreselectedRows()) instead of the if(this.data.hasPreselectedRows()) we could end up with an error when accessing the elements of the this.data.getPreselectedRows() as it can be that there are no elements there.

const preselectedNodes = [...this.isSingleSelectionMode() ? [this.data.getPreselectedRows()[0]] : this.data.getPreselectedRows()];
const selectedNodes = [...this.selection, ...preselectedNodes];

for (const node of preselectedNodes) {
Expand All @@ -939,7 +939,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
return this.selectionMode === 'single';
}

hasPreselectNodes(): boolean {
return this.preselectNodes && this.preselectNodes.length > 0;
hasPreselectedNodes(): boolean {
return this.preselectNodes?.length > 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/

import { DataColumn, DataRow, DataSorting, ContentService, ThumbnailService, setupTestBed } from '@alfresco/adf-core';
import { FileNode, FolderNode, SmartFolderNode, RuleFolderNode, LinkFolderNode, mockPreselectedNodes, mockNodePagingWithPreselectedNodes, mockNode2, fakeNodePaging } from './../../mock';
import { FileNode, FolderNode, SmartFolderNode, RuleFolderNode, LinkFolderNode, mockPreselectedNodes, mockNodePagingWithPreselectedNodes, mockNode2, fakeNodePaging, mockNode1 } from './../../mock';
import { ShareDataRow } from './share-data-row.model';
import { ShareDataTableAdapter } from './share-datatable-adapter';
import { ContentTestingModule } from '../../testing/content.testing.module';
Expand Down Expand Up @@ -484,28 +484,38 @@ describe('ShareDataTableAdapter', () => {

describe('Preselect rows', () => {

it('should set isSelected to be true for each preselectRow if the preselectNodes are defined', () => {
it('should set isSelected to be true for each preselectRow if the preselectedNodes are defined', () => {
const adapter = new ShareDataTableAdapter(thumbnailService, contentService, []);
adapter.loadPage(mockNodePagingWithPreselectedNodes, null, null, mockPreselectedNodes);

expect(adapter.getPreselectRows().length).toBe(1);
expect(adapter.getPreselectRows()[0].isSelected).toBe(true);
expect(adapter.getPreselectedRows().length).toBe(1);
expect(adapter.getPreselectedRows()[0].isSelected).toBe(true);
});

it('should set preselectRows empty if preselectedNodes are undefined/empty', () => {
it('should set preselectedRows empty if preselectedNodes are undefined/empty', () => {
const adapter = new ShareDataTableAdapter(thumbnailService, contentService, []);
adapter.loadPage(mockNodePagingWithPreselectedNodes, null, null, []);

expect(adapter.getPreselectRows().length).toBe(0);
expect(adapter.getPreselectedRows().length).toBe(0);
});

it('should set preselectRows empty if preselectedNodes are not found in the list', () => {
it('should set preselectedRows empty if preselectedNodes are not found in the list', () => {
const adapter = new ShareDataTableAdapter(thumbnailService, contentService, []);
mockNode2.id = 'mock-file-id';
const preselectedNode = [ { entry: mockNode2 }];
adapter.loadPage(fakeNodePaging, null, null, preselectedNode);

expect(adapter.getPreselectRows().length).toBe(0);
expect(adapter.getPreselectedRows().length).toBe(0);
});

it('should preselected rows contain only the valid rows that exist in the datatable', () => {
const adapter = new ShareDataTableAdapter(thumbnailService, contentService, []);
const nonExistingEntry = {...mockNode1};
nonExistingEntry.id = 'non-existing-entry-id';
const preselectedNodes = [{ entry: nonExistingEntry }, { entry: mockNode1 }, { entry: mockNode2 }];
adapter.loadPage(mockNodePagingWithPreselectedNodes, null, null, preselectedNodes);

expect(adapter.getPreselectedRows().length).toBe(2);
});
});
});