From e75335a06df33678d8b3eb253f26dccf81e08e2b Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Wed, 27 Mar 2019 11:38:37 +0000 Subject: [PATCH] [ADF-3794] Update individual rows without reloading DocumentList (#4213) * reload table cells on node updates * update unit tests * update dynamic columns * fix value type * fix tests * update code as per review * update variable name * test fixes, core automation service * fix test --- demo-shell/src/app/app.module.ts | 11 +- .../app/components/files/files.component.ts | 4 - .../card-view/aspect-oriented-config.e2e.ts | 114 ++++++------- .../card-view/metadata-smoke-tests.e2e.ts | 18 ++- e2e/proxy.ts | 24 +++ .../library-name-column.component.ts | 153 +++++++++++------- .../library-role-column.component.spec.ts | 30 +++- .../library-role-column.component.ts | 108 +++++++++---- .../library-status-column.component.ts | 93 +++++++---- .../name-column/name-column.component.ts | 101 ++++++++---- .../data/share-data-row.model.ts | 5 + .../datatable-cell.component.spec.ts | 7 +- .../datatable/datatable-cell.component.ts | 61 +++++-- .../datatable/date-cell.component.ts | 25 ++- .../datatable/filesize-cell.component.ts | 13 +- .../datatable/location-cell.component.spec.ts | 32 ++-- .../datatable/location-cell.component.ts | 29 ++-- lib/core/services/automation.service.ts | 37 +++++ lib/core/services/public-api.ts | 1 + 19 files changed, 584 insertions(+), 282 deletions(-) create mode 100644 e2e/proxy.ts create mode 100644 lib/core/services/automation.service.ts diff --git a/demo-shell/src/app/app.module.ts b/demo-shell/src/app/app.module.ts index 376d429c25..4cd74be7c5 100644 --- a/demo-shell/src/app/app.module.ts +++ b/demo-shell/src/app/app.module.ts @@ -23,7 +23,7 @@ import { ChartsModule } from 'ng2-charts'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { AppConfigService, TRANSLATION_PROVIDER, DebugAppConfigService, CoreModule } from '@alfresco/adf-core'; +import { AppConfigService, TRANSLATION_PROVIDER, DebugAppConfigService, CoreModule, CoreAutomationService } from '@alfresco/adf-core'; import { ExtensionsModule } from '@alfresco/adf-extensions'; import { AppComponent } from './app.component'; import { MaterialModule } from './material.module'; @@ -172,7 +172,8 @@ import { NestedMenuPositionDirective } from './components/app-layout/cloud/direc source: 'resources/lazy-loading' } }, - PreviewService + PreviewService, + CoreAutomationService ], entryComponents: [ VersionManagerDialogAdapterComponent, @@ -180,4 +181,8 @@ import { NestedMenuPositionDirective } from './components/app-layout/cloud/direc ], bootstrap: [AppComponent] }) -export class AppModule {} +export class AppModule { + constructor(automationService: CoreAutomationService) { + automationService.setup(); + } +} diff --git a/demo-shell/src/app/components/files/files.component.ts b/demo-shell/src/app/components/files/files.component.ts index b7ea355d10..ca3caef9b2 100644 --- a/demo-shell/src/app/components/files/files.component.ts +++ b/demo-shell/src/app/components/files/files.component.ts @@ -214,10 +214,6 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy { @Optional() private route: ActivatedRoute, public authenticationService: AuthenticationService, public alfrescoApiService: AlfrescoApiService) { - - this.alfrescoApiService.nodeUpdated.subscribe(() => { - this.documentList.reload(); - }); } showFile(event) { diff --git a/e2e/core/card-view/aspect-oriented-config.e2e.ts b/e2e/core/card-view/aspect-oriented-config.e2e.ts index 83ab506542..d6f9889ae5 100644 --- a/e2e/core/card-view/aspect-oriented-config.e2e.ts +++ b/e2e/core/card-view/aspect-oriented-config.e2e.ts @@ -15,13 +15,10 @@ * limitations under the License. */ -import { browser } from 'protractor'; - import { LoginPage } from '../../pages/adf/loginPage'; import { ViewerPage } from '../../pages/adf/viewerPage'; import { MetadataViewPage } from '../../pages/adf/metadataViewPage'; import { NavigationBarPage } from '../../pages/adf/navigationBarPage'; -import { ConfigEditorPage } from '../../pages/adf/configEditorPage'; import { AcsUserModel } from '../../models/ACS/acsUserModel'; import { FileModel } from '../../models/ACS/fileModel'; @@ -33,6 +30,7 @@ import { AlfrescoApiCompatibility as AlfrescoApi } from '@alfresco/js-api'; import { UploadActions } from '../../actions/ACS/upload.actions'; import { ContentServicesPage } from '../../pages/adf/contentServicesPage'; import { check } from '../../util/material'; +import { setConfigField } from '../../proxy'; describe('Aspect oriented config', () => { @@ -40,7 +38,6 @@ describe('Aspect oriented config', () => { const viewerPage = new ViewerPage(); const metadataViewPage = new MetadataViewPage(); const navigationBarPage = new NavigationBarPage(); - const configEditorPage = new ConfigEditorPage(); const contentServicesPage = new ContentServicesPage(); const modelOneName = 'modelOne', emptyAspectName = 'emptyAspect'; const defaultModel = 'cm', defaultEmptyPropertiesAspect = 'taggable', aspectName = 'Taggable'; @@ -53,7 +50,6 @@ describe('Aspect oriented config', () => { }); beforeAll(async (done) => { - const uploadActions = new UploadActions(); this.alfrescoJsApi = new AlfrescoApi({ @@ -92,35 +88,33 @@ describe('Aspect oriented config', () => { done(); }); - beforeEach(async (done) => { - navigationBarPage.clickConfigEditorButton(); - configEditorPage.clickClearButton(); - done(); - }); - afterEach(async (done) => { viewerPage.clickCloseButton(); contentServicesPage.checkAcsContainer(); - browser.refresh(); - contentServicesPage.checkAcsContainer(); done(); }); - it('[C261117] Should be possible restrict the display properties of one an aspect', () => { + it('[C261117] Should be possible restrict the display properties of one an aspect', async () => { - configEditorPage.enterBigConfigurationText('{ "presets": {' + - ' "default": [{' + - ' "title": "IMAGE",' + - ' "items": [' + - ' {' + - ' "aspect": "exif:exif", "properties": [ "exif:pixelXDimension", "exif:pixelYDimension", "exif:isoSpeedRatings"]' + - ' }' + - ' ]' + - ' }]' + - ' }' + - ' }'); - - configEditorPage.clickSaveButton(); + await setConfigField('content-metadata', JSON.stringify({ + presets: { + default: [ + { + title: 'IMAGE', + items: [ + { + aspect: 'exif:exif', + properties: [ + 'exif:pixelXDimension', + 'exif:pixelYDimension', + 'exif:isoSpeedRatings' + ] + } + ] + } + ] + } + })); navigationBarPage.clickContentServicesButton(); @@ -141,19 +135,17 @@ describe('Aspect oriented config', () => { metadataViewPage.checkPropertyIsVisible('properties.exif:isoSpeedRatings', 'textitem'); }); - it('[C260185] Should ignore not existing aspect when present in the configuration', () => { + it('[C260185] Should ignore not existing aspect when present in the configuration', async () => { - configEditorPage.enterBigConfigurationText(' {' + - ' "presets": {' + - ' "default": {' + - ' "exif:exif": "*",' + - ' "cm:versionable": "*",' + - ' "not:exists": "*"' + - ' }' + - ' }' + - ' }'); - - configEditorPage.clickSaveButton(); + await setConfigField('content-metadata', JSON.stringify({ + presets: { + default: { + 'exif:exif': '*', + 'cm:versionable': '*', + 'not:exists': '*' + } + } + })); navigationBarPage.clickContentServicesButton(); @@ -170,11 +162,9 @@ describe('Aspect oriented config', () => { metadataViewPage.checkMetadataGroupIsNotPresent('exists'); }); - it('[C260183] Should show all the aspect if the content-metadata configuration is NOT provided', () => { + it('[C260183] Should show all the aspect if the content-metadata configuration is NOT provided', async () => { - configEditorPage.enterBigConfigurationText('{ }'); - - configEditorPage.clickSaveButton(); + await setConfigField('content-metadata', '{}'); navigationBarPage.clickContentServicesButton(); @@ -190,15 +180,13 @@ describe('Aspect oriented config', () => { metadataViewPage.checkMetadataGroupIsPresent('Versionable'); }); - it('[C260182] Should show all the aspects if the default configuration contains the star symbol', () => { + it('[C260182] Should show all the aspects if the default configuration contains the star symbol', async () => { - configEditorPage.enterBigConfigurationText('{' + - ' "presets": {' + - ' "default": "*"' + - ' }' + - '}'); - - configEditorPage.clickSaveButton(); + await setConfigField('content-metadata', JSON.stringify({ + presets: { + default: '*' + } + })); navigationBarPage.clickContentServicesButton(); @@ -215,9 +203,9 @@ describe('Aspect oriented config', () => { metadataViewPage.checkMetadataGroupIsPresent('Versionable'); }); - it('[C268899] Should be possible use a Translation key as Title of a metadata group', () => { + it('[C268899] Should be possible use a Translation key as Title of a metadata group', async () => { - configEditorPage.enterBigConfigurationText('{' + + await setConfigField('content-metadata', '{' + ' "presets": {' + ' "default": [' + ' {' + @@ -242,8 +230,6 @@ describe('Aspect oriented config', () => { ' }' + '}'); - configEditorPage.clickSaveButton(); - navigationBarPage.clickContentServicesButton(); viewerPage.viewFile(pngFileModel.name); @@ -262,9 +248,9 @@ describe('Aspect oriented config', () => { }); - it('[C279968] Should be possible use a custom preset', () => { + it('[C279968] Should be possible use a custom preset', async () => { - configEditorPage.enterBigConfigurationText('{' + + await setConfigField('content-metadata', '{' + ' "presets": {' + ' "custom-preset": {' + ' "exif:exif": "*",' + @@ -273,8 +259,6 @@ describe('Aspect oriented config', () => { ' }' + '}'); - configEditorPage.clickSaveButton(); - navigationBarPage.clickContentServicesButton(); viewerPage.viewFile(pngFileModel.name); @@ -294,9 +278,9 @@ describe('Aspect oriented config', () => { metadataViewPage.checkMetadataGroupIsPresent('Versionable'); }); - it('[C299186] The aspect without properties is not displayed', () => { + it('[C299186] The aspect without properties is not displayed', async () => { - configEditorPage.enterBigConfigurationText('{' + + await setConfigField('content-metadata', '{' + ' "presets": { "' + modelOneName + ' ": { "' + modelOneName + ':' + emptyAspectName + ' ":"*"' + @@ -304,8 +288,6 @@ describe('Aspect oriented config', () => { ' }' + '}'); - configEditorPage.clickSaveButton(); - navigationBarPage.clickContentServicesButton(); viewerPage.viewFile(pngFileModel.name); @@ -319,9 +301,9 @@ describe('Aspect oriented config', () => { metadataViewPage.checkMetadataGroupIsNotPresent(emptyAspectName); }); - it('[C299187] The aspect with empty properties is displayed when edit', () => { + it('[C299187] The aspect with empty properties is displayed when edit', async () => { - configEditorPage.enterBigConfigurationText('{' + + await setConfigField('content-metadata', '{' + ' "presets": { "' + defaultModel + ' ": { "' + defaultModel + ':' + defaultEmptyPropertiesAspect + ' ":"*"' + @@ -329,8 +311,6 @@ describe('Aspect oriented config', () => { ' }' + '}'); - configEditorPage.clickSaveButton(); - navigationBarPage.clickContentServicesButton(); viewerPage.viewFile(pngFileModel.name); diff --git a/e2e/core/card-view/metadata-smoke-tests.e2e.ts b/e2e/core/card-view/metadata-smoke-tests.e2e.ts index 5a1fee3d40..7af069627d 100644 --- a/e2e/core/card-view/metadata-smoke-tests.e2e.ts +++ b/e2e/core/card-view/metadata-smoke-tests.e2e.ts @@ -32,6 +32,7 @@ import dateFormat = require('dateformat'); import { AlfrescoApiCompatibility as AlfrescoApi } from '@alfresco/js-api'; import { UploadActions } from '../../actions/ACS/upload.actions'; import { NavigationBarPage } from '../../pages/adf/navigationBarPage'; +import { setConfigField } from '../../proxy'; describe('Metadata component', () => { @@ -95,6 +96,16 @@ describe('Metadata component', () => { describe('Viewer Metadata', () => { + beforeAll(async() => { + await setConfigField('content-metadata', JSON.stringify({ + presets: { + default: { + 'exif:exif': '*' + } + } + })); + }); + beforeEach(async (done) => { viewerPage.viewFile(pngFileModel.name); viewerPage.checkFileIsLoaded(); @@ -194,9 +205,10 @@ describe('Metadata component', () => { await metadataViewPage.clickUpdatePropertyIcon('properties.cm:description'); expect(metadataViewPage.getPropertyText('properties.cm:description')).toEqual('example description'); - viewerPage.clickCloseButton(); + await viewerPage.clickCloseButton(); + contentServicesPage.waitForTableBody(); - viewerPage.viewFile('exampleText.png'); + viewerPage.viewFile(resources.Files.ADF_DOCUMENTS.PNG.file_name); viewerPage.clickInfoButton(); viewerPage.checkInfoSideBarIsDisplayed(); metadataViewPage.clickOnPropertiesTab(); @@ -260,6 +272,7 @@ describe('Metadata component', () => { browser.controlFlow().execute(async () => { await metadataViewPage.editIconClick(); + metadataViewPage.clickEditPropertyIcons('properties.exif:software'); metadataViewPage.enterPropertyText('properties.exif:software', 'test custom text software'); await metadataViewPage.clickUpdatePropertyIcon('properties.exif:software'); @@ -303,6 +316,7 @@ describe('Metadata component', () => { browser.controlFlow().execute(async () => { await metadataViewPage.editIconClick(); + metadataViewPage.clickEditPropertyIcons('name'); metadataViewPage.enterPropertyText('name', 'newnameFolder'); await metadataViewPage.clickClearPropertyIcon('name'); diff --git a/e2e/proxy.ts b/e2e/proxy.ts new file mode 100644 index 0000000000..ec533eda52 --- /dev/null +++ b/e2e/proxy.ts @@ -0,0 +1,24 @@ +/*! + * @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 { browser } from 'protractor'; + +export async function setConfigField(field: string, value: string) { + return browser.executeScript( + `window.adf.setConfigField('${field}', '${value}');` + ); +} diff --git a/lib/content-services/document-list/components/library-name-column/library-name-column.component.ts b/lib/content-services/document-list/components/library-name-column/library-name-column.component.ts index 481875c0e7..a5b3f65595 100644 --- a/lib/content-services/document-list/components/library-name-column/library-name-column.component.ts +++ b/lib/content-services/document-list/components/library-name-column/library-name-column.component.ts @@ -16,75 +16,112 @@ */ import { - Component, - ChangeDetectionStrategy, - ViewEncapsulation, - OnInit, - Input, - ElementRef + Component, + ChangeDetectionStrategy, + ViewEncapsulation, + OnInit, + Input, + ElementRef, + OnDestroy } from '@angular/core'; -import { NodeEntry } from '@alfresco/js-api'; +import { NodeEntry, Node, Site } from '@alfresco/js-api'; import { ShareDataRow } from '../../data/share-data-row.model'; +import { AlfrescoApiService } from '@alfresco/adf-core'; +import { BehaviorSubject, Subscription } from 'rxjs'; @Component({ - selector: 'adf-library-name-column', - template: ` - - {{ displayText }} - - `, - changeDetection: ChangeDetectionStrategy.OnPush, - encapsulation: ViewEncapsulation.None, - host: { class: 'adf-datatable-cell adf-datatable-link adf-library-name-column' } + selector: 'adf-library-name-column', + template: ` + + {{ displayText$ | async }} + + `, + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, + host: { + class: 'adf-datatable-cell adf-datatable-link adf-library-name-column' + } }) -export class LibraryNameColumnComponent implements OnInit { - @Input() - context: any; +export class LibraryNameColumnComponent implements OnInit, OnDestroy { + @Input() + context: any; - displayTooltip: string; - displayText: string; - node: NodeEntry; + displayTooltip$ = new BehaviorSubject(''); + displayText$ = new BehaviorSubject(''); + node: NodeEntry; - constructor(private element: ElementRef) {} + private sub: Subscription; - ngOnInit() { - this.node = this.context.row.node; - const rows: Array = this.context.data.rows || []; - if (this.node && this.node.entry) { - this.displayText = this.makeLibraryTitle(this.node.entry, rows); - this.displayTooltip = this.makeLibraryTooltip(this.node.entry); + constructor( + private element: ElementRef, + private alfrescoApiService: AlfrescoApiService + ) {} + + ngOnInit() { + this.updateValue(); + + this.sub = this.alfrescoApiService.nodeUpdated.subscribe( + (node: Node) => { + const row: ShareDataRow = this.context.row; + if (row) { + const { entry } = row.node; + + if (entry === node) { + row.node = { entry }; + this.updateValue(); + } + } + } + ); } - } - onClick() { - this.element.nativeElement.dispatchEvent( - new CustomEvent('name-click', { - bubbles: true, - detail: { - node: this.node + protected updateValue() { + this.node = this.context.row.node; + const rows: Array = this.context.data.rows || []; + if (this.node && this.node.entry) { + this.displayText$.next( + this.makeLibraryTitle( this.node.entry, rows) + ); + this.displayTooltip$.next(this.makeLibraryTooltip(this.node.entry)); } - }) - ); - } - - makeLibraryTooltip(library: any): string { - const { description, title } = library; - - return description || title || ''; - } - - makeLibraryTitle(library: any, rows: Array): string { - const entries = rows.map((r: ShareDataRow) => r.node.entry); - const { title, id } = library; - - let isDuplicate = false; - - if (entries) { - isDuplicate = entries.some((entry: any) => { - return entry.id !== id && entry.title === title; - }); } - return isDuplicate ? `${title} (${id})` : `${title}`; - } + onClick() { + this.element.nativeElement.dispatchEvent( + new CustomEvent('name-click', { + bubbles: true, + detail: { + node: this.node + } + }) + ); + } + + makeLibraryTooltip(library: any): string { + const { description, title } = library; + + return description || title || ''; + } + + makeLibraryTitle(library: Site, rows: Array): string { + const entries = rows.map((row: ShareDataRow) => row.node.entry); + const { title, id } = library; + + let isDuplicate = false; + + if (entries) { + isDuplicate = entries.some((entry: any) => { + return entry.id !== id && entry.title === title; + }); + } + + return isDuplicate ? `${title} (${id})` : `${title}`; + } + + ngOnDestroy() { + if (this.sub) { + this.sub.unsubscribe(); + this.sub = null; + } + } } diff --git a/lib/content-services/document-list/components/library-role-column/library-role-column.component.spec.ts b/lib/content-services/document-list/components/library-role-column/library-role-column.component.spec.ts index 6184d32a93..10caf2bde4 100644 --- a/lib/content-services/document-list/components/library-role-column/library-role-column.component.spec.ts +++ b/lib/content-services/document-list/components/library-role-column/library-role-column.component.spec.ts @@ -39,39 +39,59 @@ describe('LibraryNameColumnComponent', () => { component.context = { row: { node: { entry: { role: 'SiteManager' } } } }; + + let value = ''; + component.displayText$.subscribe((val) => value = val); + fixture.detectChanges(); - expect(component.displayText).toBe('LIBRARY.ROLE.MANAGER'); + expect(value).toBe('LIBRARY.ROLE.MANAGER'); }); it('should render Collaborator', () => { component.context = { row: { node: { entry: { role: 'SiteCollaborator' } } } }; + + let value = ''; + component.displayText$.subscribe((val) => value = val); + fixture.detectChanges(); - expect(component.displayText).toBe('LIBRARY.ROLE.COLLABORATOR'); + expect(value).toBe('LIBRARY.ROLE.COLLABORATOR'); }); it('should render Contributor', () => { component.context = { row: { node: { entry: { role: 'SiteContributor' } } } }; + + let value = ''; + component.displayText$.subscribe((val) => value = val); + fixture.detectChanges(); - expect(component.displayText).toBe('LIBRARY.ROLE.CONTRIBUTOR'); + expect(value).toBe('LIBRARY.ROLE.CONTRIBUTOR'); }); it('should render Consumer', () => { component.context = { row: { node: { entry: { role: 'SiteConsumer' } } } }; + + let value = ''; + component.displayText$.subscribe((val) => value = val); + fixture.detectChanges(); - expect(component.displayText).toBe('LIBRARY.ROLE.CONSUMER'); + expect(value).toBe('LIBRARY.ROLE.CONSUMER'); }); it('should not render text for unknown', () => { component.context = { row: { node: { entry: { role: 'ROLE' } } } }; + + let value = ''; + component.displayText$.subscribe((val) => value = val); + fixture.detectChanges(); - expect(component.displayText).toBe(''); + expect(value).toBe(''); }); }); diff --git a/lib/content-services/document-list/components/library-role-column/library-role-column.component.ts b/lib/content-services/document-list/components/library-role-column/library-role-column.component.ts index 4381466762..39817101ce 100644 --- a/lib/content-services/document-list/components/library-role-column/library-role-column.component.ts +++ b/lib/content-services/document-list/components/library-role-column/library-role-column.component.ts @@ -15,44 +15,84 @@ * limitations under the License. */ -import { Component, OnInit, Input } from '@angular/core'; +import { + Component, + OnInit, + Input, + ChangeDetectionStrategy, + ViewEncapsulation, + OnDestroy +} from '@angular/core'; +import { Subscription, BehaviorSubject } from 'rxjs'; +import { AlfrescoApiService } from '@alfresco/adf-core'; +import { Node, SiteEntry, Site } from '@alfresco/js-api'; +import { ShareDataRow } from '../../data/share-data-row.model'; @Component({ - selector: 'adf-library-role-column', - template: ` - - {{ displayText | translate }} - - `, - host: { class: 'adf-library-role-column' } + selector: 'adf-library-role-column', + template: ` + + {{ (displayText$ | async) | translate }} + + `, + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, + host: { class: 'adf-library-role-column' } }) -export class LibraryRoleColumnComponent implements OnInit { - @Input() - context: any; +export class LibraryRoleColumnComponent implements OnInit, OnDestroy { + @Input() + context: any; - displayText: string; + displayText$ = new BehaviorSubject(''); - ngOnInit() { - const node = this.context.row.node; - if (node && node.entry) { - const role: string = node.entry.role; - switch (role) { - case 'SiteManager': - this.displayText = 'LIBRARY.ROLE.MANAGER'; - break; - case 'SiteCollaborator': - this.displayText = 'LIBRARY.ROLE.COLLABORATOR'; - break; - case 'SiteContributor': - this.displayText = 'LIBRARY.ROLE.CONTRIBUTOR'; - break; - case 'SiteConsumer': - this.displayText = 'LIBRARY.ROLE.CONSUMER'; - break; - default: - this.displayText = ''; - break; - } + private sub: Subscription; + + constructor(private api: AlfrescoApiService) {} + + ngOnInit() { + this.updateValue(); + + this.sub = this.api.nodeUpdated.subscribe((node: Node) => { + const row: ShareDataRow = this.context.row; + if (row) { + const { entry } = row.node; + + if (entry === node) { + row.node = { entry }; + this.updateValue(); + } + } + }); + } + + protected updateValue() { + const node: SiteEntry = this.context.row.node; + if (node && node.entry) { + const role: string = node.entry.role; + switch (role) { + case Site.RoleEnum.SiteManager: + this.displayText$.next('LIBRARY.ROLE.MANAGER'); + break; + case Site.RoleEnum.SiteCollaborator: + this.displayText$.next('LIBRARY.ROLE.COLLABORATOR'); + break; + case Site.RoleEnum.SiteContributor: + this.displayText$.next('LIBRARY.ROLE.CONTRIBUTOR'); + break; + case Site.RoleEnum.SiteConsumer: + this.displayText$.next('LIBRARY.ROLE.CONSUMER'); + break; + default: + this.displayText$.next(''); + break; + } + } + } + + ngOnDestroy() { + if (this.sub) { + this.sub.unsubscribe(); + this.sub = null; + } } - } } diff --git a/lib/content-services/document-list/components/library-status-column/library-status-column.component.ts b/lib/content-services/document-list/components/library-status-column/library-status-column.component.ts index 77744e55ad..579d5aa62a 100644 --- a/lib/content-services/document-list/components/library-status-column/library-status-column.component.ts +++ b/lib/content-services/document-list/components/library-status-column/library-status-column.component.ts @@ -15,42 +15,73 @@ * limitations under the License. */ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnInit, OnDestroy } from '@angular/core'; +import { AlfrescoApiService } from '@alfresco/adf-core'; +import { Subscription, BehaviorSubject } from 'rxjs'; +import { Node, Site, SiteEntry } from '@alfresco/js-api'; +import { ShareDataRow } from '../../data/share-data-row.model'; @Component({ - selector: 'adf-library-status-column', - template: ` - - {{ displayText | translate }} - - `, - host: { class: 'adf-library-status-column' } + selector: 'adf-library-status-column', + template: ` + + {{ (displayText$ | async) | translate }} + + `, + host: { class: 'adf-library-status-column' } }) -export class LibraryStatusColumnComponent implements OnInit { - @Input() - context: any; +export class LibraryStatusColumnComponent implements OnInit, OnDestroy { + @Input() + context: any; - displayText: string; + displayText$ = new BehaviorSubject(''); - ngOnInit() { - const node = this.context.row.node; - if (node && node.entry) { - const visibility: string = node.entry.visibility; + private sub: Subscription; - switch (visibility.toUpperCase()) { - case 'PUBLIC': - this.displayText = 'LIBRARY.VISIBILITY.PUBLIC'; - break; - case 'PRIVATE': - this.displayText = 'LIBRARY.VISIBILITY.PRIVATE'; - break; - case 'MODERATED': - this.displayText = 'LIBRARY.VISIBILITY.MODERATED'; - break; - default: - this.displayText = 'UNKNOWN'; - break; - } + constructor(private api: AlfrescoApiService) {} + + ngOnInit() { + this.updateValue(); + + this.sub = this.api.nodeUpdated.subscribe((node: Node) => { + const row: ShareDataRow = this.context.row; + if (row) { + const { entry } = row.node; + + if (entry === node) { + row.node = { entry }; + this.updateValue(); + } + } + }); + } + + protected updateValue() { + const node: SiteEntry = this.context.row.node; + if (node && node.entry) { + const visibility: string = node.entry.visibility; + + switch (visibility) { + case Site.VisibilityEnum.PUBLIC: + this.displayText$.next('LIBRARY.VISIBILITY.PUBLIC'); + break; + case Site.VisibilityEnum.PRIVATE: + this.displayText$.next('LIBRARY.VISIBILITY.PRIVATE'); + break; + case Site.VisibilityEnum.MODERATED: + this.displayText$.next('LIBRARY.VISIBILITY.MODERATED'); + break; + default: + this.displayText$.next('UNKNOWN'); + break; + } + } + } + + ngOnDestroy() { + if (this.sub) { + this.sub.unsubscribe(); + this.sub = null; + } } - } } diff --git a/lib/content-services/document-list/components/name-column/name-column.component.ts b/lib/content-services/document-list/components/name-column/name-column.component.ts index f2ae11f229..824e3cb0b6 100644 --- a/lib/content-services/document-list/components/name-column/name-column.component.ts +++ b/lib/content-services/document-list/components/name-column/name-column.component.ts @@ -16,50 +16,81 @@ */ import { - Component, - Input, - OnInit, - ChangeDetectionStrategy, - ViewEncapsulation, - ElementRef + Component, + Input, + OnInit, + ChangeDetectionStrategy, + ViewEncapsulation, + ElementRef, + OnDestroy } from '@angular/core'; import { NodeEntry } from '@alfresco/js-api'; +import { BehaviorSubject, Subscription } from 'rxjs'; +import { AlfrescoApiService } from '@alfresco/adf-core'; +import { Node } from '@alfresco/js-api'; +import { ShareDataRow } from '../../data/share-data-row.model'; @Component({ - selector: 'adf-name-column', - template: ` - - {{ displayText }} - - `, - changeDetection: ChangeDetectionStrategy.OnPush, - encapsulation: ViewEncapsulation.None, - host: { class: 'adf-datatable-cell adf-datatable-link adf-name-column' } + selector: 'adf-name-column', + template: ` + + {{ displayText$ | async }} + + `, + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, + host: { class: 'adf-datatable-cell adf-datatable-link adf-name-column' } }) -export class NameColumnComponent implements OnInit { - @Input() - context: any; +export class NameColumnComponent implements OnInit, OnDestroy { + @Input() + context: any; - displayText: string; - node: NodeEntry; + displayText$ = new BehaviorSubject(''); + node: NodeEntry; - constructor(private element: ElementRef) {} + private sub: Subscription; - ngOnInit() { - this.node = this.context.row.node; - if (this.node && this.node.entry) { - this.displayText = this.node.entry.name || this.node.entry.id; + constructor(private element: ElementRef, private alfrescoApiService: AlfrescoApiService) {} + + ngOnInit() { + this.updateValue(); + + this.sub = this.alfrescoApiService.nodeUpdated.subscribe((node: Node) => { + const row: ShareDataRow = this.context.row; + if (row) { + const { entry } = row.node; + + if (entry === node) { + row.node = { entry }; + this.updateValue(); + } + } + }); } - } - onClick() { - this.element.nativeElement.dispatchEvent( - new CustomEvent('name-click', { - bubbles: true, - detail: { - node: this.node + protected updateValue() { + this.node = this.context.row.node; + + if (this.node && this.node.entry) { + this.displayText$.next(this.node.entry.name || this.node.entry.id); } - }) - ); - } + } + + onClick() { + this.element.nativeElement.dispatchEvent( + new CustomEvent('name-click', { + bubbles: true, + detail: { + node: this.node + } + }) + ); + } + + ngOnDestroy() { + if (this.sub) { + this.sub.unsubscribe(); + this.sub = null; + } + } } diff --git a/lib/content-services/document-list/data/share-data-row.model.ts b/lib/content-services/document-list/data/share-data-row.model.ts index b02a99ef71..7a964302d1 100644 --- a/lib/content-services/document-list/data/share-data-row.model.ts +++ b/lib/content-services/document-list/data/share-data-row.model.ts @@ -32,6 +32,11 @@ export class ShareDataRow implements DataRow { return this.obj; } + set node(value: NodeEntry) { + this.obj = value; + this.cache = {}; + } + constructor(private obj: NodeEntry, private contentService: ContentService, private permissionsStyle: PermissionStyleModel[], diff --git a/lib/core/datatable/components/datatable/datatable-cell.component.spec.ts b/lib/core/datatable/components/datatable/datatable-cell.component.spec.ts index 3e685769c0..603f76c2ae 100644 --- a/lib/core/datatable/components/datatable/datatable-cell.component.spec.ts +++ b/lib/core/datatable/components/datatable/datatable-cell.component.spec.ts @@ -16,15 +16,18 @@ */ import { DateCellComponent } from './date-cell.component'; +import { Subject } from 'rxjs'; describe('DataTableCellComponent', () => { it('should use medium format by default', () => { - const component = new DateCellComponent(null); + const component = new DateCellComponent(null, null); expect(component.format).toBe('medium'); }); it('should use column format', () => { - const component = new DateCellComponent(null); + const component = new DateCellComponent(null, { + nodeUpdated: new Subject() + }); component.column = { key: 'created', type: 'date', diff --git a/lib/core/datatable/components/datatable/datatable-cell.component.ts b/lib/core/datatable/components/datatable/datatable-cell.component.ts index bc62199a0b..3308688ad6 100644 --- a/lib/core/datatable/components/datatable/datatable-cell.component.ts +++ b/lib/core/datatable/components/datatable/datatable-cell.component.ts @@ -15,23 +15,38 @@ * limitations under the License. */ -import { ChangeDetectionStrategy, Component, Input, OnInit, ViewEncapsulation } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + Input, + OnInit, + ViewEncapsulation, + OnDestroy +} from '@angular/core'; import { DataColumn } from '../../data/data-column.model'; import { DataRow } from '../../data/data-row.model'; import { DataTableAdapter } from '../../data/datatable-adapter'; +import { AlfrescoApiService } from '../../../services/alfresco-api.service'; +import { Subscription, BehaviorSubject } from 'rxjs'; +import { Node } from '@alfresco/js-api'; @Component({ selector: 'adf-datatable-cell', changeDetection: ChangeDetectionStrategy.OnPush, template: ` - {{value}} - `, + {{ value$ | async }} + + `, encapsulation: ViewEncapsulation.None, host: { class: 'adf-datatable-cell' } }) -export class DataTableCellComponent implements OnInit { - +export class DataTableCellComponent implements OnInit, OnDestroy { @Input() data: DataTableAdapter; @@ -41,20 +56,46 @@ export class DataTableCellComponent implements OnInit { @Input() row: DataRow; - @Input() - value: any; + value$ = new BehaviorSubject(''); @Input() tooltip: string; + private sub: Subscription; + + constructor(protected alfrescoApiService: AlfrescoApiService) {} + ngOnInit() { - if (!this.value && this.column && this.column.key && this.row && this.data) { - this.value = this.data.getValue(this.row, this.column); + this.updateValue(); + + this.sub = this.alfrescoApiService.nodeUpdated.subscribe((node: Node) => { + if (this.row) { + const { entry } = this.row['node']; + + if (entry === node) { + this.row['node'] = { entry }; + this.updateValue(); + } + } + }); + } + + protected updateValue() { + if (this.column && this.column.key && this.row && this.data) { + const value = this.data.getValue(this.row, this.column); + + this.value$.next(value); if (!this.tooltip) { - this.tooltip = this.value; + this.tooltip = value; } } } + ngOnDestroy() { + if (this.sub) { + this.sub.unsubscribe(); + this.sub = null; + } + } } diff --git a/lib/core/datatable/components/datatable/date-cell.component.ts b/lib/core/datatable/components/datatable/date-cell.component.ts index 89e32d6262..dc7f68fb53 100644 --- a/lib/core/datatable/components/datatable/date-cell.component.ts +++ b/lib/core/datatable/components/datatable/date-cell.component.ts @@ -21,19 +21,27 @@ import { UserPreferencesService, UserPreferenceValues } from '../../../services/user-preferences.service'; +import { AlfrescoApiService } from '../../../services/alfresco-api.service'; @Component({ selector: 'adf-date-cell', template: ` - - {{ value | adfTimeAgo: currentLocale }} + + {{ value$ | async | adfTimeAgo: currentLocale }} - - {{ value | date:format }} + + {{ value$ | async | date: format }} `, @@ -41,7 +49,7 @@ import { host: { class: 'adf-date-cell' } }) export class DateCellComponent extends DataTableCellComponent { - currentLocale; + currentLocale: string; get format(): string { if (this.column) { @@ -50,8 +58,11 @@ export class DateCellComponent extends DataTableCellComponent { return 'medium'; } - constructor(userPreferenceService: UserPreferencesService) { - super(); + constructor( + userPreferenceService: UserPreferencesService, + alfrescoApiService: AlfrescoApiService + ) { + super(alfrescoApiService); if (userPreferenceService) { userPreferenceService diff --git a/lib/core/datatable/components/datatable/filesize-cell.component.ts b/lib/core/datatable/components/datatable/filesize-cell.component.ts index 803463b69e..f263b750ea 100644 --- a/lib/core/datatable/components/datatable/filesize-cell.component.ts +++ b/lib/core/datatable/components/datatable/filesize-cell.component.ts @@ -17,15 +17,24 @@ import { Component, ViewEncapsulation } from '@angular/core'; import { DataTableCellComponent } from './datatable-cell.component'; +import { AlfrescoApiService } from '../../../services/alfresco-api.service'; @Component({ selector: 'adf-filesize-cell', template: ` - {{ value | adfFileSize }} + {{ value$ | async | adfFileSize }} `, encapsulation: ViewEncapsulation.None, host: { class: 'adf-filesize-cell' } }) -export class FileSizeCellComponent extends DataTableCellComponent {} +export class FileSizeCellComponent extends DataTableCellComponent { + constructor(alfrescoApiService: AlfrescoApiService) { + super(alfrescoApiService); + } +} diff --git a/lib/core/datatable/components/datatable/location-cell.component.spec.ts b/lib/core/datatable/components/datatable/location-cell.component.spec.ts index a866606096..1bf3e69f3c 100644 --- a/lib/core/datatable/components/datatable/location-cell.component.spec.ts +++ b/lib/core/datatable/components/datatable/location-cell.component.spec.ts @@ -69,12 +69,6 @@ describe('LocationCellComponent', () => { fixture.destroy(); }); - it('should set displayText', () => { - fixture.detectChanges(); - - expect(component.displayText).toBe('location'); - }); - it('should set tooltip', () => { fixture.detectChanges(); @@ -87,24 +81,32 @@ describe('LocationCellComponent', () => { expect(component.link).toEqual([ columnData.format , rowData.path.elements[2].id ]); }); - it('should not setup cell when path has no data', () => { + it('should not setup cell when path has no data', (done) => { rowData.path = {}; fixture.detectChanges(); - expect(component.displayText).toBe(''); expect(component.tooltip).toBeUndefined(); expect(component.link).toEqual([]); + component.value$.subscribe((value) => { + expect(value).toBe(''); + done(); + }); + }); - it('should not setup cell when path is missing required properties', () => { + it('should not setup cell when path is missing required properties', (done) => { rowData.path = { someProp: '' }; fixture.detectChanges(); - expect(component.displayText).toBe(''); expect(component.tooltip).toBeUndefined(); expect(component.link).toEqual([]); + + component.value$.subscribe((value) => { + expect(value).toBe(''); + done(); + }); }); it('should not setup cell when path data is missing one of the property', () => { @@ -112,9 +114,15 @@ describe('LocationCellComponent', () => { name: 'some-name' }; + let value = ''; + + component.value$.subscribe((val) => { + value = val; + }); + fixture.detectChanges(); - expect(component.displayText).toBe(''); + expect(value).toBe(''); expect(component.tooltip).toBeUndefined(); expect(component.link).toEqual([]); @@ -124,7 +132,7 @@ describe('LocationCellComponent', () => { fixture.detectChanges(); - expect(component.displayText).toBe(''); + expect(value).toBe(''); expect(component.tooltip).toBeUndefined(); expect(component.link).toEqual([]); }); diff --git a/lib/core/datatable/components/datatable/location-cell.component.ts b/lib/core/datatable/components/datatable/location-cell.component.ts index 12c6603a42..1a62782a1e 100644 --- a/lib/core/datatable/components/datatable/location-cell.component.ts +++ b/lib/core/datatable/components/datatable/location-cell.component.ts @@ -15,9 +15,16 @@ * limitations under the License. */ -import { ChangeDetectionStrategy, Component, Input, OnInit, ViewEncapsulation } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + Input, + OnInit, + ViewEncapsulation +} from '@angular/core'; import { PathInfoEntity } from '@alfresco/js-api'; import { DataTableCellComponent } from './datatable-cell.component'; +import { AlfrescoApiService } from '../../../services/alfresco-api.service'; @Component({ selector: 'adf-location-cell', @@ -25,7 +32,7 @@ import { DataTableCellComponent } from './datatable-cell.component'; template: ` - {{ displayText }} + {{ value$ | async }} `, @@ -33,28 +40,30 @@ import { DataTableCellComponent } from './datatable-cell.component'; host: { class: 'adf-location-cell' } }) export class LocationCellComponent extends DataTableCellComponent implements OnInit { - @Input() link: any[]; - @Input() - displayText: string = ''; + constructor(alfrescoApiService: AlfrescoApiService) { + super(alfrescoApiService); + } /** @override */ ngOnInit() { - if (!this.value && this.column && this.column.key && this.row && this.data) { - const path: PathInfoEntity = this.data.getValue(this.row, this.column); + if (this.column && this.column.key && this.row && this.data) { + const path: PathInfoEntity = this.data.getValue( + this.row, + this.column + ); if (path && path.name && path.elements) { - this.value = path; - this.displayText = path.name.split('/').pop(); + this.value$.next(path.name.split('/').pop()); if (!this.tooltip) { this.tooltip = path.name; } const parent = path.elements[path.elements.length - 1]; - this.link = [ this.column.format, parent.id ]; + this.link = [this.column.format, parent.id]; } } } diff --git a/lib/core/services/automation.service.ts b/lib/core/services/automation.service.ts new file mode 100644 index 0000000000..da6478daf8 --- /dev/null +++ b/lib/core/services/automation.service.ts @@ -0,0 +1,37 @@ +/*! + * @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 { Injectable } from '@angular/core'; +import { AppConfigService } from '../app-config/app-config.service'; + +@Injectable({ + providedIn: 'root' +}) +export class CoreAutomationService { + constructor(private appConfigService: AppConfigService) { + } + + setup() { + const adfProxy = window['adf'] || {}; + + adfProxy.setConfigField = (field: string, value: string) => { + this.appConfigService.config[field] = JSON.parse(value); + }; + + window['adf'] = adfProxy; + } +} diff --git a/lib/core/services/public-api.ts b/lib/core/services/public-api.ts index a9b659c75e..19f310b7e5 100644 --- a/lib/core/services/public-api.ts +++ b/lib/core/services/public-api.ts @@ -53,3 +53,4 @@ export * from './login-dialog.service'; export * from './external-alfresco-api.service'; export * from './jwt-helper.service'; export * from './download-zip.service'; +export * from './automation.service';