diff --git a/README.md b/README.md index 239d705fe0..4b78c7b978 100644 --- a/README.md +++ b/README.md @@ -65,5 +65,5 @@ All components are supported in the following browsers: * Due to a [known issue](https://bugzilla.mozilla.org/show_bug.cgi?id=1188880) in Firefox, the Alfresco Upload Component does not currently support folder upload functionality on Firefox. -See the [Browser Support](BROWSER-SUPPORT.md) article for more details. +See the [Browser Support](BROWSER-SUPPORT.md) article for more details. diff --git a/docs/content-services/components/content-node-selector-panel.component.md b/docs/content-services/components/content-node-selector-panel.component.md index cdfcfc587c..f97affb21c 100644 --- a/docs/content-services/components/content-node-selector-panel.component.md +++ b/docs/content-services/components/content-node-selector-panel.component.md @@ -49,6 +49,7 @@ Opens a [Content Node Selector](content-node-selector.component.md) in its own | Name | Type | Description | | ---- | ---- | ----------- | | select | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`Node`](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/Node.md)`[]>` | Emitted when the user has chosen an item. | +| siteChange | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`` | Emitted when the select site changes. | ## Details diff --git a/docs/process-services/components/process-list.component.md b/docs/process-services/components/process-list.component.md index f77d5c8e2a..364d9722ab 100644 --- a/docs/process-services/components/process-list.component.md +++ b/docs/process-services/components/process-list.component.md @@ -193,7 +193,7 @@ The Process Instance List also supports pagination: Emitted before the context menu is displayed for a row. -Note that the [ProcessInstanceListComponent](../../../lib/process-services/src/lib/process-list/components/process-list.component.ts) itself does not populate the context menu with items. You can provide all necessary content via the handler. +Note that the [`ProcessInstanceListComponent`](../../../lib/process-services/src/lib/process-list/components/process-list.component.ts) itself does not populate the context menu with items. You can provide all necessary content via the handler. ```html diff --git a/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.spec.ts b/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.spec.ts index 10d39031c4..ed46599992 100644 --- a/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.spec.ts +++ b/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.spec.ts @@ -19,7 +19,7 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { async, fakeAsync, tick, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { NodeEntry, Node, SiteEntry, SitePaging, NodePaging } from '@alfresco/js-api'; -import { SearchService, SitesService, setupTestBed } from '@alfresco/adf-core'; +import { SearchService, SitesService, setupTestBed, NodesApiService } from '@alfresco/adf-core'; import { Observable, Observer, of, throwError } from 'rxjs'; import { DropdownBreadcrumbComponent } from '../breadcrumb'; import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel.component'; @@ -27,6 +27,7 @@ import { ContentNodeSelectorService } from './content-node-selector.service'; import { ContentTestingModule } from '../testing/content.testing.module'; import { DocumentListService } from '../document-list/services/document-list.service'; import { DocumentListComponent } from '../document-list/components/document-list.component'; +import { DropdownSitesComponent } from '../site-dropdown/sites-dropdown.component'; import { CustomResourcesService } from '../document-list/services/custom-resources.service'; import { ShareDataRow } from '../document-list'; @@ -53,6 +54,8 @@ describe('ContentNodeSelectorComponent', () => { let fixture: ComponentFixture; let contentNodeSelectorService: ContentNodeSelectorService; let searchService: SearchService; + let nodeService: NodesApiService; + let sitesService: SitesService; let searchSpy: jasmine.Spy; let cnSearchSpy: jasmine.Spy; @@ -82,13 +85,19 @@ describe('ContentNodeSelectorComponent', () => { component.debounceSearch = 0; searchService = TestBed.get(SearchService); + nodeService = TestBed.get(NodesApiService); contentNodeSelectorService = TestBed.get(ContentNodeSelectorService); + sitesService = TestBed.get(SitesService); + + spyOn(nodeService, 'getNode').and.returnValue(of({ id: 'fake-node', path: { elements: [{ nodeType: 'st:site', name: 'fake-site'}] } })); cnSearchSpy = spyOn(contentNodeSelectorService, 'search').and.callThrough(); searchSpy = spyOn(searchService, 'searchByQueryBody').and.callFake(() => { return new Observable((observer: Observer) => { _observer = observer; }); }); + const fakeSite = new SiteEntry({ entry: { id: 'fake-site', guid: 'fake-site', title: 'fake-site', visibility: 'visible' } }); + spyOn(sitesService, 'getSite').and.returnValue(of(fakeSite)); }); afterEach(() => { @@ -98,11 +107,9 @@ describe('ContentNodeSelectorComponent', () => { describe('Parameters', () => { let documentListService: DocumentListService; - let sitesService: SitesService; beforeEach(() => { documentListService = TestBed.get(DocumentListService); - sitesService = TestBed.get(SitesService); spyOn(documentListService, 'getFolderNode').and.returnValue(of( { entry: { path: { elements: [] } } })); spyOn(documentListService, 'getFolder').and.returnValue(throwError('No results for test')); @@ -205,11 +212,9 @@ describe('ContentNodeSelectorComponent', () => { describe('Breadcrumbs', () => { let documentListService: DocumentListService; - let sitesService: SitesService; beforeEach(() => { documentListService = TestBed.get(DocumentListService); - sitesService = TestBed.get(SitesService); spyOn(documentListService, 'getFolderNode').and.returnValue(of( { entry: { path: { elements: [] } } })); spyOn(documentListService, 'getFolder').and.returnValue(throwError('No results for test')); @@ -350,6 +355,40 @@ describe('ContentNodeSelectorComponent', () => { }); }); + describe('Site selection', () => { + + beforeEach(() => { + spyOn(sitesService, 'getSites').and.returnValue(of({ list: { entries: [] } })); + component.currentFolderId = 'fake-starting-folder'; + }); + + it('should trigger siteChange event on init with parent site Title of start folder', (done) => { + component.siteChange.subscribe((siteTitle: string) => { + expect(siteTitle).toBe('fake-site'); + done(); + }); + + component.ngOnInit(); + fixture.detectChanges(); + expect(component.startSiteGuid).toBe('fake-site'); + }); + + it('should trigger siteChange event when a site is selected in sites-dropdown', (done) => { + const fakeSiteEntry = new SiteEntry({ entry: { title: 'fake-new-site', guid: 'fake-new-site' } }); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + component.siteChange.subscribe((siteTitle: string) => { + expect(siteTitle).toBe('fake-new-site'); + done(); + }); + + const sitesDropdown = fixture.debugElement.query(By.directive(DropdownSitesComponent)); + sitesDropdown.componentInstance.selectedSite({value: fakeSiteEntry}); + }); + }); + }); + describe('Search functionality', () => { let getCorrespondingNodeIdsSpy; @@ -392,7 +431,6 @@ describe('ContentNodeSelectorComponent', () => { } })); - const sitesService = TestBed.get(SitesService); spyOn(sitesService, 'getSites').and.returnValue(of({ list: { entries: [] } })); const customResourcesService = TestBed.get(CustomResourcesService); @@ -883,7 +921,6 @@ describe('ContentNodeSelectorComponent', () => { } beforeEach(() => { - const sitesService = TestBed.get(SitesService); spyOn(sitesService, 'getSites').and.returnValue(of({ list: { entries: [] } })); }); diff --git a/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.ts b/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.ts index 50ab214fc3..0c2e95a060 100644 --- a/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.ts +++ b/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.component.ts @@ -21,7 +21,9 @@ import { UserPreferencesService, PaginationModel, UserPreferenceValues, - InfinitePaginationComponent, PaginatedComponent + InfinitePaginationComponent, PaginatedComponent, + NodesApiService, + SitesService } from '@alfresco/adf-core'; import { FormControl } from '@angular/forms'; import { Node, NodePaging, Pagination, SiteEntry, SitePaging } from '@alfresco/js-api'; @@ -176,6 +178,10 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy { @Output() select: EventEmitter = new EventEmitter(); + /** Emitted when the select site changes. */ + @Output() + siteChange: EventEmitter = new EventEmitter(); + @ViewChild('documentList') documentList: DocumentListComponent; @@ -191,6 +197,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy { _chosenNode: Node = null; folderIdToShow: string | null = null; breadcrumbFolderTitle: string | null = null; + startSiteGuid: string | null = null; pagination: PaginationModel = this.DEFAULT_PAGINATION; @@ -207,7 +214,9 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy { constructor(private contentNodeSelectorService: ContentNodeSelectorService, private customResourcesService: CustomResourcesService, - private userPreferencesService: UserPreferencesService) { + private userPreferencesService: UserPreferencesService, + private nodesApiService: NodesApiService, + private sitesService: SitesService) { } set chosenNode(value: Node) { @@ -238,6 +247,9 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy { this.target = this.documentList; this.folderIdToShow = this.currentFolderId; + if (this.currentFolderId) { + this.getStartSite(); + } this.breadcrumbTransform = this.breadcrumbTransform ? this.breadcrumbTransform : null; this.isSelectionValid = this.isSelectionValid ? this.isSelectionValid : defaultValidation; @@ -248,6 +260,19 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy { this.onDestroy$.complete(); } + private getStartSite() { + this.nodesApiService.getNode(this.currentFolderId).subscribe((startNodeEntry) => { + this.startSiteGuid = this.sitesService.getSiteNameFromNodePath(startNodeEntry); + if (this.startSiteGuid) { + this.sitesService.getSite(this.startSiteGuid).subscribe((startSiteEntry) => { + if (startSiteEntry instanceof SiteEntry) { + this.siteChange.emit(startSiteEntry.entry.title); + } + }); + } + }); + } + private createRowFilter(filter?: RowFilter) { if (!filter) { filter = () => true; @@ -280,8 +305,8 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy { siteChanged(chosenSite: SiteEntry): void { this.siteId = chosenSite.entry.guid; this.setTitleIfCustomSite(chosenSite); + this.siteChange.emit(chosenSite.entry.title); this.updateResults(); - } /** diff --git a/lib/content-services/src/lib/content-node-selector/content-node-selector.component.html b/lib/content-services/src/lib/content-node-selector/content-node-selector.component.html index 8223627c71..5362b57cd7 100644 --- a/lib/content-services/src/lib/content-node-selector/content-node-selector.component.html +++ b/lib/content-services/src/lib/content-node-selector/content-node-selector.component.html @@ -18,7 +18,8 @@ [showSearch]="data?.showSearch" [showDropdownSiteList]="data?.showDropdownSiteList" [showFilesInResult]="data?.showFilesInResult" - (select)="onSelect($event)"> + (select)="onSelect($event)" + (siteChange)="onSiteChange($event)"> diff --git a/lib/content-services/src/lib/content-node-selector/content-node-selector.component.spec.ts b/lib/content-services/src/lib/content-node-selector/content-node-selector.component.spec.ts index 2ad6dfca51..b09241cf20 100644 --- a/lib/content-services/src/lib/content-node-selector/content-node-selector.component.spec.ts +++ b/lib/content-services/src/lib/content-node-selector/content-node-selector.component.spec.ts @@ -20,6 +20,7 @@ import { CUSTOM_ELEMENTS_SCHEMA, EventEmitter } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ContentNodeSelectorComponent } from './content-node-selector.component'; import { Node } from '@alfresco/js-api'; +import { ContentNodeSelectorPanelComponent } from '@alfresco/adf-content-services'; import { By } from '@angular/platform-browser'; import { setupTestBed, SitesService } from '@alfresco/adf-core'; import { of } from 'rxjs'; @@ -159,8 +160,10 @@ describe('ContentNodeSelectorDialogComponent', () => { describe('Title', () => { - it('should be updated when a node is chosen', () => { - component.onSelect([new Node({ id: 'fake', name: 'fake-node' })]); + it('should be updated when a site is chosen', () => { + const fakeSiteTitle = 'My fake site'; + const contentNodePanel = fixture.debugElement.query(By.directive(ContentNodeSelectorPanelComponent)); + contentNodePanel.componentInstance.siteChange.emit(fakeSiteTitle); fixture.detectChanges(); const titleElement = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-title"]')); diff --git a/lib/content-services/src/lib/content-node-selector/content-node-selector.component.ts b/lib/content-services/src/lib/content-node-selector/content-node-selector.component.ts index dde90f4bb6..06f73aa673 100644 --- a/lib/content-services/src/lib/content-node-selector/content-node-selector.component.ts +++ b/lib/content-services/src/lib/content-node-selector/content-node-selector.component.ts @@ -34,7 +34,7 @@ export class ContentNodeSelectorComponent { buttonActionName: string; chosenNode: Node[]; - constructor(public translation: TranslationService, + constructor(private translation: TranslationService, @Inject(MAT_DIALOG_DATA) public data: ContentNodeSelectorComponentData) { this.action = data.actionName ? data.actionName.toUpperCase() : 'CHOOSE'; this.buttonActionName = `NODE_SELECTOR.${this.action}`; @@ -47,7 +47,10 @@ export class ContentNodeSelectorComponent { onSelect(nodeList: Node[]) { this.chosenNode = nodeList; - this.updateTitle(nodeList); + } + + onSiteChange(siteTitle: string) { + this.updateTitle(siteTitle); } onClick(): void { @@ -55,13 +58,13 @@ export class ContentNodeSelectorComponent { this.data.select.complete(); } - updateTitle(nodeList: Node[]): void { - if (this.action === 'CHOOSE' && nodeList) { - this.title = this.getTitleTranslation(this.action, nodeList[0].name); + updateTitle(siteTitle: string) { + if (this.action === 'CHOOSE' && siteTitle) { + this.title = this.getTitleTranslation(this.action, siteTitle); } } getTitleTranslation(action: string, name: string): string { - return this.translation.instant(`NODE_SELECTOR.${action}_ITEM`, { name }); + return this.translation.instant(`NODE_SELECTOR.${action}_ITEM`, { name: this.translation.instant(name) }); } } diff --git a/lib/content-services/src/lib/mock/public-api.ts b/lib/content-services/src/lib/mock/public-api.ts index 0ee68b00a0..151df1f0df 100644 --- a/lib/content-services/src/lib/mock/public-api.ts +++ b/lib/content-services/src/lib/mock/public-api.ts @@ -20,3 +20,4 @@ export * from './document-list.component.mock'; export * from './search.component.mock'; export * from './search.service.mock'; export * from './search-filter-mock'; +export * from './sites-dropdown.component.mock'; diff --git a/lib/content-services/src/lib/mock/sites-dropdown.component.mock.ts b/lib/content-services/src/lib/mock/sites-dropdown.component.mock.ts new file mode 100644 index 0000000000..e1a40657ae --- /dev/null +++ b/lib/content-services/src/lib/mock/sites-dropdown.component.mock.ts @@ -0,0 +1,293 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SitePaging } from '@alfresco/js-api'; + +/* We are using functions instead of constants here to pass a new instance of the object each time */ +export function getFakeSitePaging(): SitePaging { + return { + 'list': { + 'pagination': { + 'count': 2, + 'hasMoreItems': true, + 'totalItems': 2, + 'skipCount': 0, + 'maxItems': 100 + }, + 'entries': [ + { + 'entry': { + 'role': 'SiteManager', + 'visibility': 'PUBLIC', + 'guid': 'fake-1', + 'description': 'fake-test-site', + 'id': 'fake-test-site', + 'preset': 'site-dashboard', + 'title': 'fake-test-site' + } + }, + { + 'entry': { + 'role': 'SiteManager', + 'visibility': 'PUBLIC', + 'guid': 'fake-2', + 'description': 'This is a Sample Alfresco Team site.', + 'id': 'swsdp', + 'preset': 'site-dashboard', + 'title': 'fake-test-2' + } + } + ] + } + }; +} + +export function getFakeSitePagingNoMoreItems(): SitePaging { + return { + 'list': { + 'pagination': { + 'count': 2, + 'hasMoreItems': false, + 'totalItems': 2, + 'skipCount': 0, + 'maxItems': 100 + }, + 'entries': [ + { + 'entry': { + 'role': 'SiteManager', + 'visibility': 'PUBLIC', + 'guid': 'fake-1', + 'description': 'fake-test-site', + 'id': 'fake-test-site', + 'preset': 'site-dashboard', + 'title': 'fake-test-site' + } + }, + { + 'entry': { + 'role': 'SiteManager', + 'visibility': 'PUBLIC', + 'guid': 'fake-2', + 'description': 'This is a Sample Alfresco Team site.', + 'id': 'swsdp', + 'preset': 'site-dashboard', + 'title': 'fake-test-2' + } + } + ] + } + }; +} + +export function getFakeSitePagingFirstPage(): SitePaging { + return { + 'list': { + 'pagination': { + 'count': 2, + 'hasMoreItems': true, + 'totalItems': 2, + 'skipCount': 0, + 'maxItems': 4 + }, + 'entries': [ + { + 'entry': { + 'role': 'SiteManager', + 'visibility': 'PUBLIC', + 'guid': 'fake-1', + 'description': 'fake-test-site', + 'id': 'fake-test-site', + 'preset': 'site-dashboard', + 'title': 'fake-test-site' + } + }, + { + 'entry': { + 'role': 'SiteManager', + 'visibility': 'PUBLIC', + 'guid': 'fake-2', + 'description': 'This is a Sample Alfresco Team site.', + 'id': 'swsdp', + 'preset': 'site-dashboard', + 'title': 'fake-test-2' + } + } + ] + } + }; +} + +export function getFakeSitePagingLastPage(): SitePaging { + return { + 'list': { + 'pagination': { + 'count': 4, + 'hasMoreItems': false, + 'totalItems': 2, + 'skipCount': 2, + 'maxItems': 4 + }, + 'entries': [ + { + 'entry': { + 'role': 'SiteManager', + 'visibility': 'PUBLIC', + 'guid': 'fake-3', + 'description': 'fake-test-3', + 'id': 'fake-test-3', + 'preset': 'site-dashboard', + 'title': 'fake-test-3' + } + }, + { + 'entry': { + 'role': 'SiteManager', + 'visibility': 'PUBLIC', + 'guid': 'fake-test-4', + 'description': 'This is a Sample Alfresco Team site.', + 'id': 'fake-test-4', + 'preset': 'site-dashboard', + 'title': 'fake-test-4' + } + } + ] + } + }; +} + +export function getFakeSitePagingWithMembers() { + return { + 'list': { + 'entries': [{ + 'entry': { + 'visibility': 'MODERATED', + 'guid': 'b4cff62a-664d-4d45-9302-98723eac1319', + 'description': 'This is a Sample Alfresco Team site.', + 'id': 'MODERATED-SITE', + 'preset': 'site-dashboard', + 'title': 'FAKE-MODERATED-SITE' + }, + 'relations': { + 'members': { + 'list': { + 'pagination': { + 'count': 3, + 'hasMoreItems': false, + 'skipCount': 0, + 'maxItems': 100 + }, + 'entries': [ + { + 'entry': { + 'role': 'SiteManager', + 'person': { + 'firstName': 'Administrator', + 'emailNotificationsEnabled': true, + 'company': {}, + 'id': 'admin', + 'enabled': true, + 'email': 'admin@alfresco.com' + }, + 'id': 'admin' + } + }, + { + 'entry': { + 'role': 'SiteCollaborator', + 'person': { + 'lastName': 'Beecher', + 'userStatus': 'Helping to design the look and feel of the new web site', + 'jobTitle': 'Graphic Designer', + 'statusUpdatedAt': '2011-02-15T20:20:13.432+0000', + 'mobile': '0112211001100', + 'emailNotificationsEnabled': true, + 'description': 'Alice is a demo user for the sample Alfresco Team site.', + 'telephone': '0112211001100', + 'enabled': false, + 'firstName': 'Alice', + 'skypeId': 'abeecher', + 'avatarId': '198500fc-1e99-4f5f-8926-248cea433366', + 'location': 'Tilbury, UK', + 'company': { + 'organization': 'Moresby, Garland and Wedge', + 'address1': '200 Butterwick Street', + 'address2': 'Tilbury', + 'address3': 'UK', + 'postcode': 'ALF1 SAM1' + }, + 'id': 'abeecher', + 'email': 'abeecher@example.com' + }, + 'id': 'abeecher' + } + } + ] + } + } + } + }, { + 'entry': { + 'visibility': 'PUBLIC', + 'guid': 'b4cff62a-664d-4d45-9302-98723eac1319', + 'description': 'This is a Sample Alfresco Team site.', + 'id': 'PUBLIC-SITE', + 'preset': 'site-dashboard', + 'title': 'FAKE-SITE-PUBLIC' + } + }, { + 'entry': { + 'visibility': 'PRIVATE', + 'guid': 'b4cff62a-664d-4d45-9302-98723eac1319', + 'description': 'This is a Sample Alfresco Team site.', + 'id': 'MEMBER-SITE', + 'preset': 'site-dashboard', + 'title': 'FAKE-PRIVATE-SITE-MEMBER' + }, + 'relations': { + 'members': { + 'list': { + 'pagination': { + 'count': 3, + 'hasMoreItems': false, + 'skipCount': 0, + 'maxItems': 100 + }, + 'entries': [ + { + 'entry': { + 'role': 'SiteManager', + 'person': { + 'firstName': 'Administrator', + 'emailNotificationsEnabled': true, + 'company': {}, + 'id': 'admin', + 'enabled': true, + 'email': 'admin@alfresco.com' + }, + 'id': 'test' + } + } + ] + } + } + } + } + ] + } + }; +} diff --git a/lib/content-services/src/lib/site-dropdown/sites-dropdown.component.spec.ts b/lib/content-services/src/lib/site-dropdown/sites-dropdown.component.spec.ts index b9438a1d20..193038f510 100644 --- a/lib/content-services/src/lib/site-dropdown/sites-dropdown.component.spec.ts +++ b/lib/content-services/src/lib/site-dropdown/sites-dropdown.component.spec.ts @@ -22,6 +22,31 @@ import { DropdownSitesComponent, Relations } from './sites-dropdown.component'; import { SitesService, setupTestBed, CoreModule, AlfrescoApiService, AlfrescoApiServiceMock } from '@alfresco/adf-core'; import { of } from 'rxjs'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { getFakeSitePaging, + getFakeSitePagingNoMoreItems, + getFakeSitePagingFirstPage, + getFakeSitePagingLastPage, + getFakeSitePagingWithMembers +} from '../mock'; + +const customSiteList = { + 'list': { + 'entries': [ + { + 'entry': { + 'guid': '-my-', + 'title': 'PERSONAL_FILES' + } + }, + { + 'entry': { + 'guid': '-mysites-', + 'title': 'FILE_LIBRARIES' + } + } + ] + } +}; describe('DropdownSitesComponent', () => { @@ -54,45 +79,10 @@ describe('DropdownSitesComponent', () => { debug = fixture.debugElement; element = fixture.nativeElement; component = fixture.componentInstance; + spyOn(siteService, 'getSites').and.returnValue(of(getFakeSitePaging())); })); it('Should show loading item if there are more itemes', async(() => { - spyOn(siteService, 'getSites').and.returnValue(of({ - 'list': { - 'pagination': { - 'count': 2, - 'hasMoreItems': true, - 'totalItems': 2, - 'skipCount': 0, - 'maxItems': 100 - }, - 'entries': [ - { - 'entry': { - 'role': 'SiteManager', - 'visibility': 'PUBLIC', - 'guid': 'fake-1', - 'description': 'fake-test-site', - 'id': 'fake-test-site', - 'preset': 'site-dashboard', - 'title': 'fake-test-site' - } - }, - { - 'entry': { - 'role': 'SiteManager', - 'visibility': 'PUBLIC', - 'guid': 'fake-2', - 'description': 'This is a Sample Alfresco Team site.', - 'id': 'swsdp', - 'preset': 'site-dashboard', - 'title': 'fake-test-2' - } - } - ] - } - })); - fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -101,42 +91,6 @@ describe('DropdownSitesComponent', () => { })); it('Should not show loading item if there are more itemes', async(() => { - spyOn(siteService, 'getSites').and.returnValue(of({ - 'list': { - 'pagination': { - 'count': 2, - 'hasMoreItems': false, - 'totalItems': 2, - 'skipCount': 0, - 'maxItems': 100 - }, - 'entries': [ - { - 'entry': { - 'role': 'SiteManager', - 'visibility': 'PUBLIC', - 'guid': 'fake-1', - 'description': 'fake-test-site', - 'id': 'fake-test-site', - 'preset': 'site-dashboard', - 'title': 'fake-test-site' - } - }, - { - 'entry': { - 'role': 'SiteManager', - 'visibility': 'PUBLIC', - 'guid': 'fake-2', - 'description': 'This is a Sample Alfresco Team site.', - 'id': 'swsdp', - 'preset': 'site-dashboard', - 'title': 'fake-test-2' - } - } - ] - } - })); - fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -150,41 +104,7 @@ describe('DropdownSitesComponent', () => { beforeEach(async(() => { siteService = TestBed.get(SitesService); - spyOn(siteService, 'getSites').and.returnValue(of({ - 'list': { - 'pagination': { - 'count': 2, - 'hasMoreItems': false, - 'totalItems': 2, - 'skipCount': 0, - 'maxItems': 100 - }, - 'entries': [ - { - 'entry': { - 'role': 'SiteManager', - 'visibility': 'PUBLIC', - 'guid': 'fake-1', - 'description': 'fake-test-site', - 'id': 'fake-test-site', - 'preset': 'site-dashboard', - 'title': 'fake-test-site' - } - }, - { - 'entry': { - 'role': 'SiteManager', - 'visibility': 'PUBLIC', - 'guid': 'fake-2', - 'description': 'This is a Sample Alfresco Team site.', - 'id': 'swsdp', - 'preset': 'site-dashboard', - 'title': 'fake-test-2' - } - } - ] - } - })); + spyOn(siteService, 'getSites').and.returnValue(of(getFakeSitePagingNoMoreItems())); fixture = TestBed.createComponent(DropdownSitesComponent); debug = fixture.debugElement; @@ -260,24 +180,7 @@ describe('DropdownSitesComponent', () => { })); it('should load custom sites when the \'siteList\' input property is given a value', async(() => { - component.siteList = { - 'list': { - 'entries': [ - { - 'entry': { - 'guid': '-my-', - 'title': 'PERSONAL_FILES' - } - }, - { - 'entry': { - 'guid': '-mysites-', - 'title': 'FILE_LIBRARIES' - } - } - ] - } - }; + component.siteList = customSiteList; fixture.detectChanges(); @@ -339,129 +242,43 @@ describe('DropdownSitesComponent', () => { }); }); - describe('Sites with members', () => { + describe('Default value', () => { beforeEach(async(() => { siteService = TestBed.get(SitesService); - spyOn(siteService, 'getSites').and.returnValue(of({ - 'list': { - 'entries': [{ - 'entry': { - 'visibility': 'MODERATED', - 'guid': 'b4cff62a-664d-4d45-9302-98723eac1319', - 'description': 'This is a Sample Alfresco Team site.', - 'id': 'MODERATED-SITE', - 'preset': 'site-dashboard', - 'title': 'FAKE-MODERATED-SITE' - }, - 'relations': { - 'members': { - 'list': { - 'pagination': { - 'count': 3, - 'hasMoreItems': false, - 'skipCount': 0, - 'maxItems': 100 - }, - 'entries': [ - { - 'entry': { - 'role': 'SiteManager', - 'person': { - 'firstName': 'Administrator', - 'emailNotificationsEnabled': true, - 'company': {}, - 'id': 'admin', - 'enabled': true, - 'email': 'admin@alfresco.com' - }, - 'id': 'admin' - } - }, - { - 'entry': { - 'role': 'SiteCollaborator', - 'person': { - 'lastName': 'Beecher', - 'userStatus': 'Helping to design the look and feel of the new web site', - 'jobTitle': 'Graphic Designer', - 'statusUpdatedAt': '2011-02-15T20:20:13.432+0000', - 'mobile': '0112211001100', - 'emailNotificationsEnabled': true, - 'description': 'Alice is a demo user for the sample Alfresco Team site.', - 'telephone': '0112211001100', - 'enabled': false, - 'firstName': 'Alice', - 'skypeId': 'abeecher', - 'avatarId': '198500fc-1e99-4f5f-8926-248cea433366', - 'location': 'Tilbury, UK', - 'company': { - 'organization': 'Moresby, Garland and Wedge', - 'address1': '200 Butterwick Street', - 'address2': 'Tilbury', - 'address3': 'UK', - 'postcode': 'ALF1 SAM1' - }, - 'id': 'abeecher', - 'email': 'abeecher@example.com' - }, - 'id': 'abeecher' - } - } - ] - } - } - } - }, { - 'entry': { - 'visibility': 'PUBLIC', - 'guid': 'b4cff62a-664d-4d45-9302-98723eac1319', - 'description': 'This is a Sample Alfresco Team site.', - 'id': 'PUBLIC-SITE', - 'preset': 'site-dashboard', - 'title': 'FAKE-SITE-PUBLIC' - } - }, { - 'entry': { - 'visibility': 'PRIVATE', - 'guid': 'b4cff62a-664d-4d45-9302-98723eac1319', - 'description': 'This is a Sample Alfresco Team site.', - 'id': 'MEMBER-SITE', - 'preset': 'site-dashboard', - 'title': 'FAKE-PRIVATE-SITE-MEMBER' - }, - 'relations': { - 'members': { - 'list': { - 'pagination': { - 'count': 3, - 'hasMoreItems': false, - 'skipCount': 0, - 'maxItems': 100 - }, - 'entries': [ - { - 'entry': { - 'role': 'SiteManager', - 'person': { - 'firstName': 'Administrator', - 'emailNotificationsEnabled': true, - 'company': {}, - 'id': 'admin', - 'enabled': true, - 'email': 'admin@alfresco.com' - }, - 'id': 'test' - } - } - ] - } - } - } - } - ] - } - })); + spyOn(siteService, 'getSites').and.returnValues(of(getFakeSitePagingFirstPage()), of(getFakeSitePagingLastPage())); + + fixture = TestBed.createComponent(DropdownSitesComponent); + component = fixture.componentInstance; + })); + + it('should load new sites if default value is not in the first page', (done) => { + component.value = 'fake-test-4'; + fixture.detectChanges(); + + fixture.whenStable().then(() => { + expect(component.selected.entry.title).toBe('fake-test-4'); + done(); + }); + }); + + it('should NOT reload infinitely if default value is NOT found after all sites are loaded', (done) => { + component.value = 'nonexistent-site'; + fixture.detectChanges(); + + fixture.whenStable().then(() => { + expect(component.selected).toBeUndefined(); + expect(component.loading).toBeFalsy(); + done(); + }); + }); + }); + + describe('Sites with members', () => { + + beforeEach(async(() => { + siteService = TestBed.get(SitesService); + spyOn(siteService, 'getSites').and.returnValue(of(getFakeSitePagingWithMembers())); fixture = TestBed.createComponent(DropdownSitesComponent); debug = fixture.debugElement; diff --git a/lib/content-services/src/lib/site-dropdown/sites-dropdown.component.ts b/lib/content-services/src/lib/site-dropdown/sites-dropdown.component.ts index 38b4b06ed4..7c2b07786a 100644 --- a/lib/content-services/src/lib/site-dropdown/sites-dropdown.component.ts +++ b/lib/content-services/src/lib/site-dropdown/sites-dropdown.component.ts @@ -142,8 +142,8 @@ export class DropdownSitesComponent implements OnInit, OnDestroy { if (!this.hideMyFiles) { const siteEntry = new SiteEntry({ entry: { - id: '-my-', - guid: '-my-', + id: this.MY_FILES_VALUE, + guid: this.MY_FILES_VALUE, title: 'DROPDOWN.MY_FILES_OPTION' } }); @@ -151,7 +151,7 @@ export class DropdownSitesComponent implements OnInit, OnDestroy { this.siteList.list.entries.unshift(siteEntry); if (!this.value) { - this.value = '-my-'; + this.value = this.MY_FILES_VALUE; } } @@ -164,6 +164,10 @@ export class DropdownSitesComponent implements OnInit, OnDestroy { this.selected = this.siteList.list.entries.find((site: SiteEntry) => site.entry.id === this.value); + if (this.value && !this.selected && this.siteListHasMoreItems()) { + this.loadSiteList(); + } + this.loading = false; }, (error) => { @@ -172,11 +176,15 @@ export class DropdownSitesComponent implements OnInit, OnDestroy { } showLoading(): boolean { - return this.loading && (this.siteList && this.siteList.list.pagination && this.siteList.list.pagination.hasMoreItems); + return this.loading && this.siteListHasMoreItems(); } isInfiniteScrollingEnabled(): boolean { - return !this.loading && (this.siteList && this.siteList.list.pagination && this.siteList.list.pagination.hasMoreItems); + return !this.loading && this.siteListHasMoreItems(); + } + + private siteListHasMoreItems(): boolean { + return this.siteList && this.siteList.list.pagination && this.siteList.list.pagination.hasMoreItems; } private filteredResultsByMember(sites: SitePaging): SitePaging { @@ -191,5 +199,4 @@ export class DropdownSitesComponent implements OnInit, OnDestroy { return member.entry.id.toLowerCase() === loggedUserName.toLowerCase(); }); } - } diff --git a/lib/core/form/services/activiti-alfresco.service.ts b/lib/core/form/services/activiti-alfresco.service.ts index c6f1d0b716..8f497d4a2b 100644 --- a/lib/core/form/services/activiti-alfresco.service.ts +++ b/lib/core/form/services/activiti-alfresco.service.ts @@ -17,6 +17,7 @@ import { AlfrescoApiService } from '../../services/alfresco-api.service'; import { LogService } from '../../services/log.service'; +import { SitesService } from '../../services/sites.service'; import { Injectable } from '@angular/core'; import { AlfrescoApiCompatibility, MinimalNode, RelatedContentRepresentation } from '@alfresco/js-api'; import { Observable, from, throwError } from 'rxjs'; @@ -33,7 +34,8 @@ export class ActivitiContentService { static GENERIC_ERROR_MESSAGE: string = 'Server error'; constructor(private apiService: AlfrescoApiService, - private logService: LogService) { + private logService: LogService, + private sitesService: SitesService) { } /** @@ -94,7 +96,7 @@ export class ActivitiContentService { applyAlfrescoNode(node: MinimalNode, siteId: string, accountId: string) { const apiService: AlfrescoApiCompatibility = this.apiService.getInstance(); - const currentSideId = siteId ? siteId : this.getSiteNameFromNodePath(node); + const currentSideId = siteId ? siteId : this.sitesService.getSiteNameFromNodePath(node); const params: RelatedContentRepresentation = { source: accountId, mimeType: node.content.mimeType, @@ -109,18 +111,6 @@ export class ActivitiContentService { ); } - private getSiteNameFromNodePath(node: MinimalNode): string { - let siteName = ''; - if (node.path) { - const foundNode = node.path - .elements.find((pathNode: MinimalNode) => - pathNode.nodeType === 'st:site' && - pathNode.name !== 'Sites'); - siteName = foundNode ? foundNode.name : ''; - } - return siteName.toLocaleLowerCase(); - } - toJson(res: any) { if (res) { return res || {}; diff --git a/lib/core/services/sites.service.ts b/lib/core/services/sites.service.ts index a444ce0218..a253465b99 100644 --- a/lib/core/services/sites.service.ts +++ b/lib/core/services/sites.service.ts @@ -18,7 +18,7 @@ import { Injectable } from '@angular/core'; import { Observable, from, throwError } from 'rxjs'; import { AlfrescoApiService } from './alfresco-api.service'; -import { SitePaging, SiteEntry, SitesApi, SiteMembershipRequestWithPersonPaging } from '@alfresco/js-api'; +import { SitePaging, SiteEntry, MinimalNode, SitesApi, SiteMembershipRequestWithPersonPaging } from '@alfresco/js-api'; import { catchError } from 'rxjs/operators'; @Injectable({ @@ -103,6 +103,24 @@ export class SitesService { return this.apiService.getInstance().getEcmUsername(); } + /** + * Looks for a site inside the path of a Node and returns its guid if it finds one. + * (return an empty string if no site is found) + * @param node Node to look for parent site + * @returns Site guid + */ + getSiteNameFromNodePath(node: MinimalNode): string { + let siteName = ''; + if (node.path && node.path.elements) { + const foundNode = node.path + .elements.find((pathNode: MinimalNode) => + pathNode.nodeType === 'st:site' && + pathNode.name !== 'Sites'); + siteName = foundNode ? foundNode.name : ''; + } + return siteName.toLocaleLowerCase(); + } + /** * Gets a list of site membership requests. * @param opts Options supported by JS-API diff --git a/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.component.html b/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.component.html index 70d0051436..500df55247 100644 --- a/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.component.html +++ b/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.component.html @@ -1,6 +1,8 @@
{{title}} + data-automation-id="content-node-selector-title"> + {{title}} + {{data.title}}
diff --git a/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.component.spec.ts b/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.component.spec.ts index 08a65c10a5..0838b7b34a 100644 --- a/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.component.spec.ts +++ b/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.component.spec.ts @@ -145,22 +145,14 @@ describe('AttachFileWidgetDialogComponent', () => { }); }); - it('should update the title when the selected node is a file', () => { - const fakeNode: Node = new Node({ id: 'fake', isFile: true}); - contentNodePanel.componentInstance.select.emit([fakeNode]); + it('should update the title when a site is selected', () => { + const fakeSiteTitle = 'My fake site'; + contentNodePanel.componentInstance.siteChange.emit(fakeSiteTitle); fixture.detectChanges(); + const titleElement = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-title"]')); expect(titleElement).not.toBeNull(); expect(titleElement.nativeElement.innerText).toBe('ATTACH-FILE.ACTIONS.CHOOSE_ITEM'); }); - - it('should update the title when the selected node is a folder', () => { - const fakeNode: Node = new Node({ id: 'fake', isFolder: true}); - contentNodePanel.componentInstance.select.emit([fakeNode]); - fixture.detectChanges(); - const titleElement = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-title"]')); - expect(titleElement).not.toBeNull(); - expect(titleElement.nativeElement.innerText).toBe('ATTACH-FILE.ACTIONS.CHOOSE_IN'); - }); }); }); diff --git a/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.component.ts b/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.component.ts index ba45178e0a..8c8dba3876 100644 --- a/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.component.ts +++ b/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.component.ts @@ -50,7 +50,7 @@ export class AttachFileWidgetDialogComponent { ( externalApiService).init(data.ecmHost, data.context); this.action = data.actionName ? data.actionName.toUpperCase() : 'CHOOSE'; this.buttonActionName = `ATTACH-FILE.ACTIONS.${this.action}`; - this.title = data.title; + this.updateTitle('DROPDOWN.MY_FILES_OPTION'); } isLoggedIn() { @@ -66,12 +66,11 @@ export class AttachFileWidgetDialogComponent { } onSelect(nodeList: Node[]) { - if (nodeList && nodeList[0].isFile) { - this.chosenNode = nodeList; - } else { - this.chosenNode = null; - } - this.updateTitle(nodeList); + this.chosenNode = nodeList; + } + + onSiteChange(siteTitle: string) { + this.updateTitle(siteTitle); } onClick() { @@ -79,17 +78,13 @@ export class AttachFileWidgetDialogComponent { this.data.selected.complete(); } - updateTitle(nodeList: Node[]): void { - if (this.action === 'CHOOSE' && nodeList) { - if (nodeList[0].isFile) { - this.title = this.getTitleTranslation(this.action + '_ITEM', nodeList[0].name); - } else if (nodeList[0].isFolder) { - this.title = this.getTitleTranslation(this.action + '_IN', nodeList[0].name); - } + updateTitle(siteTitle: string) { + if (this.action === 'CHOOSE' && siteTitle) { + this.title = this.getTitleTranslation(this.action, siteTitle); } } - getTitleTranslation(action: string, name: string): string { - return this.translation.instant(`ATTACH-FILE.ACTIONS.${action}`, { name }); + getTitleTranslation(action: string, name?: string): string { + return this.translation.instant(`ATTACH-FILE.ACTIONS.${action}_ITEM`, { name: this.translation.instant(name) }); } } diff --git a/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.service.ts b/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.service.ts index 0f84187161..ad386e505f 100644 --- a/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.service.ts +++ b/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.service.ts @@ -54,7 +54,7 @@ export class AttachFileWidgetDialogService { selected, ecmHost, context, - isSelectionValid: this.isNodeValid.bind(this), + isSelectionValid: this.isNodeFile.bind(this), showFilesInResult: true }; @@ -71,8 +71,8 @@ export class AttachFileWidgetDialogService { this.dialog.closeAll(); } - private isNodeValid(entry: Node): boolean { - return entry.isFile || entry.isFolder; + private isNodeFile(entry: Node): boolean { + return entry.isFile; } private getLoginTitleTranslation(ecmHost: string): string {