mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[AAE-10777] Move services from Core in Content the right place (#8242)
Clean core services and directive
This commit is contained in:
@@ -18,10 +18,8 @@
|
||||
import { Meta, moduleMetadata, Story } from '@storybook/angular';
|
||||
import { AboutComponent } from './about.component';
|
||||
import { AboutModule } from './about.module';
|
||||
import { DiscoveryApiService } from '../services';
|
||||
import { AuthenticationService } from '../auth/services/authentication.service';
|
||||
import { AuthenticationMock } from '../auth/mock/authentication.service.mock';
|
||||
import { DiscoveryApiServiceMock } from '../mock/discovery-api.service.mock';
|
||||
import { AppExtensionService, AppExtensionServiceMock } from '@alfresco/adf-extensions';
|
||||
import { AppConfigService } from '../app-config/app-config.service';
|
||||
import { AppConfigServiceMock } from '../common/mock/app-config.service.mock';
|
||||
@@ -35,7 +33,6 @@ export default {
|
||||
imports: [CoreStoryModule, AboutModule],
|
||||
providers: [
|
||||
{ provide: AuthenticationService, useClass: AuthenticationMock },
|
||||
{ provide: DiscoveryApiService, useClass: DiscoveryApiServiceMock },
|
||||
{ provide: AppExtensionService, useClass: AppExtensionServiceMock },
|
||||
{ provide: AppConfigService, useClass: AppConfigServiceMock }
|
||||
]
|
||||
|
@@ -23,15 +23,13 @@ import { AuthGuardSsoRoleService } from './auth-guard-sso-role.service';
|
||||
import { JwtHelperService } from '../services/jwt-helper.service';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { UserAccessService } from '../../services/user-access.service';
|
||||
import { UserContentAccessService } from '../../services/user-content-access.service';
|
||||
import { UserAccessService } from '../services/user-access.service';
|
||||
|
||||
describe('Auth Guard SSO role service', () => {
|
||||
|
||||
let authGuard: AuthGuardSsoRoleService;
|
||||
let jwtHelperService: JwtHelperService;
|
||||
let routerService: Router;
|
||||
let userContentAccessService: UserContentAccessService;
|
||||
let userAccessService: UserAccessService;
|
||||
|
||||
setupTestBed({
|
||||
@@ -46,7 +44,6 @@ describe('Auth Guard SSO role service', () => {
|
||||
authGuard = TestBed.inject(AuthGuardSsoRoleService);
|
||||
jwtHelperService = TestBed.inject(JwtHelperService);
|
||||
routerService = TestBed.inject(Router);
|
||||
userContentAccessService = TestBed.inject(UserContentAccessService);
|
||||
userAccessService = TestBed.inject(UserAccessService);
|
||||
userAccessService.resetAccess();
|
||||
});
|
||||
@@ -178,43 +175,6 @@ describe('Auth Guard SSO role service', () => {
|
||||
expect(materialDialog.closeAll).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('Content Admin', () => {
|
||||
|
||||
it('Should give access to a content section (ALFRESCO_ADMINISTRATORS) when the user has content admin capability', async () => {
|
||||
spyOn(userContentAccessService, 'isCurrentUserAdmin').and.returnValue(Promise.resolve(true));
|
||||
|
||||
spyUserAccess([], {});
|
||||
|
||||
const router: ActivatedRouteSnapshot = new ActivatedRouteSnapshot();
|
||||
router.data = { roles: ['ALFRESCO_ADMINISTRATORS'] };
|
||||
|
||||
expect(await authGuard.canActivate(router)).toBe(true);
|
||||
});
|
||||
|
||||
it('Should not give access to a content section (ALFRESCO_ADMINISTRATORS) when the user does not have content admin capability', async () => {
|
||||
spyOn(userContentAccessService, 'isCurrentUserAdmin').and.returnValue(Promise.resolve(false));
|
||||
|
||||
spyUserAccess([], {});
|
||||
|
||||
const router: ActivatedRouteSnapshot = new ActivatedRouteSnapshot();
|
||||
router.data = { roles: ['ALFRESCO_ADMINISTRATORS'] };
|
||||
|
||||
expect(await authGuard.canActivate(router)).toBe(false);
|
||||
});
|
||||
|
||||
it('Should not call the service to check if the user has content admin capability when the roles do not contain ALFRESCO_ADMINISTRATORS', async () => {
|
||||
const isCurrentAdminSpy = spyOn(userContentAccessService, 'isCurrentUserAdmin').and.stub();
|
||||
spyUserAccess([], {});
|
||||
|
||||
const router: ActivatedRouteSnapshot = new ActivatedRouteSnapshot();
|
||||
router.data = { roles: ['fakeRole'] };
|
||||
|
||||
await authGuard.canActivate(router);
|
||||
|
||||
expect(isCurrentAdminSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Excluded Roles', () => {
|
||||
it('Should canActivate be false when the user has one of the excluded roles', async () => {
|
||||
spyUserAccess(['MOCK_USER_ROLE'], {});
|
||||
@@ -234,24 +194,5 @@ describe('Auth Guard SSO role service', () => {
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Should canActivate be true when the user has none of the excluded role and is not a content admin', async () => {
|
||||
spyOn(userContentAccessService, 'isCurrentUserAdmin').and.returnValue(Promise.resolve(false));
|
||||
spyUserAccess(['MOCK_USER_ROLE'], {});
|
||||
|
||||
const router: ActivatedRouteSnapshot = new ActivatedRouteSnapshot();
|
||||
router.data = { roles: ['MOCK_USER_ROLE'], excludedRoles: ['ALFRESCO_ADMINISTRATORS'] };
|
||||
|
||||
expect(await authGuard.canActivate(router)).toBe(true);
|
||||
});
|
||||
|
||||
it('Should canActivate be false if the user is a content admin but has one of the excluded roles', async () => {
|
||||
spyOn(userContentAccessService, 'isCurrentUserAdmin').and.returnValue(Promise.resolve(false));
|
||||
spyUserAccess(['MOCK_USER_ROLE'], {});
|
||||
|
||||
const router: ActivatedRouteSnapshot = new ActivatedRouteSnapshot();
|
||||
router.data = { roles: ['ALFRESCO_ADMINISTRATORS'], excludedRoles: ['MOCK_USER_ROLE'] };
|
||||
|
||||
expect(await authGuard.canActivate(router)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -18,9 +18,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ContentGroups } from '../../services/people-content.service';
|
||||
import { UserAccessService } from '../../services/user-access.service';
|
||||
import { UserContentAccessService } from '../../services/user-content-access.service';
|
||||
import { UserAccessService } from '../services/user-access.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@@ -28,8 +26,7 @@ import { UserContentAccessService } from '../../services/user-content-access.ser
|
||||
export class AuthGuardSsoRoleService implements CanActivate {
|
||||
constructor(private userAccessService: UserAccessService,
|
||||
private router: Router,
|
||||
private dialog: MatDialog,
|
||||
private userContentAccessService: UserContentAccessService) {
|
||||
private dialog: MatDialog) {
|
||||
}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||
@@ -74,14 +71,7 @@ export class AuthGuardSsoRoleService implements CanActivate {
|
||||
}
|
||||
|
||||
private async hasRoles(roles: string[] = []): Promise<boolean> {
|
||||
if (this.containsAlfrescoAdminRole(roles)) {
|
||||
return await this.userContentAccessService.isCurrentUserAdmin() || this.userAccessService.hasGlobalAccess(roles);
|
||||
}
|
||||
return this.userAccessService.hasGlobalAccess(roles);
|
||||
}
|
||||
|
||||
private containsAlfrescoAdminRole(roles: string []): boolean {
|
||||
return roles.includes(ContentGroups.ALFRESCO_ADMINISTRATORS);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -29,6 +29,7 @@ export * from './services/identity-user.service';
|
||||
export * from './services/identity-group.service';
|
||||
export * from './services/jwt-helper.service';
|
||||
export * from './services/oauth2.service';
|
||||
export * from './services/user-access.service';
|
||||
|
||||
export * from './interfaces/identity-user.service.interface';
|
||||
export * from './interfaces/identity-group.interface';
|
||||
@@ -41,4 +42,7 @@ export * from './models/identity-group.model';
|
||||
export * from './models/identity-user.model';
|
||||
export * from './models/identity-role.model';
|
||||
|
||||
export * from './models/user-access.model';
|
||||
export * from './models/application-access.model';
|
||||
|
||||
|
||||
|
@@ -15,14 +15,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CoreTestingModule, setupTestBed } from '../testing';
|
||||
import { CoreTestingModule, setupTestBed } from '../../testing';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { UserAccessService } from './user-access.service';
|
||||
import { JwtHelperService } from '../auth/services/jwt-helper.service';
|
||||
import { OAuth2Service } from '../auth/services/oauth2.service';
|
||||
import { JwtHelperService } from './jwt-helper.service';
|
||||
import { OAuth2Service } from './oauth2.service';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { userAccessMock } from '../mock/user-access.mock';
|
||||
import { AppConfigService } from '../app-config';
|
||||
import { userAccessMock } from '../../mock/user-access.mock';
|
||||
import { AppConfigService } from '../../app-config';
|
||||
|
||||
describe('UserAccessService', () => {
|
||||
let userAccessService: UserAccessService;
|
@@ -16,11 +16,11 @@
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { JwtHelperService } from '../auth/services/jwt-helper.service';
|
||||
import { JwtHelperService } from './jwt-helper.service';
|
||||
import { ApplicationAccessModel } from '../models/application-access.model';
|
||||
import { UserAccessModel } from '../models/user-access.model';
|
||||
import { AppConfigService } from '../app-config/app-config.service';
|
||||
import { OAuth2Service } from '../auth/services/oauth2.service';
|
||||
import { AppConfigService } from '../../app-config/app-config.service';
|
||||
import { OAuth2Service } from './oauth2.service';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { of } from 'rxjs';
|
||||
|
@@ -20,7 +20,7 @@ import { CoreStoryModule } from '../../testing/core.story.module';
|
||||
import { CommentListComponent } from './comment-list.component';
|
||||
import { CommentsModule } from '../comments.module';
|
||||
import { commentsTaskData, commentsNodeData } from '../mocks/comments.stories.mock';
|
||||
import { EcmUserService } from '../../services';
|
||||
import { CommentListServiceMock } from './mocks/comment-list.service.mock';
|
||||
|
||||
export default {
|
||||
component: CommentListComponent,
|
||||
@@ -29,7 +29,7 @@ export default {
|
||||
moduleMetadata({
|
||||
imports: [CoreStoryModule, CommentsModule],
|
||||
providers: [
|
||||
{ provide: EcmUserService, useValue: { getUserProfileImage: () => '../assets/images/logo.png' } }
|
||||
{ provide: CommentListServiceMock, useValue: { getUserProfileImage: () => '../assets/images/logo.png' } }
|
||||
]
|
||||
})
|
||||
],
|
||||
|
@@ -16,14 +16,13 @@
|
||||
*/
|
||||
|
||||
import { CommentModel } from '../../../models/comment.model';
|
||||
import { UserProcessModel } from '../../../models/user-process.model';
|
||||
|
||||
export const testUser = new UserProcessModel({
|
||||
export const testUser = {
|
||||
id: '1',
|
||||
firstName: 'Test',
|
||||
lastName: 'User',
|
||||
email: 'tu@domain.com'
|
||||
});
|
||||
};
|
||||
|
||||
export const mockCommentOne = new CommentModel({
|
||||
id: 1,
|
||||
|
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
|
||||
import { Meta, moduleMetadata, Story } from '@storybook/angular';
|
||||
import { EcmUserService } from '../services';
|
||||
import { CoreStoryModule } from '../testing/core.story.module';
|
||||
import { CommentsComponent } from './comments.component';
|
||||
import { CommentsModule } from './comments.module';
|
||||
@@ -31,7 +30,7 @@ export default {
|
||||
moduleMetadata({
|
||||
imports: [CoreStoryModule, CommentsModule],
|
||||
providers: [
|
||||
{ provide: EcmUserService, useValue: { getUserProfileImage: () => '../assets/images/logo.png' } },
|
||||
{ provide: CommentsServiceStoriesMock, useValue: { getUserProfileImage: () => '../assets/images/logo.png' } },
|
||||
{ provide: ADF_COMMENTS_SERVICE, useClass: CommentsServiceStoriesMock }
|
||||
]
|
||||
})
|
||||
|
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CommentModel, EcmUserModel } from '../../models';
|
||||
import { CommentModel, User } from '../../models';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { CommentsService } from '../interfaces';
|
||||
|
||||
@@ -57,7 +57,7 @@ export const commentsResponseMock = {
|
||||
id: 'hruser',
|
||||
email: 'test',
|
||||
isAdmin: () => false
|
||||
} as EcmUserModel,
|
||||
} as User,
|
||||
isSelected: false
|
||||
} as CommentModel,
|
||||
{
|
||||
@@ -84,7 +84,7 @@ export const commentsResponseMock = {
|
||||
id: 'hruser',
|
||||
email: 'test',
|
||||
isAdmin: () => false
|
||||
} as EcmUserModel,
|
||||
} as User,
|
||||
isSelected: false
|
||||
} as CommentModel,
|
||||
{
|
||||
@@ -111,7 +111,7 @@ export const commentsResponseMock = {
|
||||
id: 'hruser',
|
||||
email: 'test',
|
||||
isAdmin: () => false
|
||||
} as EcmUserModel,
|
||||
} as User,
|
||||
isSelected: false
|
||||
} as CommentModel
|
||||
]),
|
||||
@@ -139,7 +139,7 @@ export const commentsResponseMock = {
|
||||
id: 'hruser',
|
||||
email: 'test',
|
||||
isAdmin: () => false
|
||||
} as EcmUserModel,
|
||||
} as User,
|
||||
isSelected: false
|
||||
} as CommentModel)
|
||||
};
|
||||
|
@@ -15,10 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CommentModel, EcmUserModel } from '../../models';
|
||||
import { CommentModel } from '../../models';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { CommentsService } from '../interfaces';
|
||||
import { testUser } from './comments.stories.mock';
|
||||
import { UserLike } from '../../pipes/user-like.interface';
|
||||
|
||||
export class CommentsServiceStoriesMock implements Partial<CommentsService> {
|
||||
|
||||
@@ -58,7 +59,7 @@ export const commentsResponseMock = {
|
||||
id: 'hruser',
|
||||
email: 'test',
|
||||
isAdmin: () => false
|
||||
} as EcmUserModel,
|
||||
} as UserLike,
|
||||
isSelected: false
|
||||
} as CommentModel,
|
||||
{
|
||||
@@ -85,7 +86,7 @@ export const commentsResponseMock = {
|
||||
id: 'hruser',
|
||||
email: 'test',
|
||||
isAdmin: () => false
|
||||
} as EcmUserModel,
|
||||
} as UserLike,
|
||||
isSelected: false
|
||||
} as CommentModel,
|
||||
{
|
||||
@@ -112,7 +113,7 @@ export const commentsResponseMock = {
|
||||
id: 'hruser',
|
||||
email: 'test',
|
||||
isAdmin: () => false
|
||||
} as EcmUserModel,
|
||||
} as UserLike,
|
||||
isSelected: false
|
||||
} as CommentModel
|
||||
]),
|
||||
|
@@ -15,9 +15,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CommentModel, EcmCompanyModel, EcmUserModel } from '../../models';
|
||||
import { CommentModel } from '../../models';
|
||||
|
||||
const fakeCompany: EcmCompanyModel = {
|
||||
const fakeCompany: any = {
|
||||
organization: '',
|
||||
address1: '',
|
||||
address2: '',
|
||||
@@ -30,7 +30,7 @@ const fakeCompany: EcmCompanyModel = {
|
||||
|
||||
export const getDateXMinutesAgo = (minutes: number) => new Date(new Date().getTime() - minutes * 60000);
|
||||
|
||||
const johnDoe: EcmUserModel = {
|
||||
const johnDoe: any = {
|
||||
id: '1',
|
||||
email: 'john.doe@alfresco.com',
|
||||
firstName: 'John',
|
||||
@@ -41,7 +41,7 @@ const johnDoe: EcmUserModel = {
|
||||
avatarId: '001'
|
||||
};
|
||||
|
||||
const janeEod: EcmUserModel = {
|
||||
const janeEod: any = {
|
||||
id: '2',
|
||||
email: 'jane.eod@alfresco.com',
|
||||
firstName: 'Jane',
|
||||
@@ -51,7 +51,7 @@ const janeEod: EcmUserModel = {
|
||||
isAdmin: undefined
|
||||
};
|
||||
|
||||
const robertSmith: EcmUserModel = {
|
||||
const robertSmith: any = {
|
||||
id: '3',
|
||||
email: 'robert.smith@alfresco.com',
|
||||
firstName: 'Robert',
|
||||
@@ -61,7 +61,7 @@ const robertSmith: EcmUserModel = {
|
||||
isAdmin: undefined
|
||||
};
|
||||
|
||||
export const testUser: EcmUserModel = {
|
||||
export const testUser: any = {
|
||||
id: '44',
|
||||
email: 'test.user@hyland.com',
|
||||
firstName: 'Test',
|
||||
|
@@ -25,6 +25,8 @@ export * from './services/highlight-transform.service';
|
||||
export * from './services/page-title.service';
|
||||
export * from './services/thumbnail.service';
|
||||
export * from './services/sort-by-category.service';
|
||||
export * from './services/download.service';
|
||||
export * from './services/url.service';
|
||||
|
||||
export * from './models/log-levels.model';
|
||||
export * from './models/user-info-mode.enum';
|
||||
|
@@ -52,11 +52,6 @@ describe('ThumbnailService', () => {
|
||||
expect(service.getMimeTypeIcon('x-unknown/yyy')).toContain('ft_ic_miscellaneous');
|
||||
});
|
||||
|
||||
it('should return the thumbnail URL for a content item', () => {
|
||||
spyOn(service['contentApi'], 'getDocumentThumbnailUrl').and.returnValue('/fake-thumbnail.png');
|
||||
expect(service.getDocumentThumbnailUrl('some-id')).toContain('/fake-thumbnail.png');
|
||||
});
|
||||
|
||||
it('should return the correct icon for a mht file', () => {
|
||||
expect(service.getMimeTypeIcon('multipart/related')).toContain('ft_ic_website.svg');
|
||||
});
|
||||
|
@@ -19,8 +19,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MatIconRegistry } from '@angular/material/icon';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { AlfrescoApiService } from '../../services/alfresco-api.service';
|
||||
import { ContentApi, NodeEntry } from '@alfresco/js-api';
|
||||
|
||||
const DEFAULT_ICON = './assets/images/ft_ic_miscellaneous.svg';
|
||||
|
||||
@@ -163,13 +161,7 @@ export class ThumbnailService {
|
||||
'multipart/related': './assets/images/ft_ic_website.svg'
|
||||
};
|
||||
|
||||
private _contentApi: ContentApi;
|
||||
get contentApi(): ContentApi {
|
||||
this._contentApi = this._contentApi ?? new ContentApi(this.apiService.getInstance());
|
||||
return this._contentApi;
|
||||
}
|
||||
|
||||
constructor(protected apiService: AlfrescoApiService, matIconRegistry: MatIconRegistry, sanitizer: DomSanitizer) {
|
||||
constructor(matIconRegistry: MatIconRegistry, sanitizer: DomSanitizer) {
|
||||
Object.keys(this.mimeTypeIcons).forEach((key) => {
|
||||
const url = sanitizer.bypassSecurityTrustResourceUrl(this.mimeTypeIcons[key]);
|
||||
|
||||
@@ -178,32 +170,6 @@ export class ThumbnailService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a thumbnail URL for the given document node.
|
||||
*
|
||||
* @param node Node or Node ID to get URL for.
|
||||
* @param attachment Toggles whether to retrieve content as an attachment for download
|
||||
* @param ticket Custom ticket to use for authentication
|
||||
* @returns URL string
|
||||
*/
|
||||
getDocumentThumbnailUrl(node: NodeEntry | string, attachment?: boolean, ticket?: string): string {
|
||||
let resultUrl: string;
|
||||
|
||||
if (node) {
|
||||
let nodeId: string;
|
||||
|
||||
if (typeof node === 'string') {
|
||||
nodeId = node;
|
||||
} else if (node.entry) {
|
||||
nodeId = node.entry.id;
|
||||
}
|
||||
|
||||
resultUrl = this.contentApi.getDocumentThumbnailUrl(nodeId, attachment, ticket);
|
||||
}
|
||||
|
||||
return resultUrl || DEFAULT_ICON;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a thumbnail URL for a MIME type.
|
||||
*
|
||||
|
@@ -43,7 +43,6 @@ import { NotificationHistoryModule } from './notifications/notification-history.
|
||||
import { BlankPageModule } from './blank-page/blank-page.module';
|
||||
|
||||
import { DirectiveModule } from './directives/directive.module';
|
||||
import { DownloadZipDialogModule } from './dialogs/download-zip/download-zip.dialog.module';
|
||||
import { PipeModule } from './pipes/pipe.module';
|
||||
|
||||
import { AlfrescoApiService } from './services/alfresco-api.service';
|
||||
@@ -86,7 +85,6 @@ const defaultConfig: Config = { useAngularBasedHttpClientInAlfrescoJs: false };
|
||||
CommonModule,
|
||||
IdentityUserInfoModule,
|
||||
DirectiveModule,
|
||||
DownloadZipDialogModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MaterialModule,
|
||||
@@ -124,7 +122,6 @@ const defaultConfig: Config = { useAngularBasedHttpClientInAlfrescoJs: false };
|
||||
PipeModule,
|
||||
CommonModule,
|
||||
DirectiveModule,
|
||||
DownloadZipDialogModule,
|
||||
ClipboardModule,
|
||||
FormsModule,
|
||||
IdentityUserInfoModule,
|
||||
|
@@ -22,7 +22,6 @@ import {
|
||||
OnInit, Optional,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import { PathInfoEntity } from '@alfresco/js-api';
|
||||
import { DataTableCellComponent } from '../datatable-cell/datatable-cell.component';
|
||||
import { DataTableService } from '../../services/datatable.service';
|
||||
|
||||
@@ -50,7 +49,7 @@ export class LocationCellComponent extends DataTableCellComponent implements OnI
|
||||
/** @override */
|
||||
ngOnInit() {
|
||||
if (this.column && this.column.key && this.row && this.data) {
|
||||
const path: PathInfoEntity = this.data.getValue(
|
||||
const path: any = this.data.getValue(
|
||||
this.row,
|
||||
this.column,
|
||||
this.resolverFn
|
||||
|
@@ -1,9 +0,0 @@
|
||||
<h1 matDialogTitle>{{ 'CORE.DIALOG.DOWNLOAD_ZIP.TITLE' | translate }}</h1>
|
||||
<div mat-dialog-content>
|
||||
<mat-progress-bar color="primary" mode="indeterminate"></mat-progress-bar>
|
||||
</div>
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-button color="primary" id="cancel-button" (click)="cancelDownload()">
|
||||
{{ 'CORE.DIALOG.DOWNLOAD_ZIP.ACTIONS.CANCEL' | translate }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
@@ -1,39 +0,0 @@
|
||||
/*!
|
||||
* @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 { NgModule } from '@angular/core';
|
||||
import { DownloadZipDialogComponent } from './download-zip.dialog';
|
||||
import { PipeModule } from '../../pipes/pipe.module';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@NgModule({
|
||||
declarations: [DownloadZipDialogComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
PipeModule,
|
||||
MatDialogModule,
|
||||
MatProgressBarModule,
|
||||
MatButtonModule,
|
||||
TranslateModule
|
||||
],
|
||||
exports: [DownloadZipDialogComponent]
|
||||
})
|
||||
export class DownloadZipDialogModule {}
|
@@ -1,3 +0,0 @@
|
||||
.adf-download-zip-dialog .mat-dialog-actions .mat-button-wrapper {
|
||||
text-transform: uppercase;
|
||||
}
|
@@ -1,160 +0,0 @@
|
||||
/*!
|
||||
* @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 { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { DownloadZipDialogComponent } from './download-zip.dialog';
|
||||
import { setupTestBed } from '../../testing/setup-test-bed';
|
||||
import { CoreTestingModule } from '../../testing/core.testing.module';
|
||||
import { DownloadZipService } from '../../services/download-zip.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
describe('DownloadZipDialogComponent', () => {
|
||||
|
||||
let fixture: ComponentFixture<DownloadZipDialogComponent>;
|
||||
let component: DownloadZipDialogComponent;
|
||||
let element: HTMLElement;
|
||||
let downloadZipService: DownloadZipService;
|
||||
const dialogRef = {
|
||||
close: jasmine.createSpy('close')
|
||||
};
|
||||
|
||||
const dataMock = {
|
||||
nodeIds: [
|
||||
'123'
|
||||
]
|
||||
};
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
],
|
||||
providers: [
|
||||
{ provide: MatDialogRef, useValue: dialogRef },
|
||||
{ provide: MAT_DIALOG_DATA, useValue: dataMock }
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
dialogRef.close.calls.reset();
|
||||
fixture = TestBed.createComponent(DownloadZipDialogComponent);
|
||||
downloadZipService = TestBed.inject(DownloadZipService);
|
||||
component = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should call downloadZip when it is not cancelled', () => {
|
||||
component.cancelled = false;
|
||||
spyOn(component, 'downloadZip');
|
||||
|
||||
component.ngOnInit();
|
||||
|
||||
expect(component.downloadZip).toHaveBeenCalledWith(['123']);
|
||||
});
|
||||
|
||||
it('should not call downloadZip when it is cancelled', () => {
|
||||
component.cancelled = true;
|
||||
spyOn(component, 'downloadZip');
|
||||
|
||||
component.ngOnInit();
|
||||
|
||||
expect(component.downloadZip).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not call downloadZip when it contains zero nodeIds', () => {
|
||||
component.data = {
|
||||
nodeIds: []
|
||||
};
|
||||
spyOn(component, 'downloadZip');
|
||||
|
||||
component.ngOnInit();
|
||||
|
||||
expect(component.downloadZip).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call cancelDownload when CANCEL button is clicked', () => {
|
||||
spyOn(downloadZipService, 'createDownload').and.callFake(() => new Observable((observer) => {
|
||||
observer.next();
|
||||
observer.complete();
|
||||
}));
|
||||
|
||||
fixture.detectChanges();
|
||||
spyOn(component, 'cancelDownload');
|
||||
|
||||
const cancelButton = element.querySelector<HTMLButtonElement>('#cancel-button');
|
||||
cancelButton.click();
|
||||
|
||||
expect(component.cancelDownload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call createDownload when component is initialize', () => {
|
||||
const createDownloadSpy = spyOn(downloadZipService, 'createDownload').and.callFake(() => new Observable((observer) => {
|
||||
observer.next();
|
||||
observer.complete();
|
||||
}));
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(createDownloadSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should close dialog when download is completed', () => {
|
||||
spyOn(downloadZipService, 'createDownload').and.callFake(() => new Observable((observer) => {
|
||||
observer.next();
|
||||
observer.complete();
|
||||
}));
|
||||
|
||||
component.download('fakeUrl', 'fileName');
|
||||
spyOn(component, 'cancelDownload');
|
||||
fixture.detectChanges();
|
||||
expect(dialogRef.close).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should close dialog when download is cancelled', () => {
|
||||
spyOn(downloadZipService, 'createDownload').and.callFake(() => new Observable((observer) => {
|
||||
observer.next();
|
||||
observer.complete();
|
||||
}));
|
||||
|
||||
fixture.detectChanges();
|
||||
component.download('url', 'filename');
|
||||
spyOn(downloadZipService, 'cancelDownload');
|
||||
component.cancelDownload();
|
||||
expect(dialogRef.close).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should interrupt download when cancel button is clicked', () => {
|
||||
spyOn(component, 'downloadZip');
|
||||
spyOn(component, 'download');
|
||||
spyOn(component, 'cancelDownload');
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.downloadZip).toHaveBeenCalled();
|
||||
|
||||
const cancelButton = element.querySelector<HTMLButtonElement>('#cancel-button');
|
||||
cancelButton.click();
|
||||
|
||||
expect(component.cancelDownload).toHaveBeenCalled();
|
||||
expect(component.download).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
@@ -1,57 +0,0 @@
|
||||
/*!
|
||||
* @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 { Component, Input, OnInit, OnChanges } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { DownloadZipDialogComponent } from './download-zip.dialog';
|
||||
import { zipNode, downloadEntry } from '../../mock/download-zip-data.mock';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-download-zip-dialog-storybook',
|
||||
template: `<button mat-raised-button (click)="openDialog()">Open dialog</button>`
|
||||
})
|
||||
export class DownloadZipDialogStorybookComponent implements OnInit, OnChanges {
|
||||
@Input()
|
||||
showLoading: boolean;
|
||||
|
||||
constructor(private dialog: MatDialog) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.setEntryStatus(this.showLoading);
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.setEntryStatus(this.showLoading);
|
||||
}
|
||||
|
||||
setEntryStatus(isLoading: boolean){
|
||||
if (!isLoading) {
|
||||
downloadEntry.entry.status = 'DONE';
|
||||
} else {
|
||||
downloadEntry.entry.status = 'PACKING';
|
||||
}
|
||||
}
|
||||
|
||||
openDialog() {
|
||||
this.dialog.open(DownloadZipDialogComponent, {
|
||||
minWidth: '50%',
|
||||
data: {
|
||||
nodeIds: [zipNode.entry.id]
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,87 +0,0 @@
|
||||
/*!
|
||||
* @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 { Meta, moduleMetadata, Story } from '@storybook/angular';
|
||||
import { CoreStoryModule } from '../../testing/core.story.module';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import {
|
||||
AlfrescoApiService,
|
||||
ContentService,
|
||||
DownloadZipService,
|
||||
NodesApiService
|
||||
} from '../../services';
|
||||
import { DownloadZipDialogStorybookComponent } from './download-zip.dialog.stories.component';
|
||||
import {
|
||||
AlfrescoApiServiceMock,
|
||||
ContentApiMock,
|
||||
DownloadZipMockService,
|
||||
NodesApiMock
|
||||
} from '../../mock/download-zip-service.mock';
|
||||
import { DownloadZipDialogModule } from './download-zip.dialog.module';
|
||||
|
||||
export default {
|
||||
component: DownloadZipDialogStorybookComponent,
|
||||
title: 'Core/Dialog/Download ZIP Dialog',
|
||||
decorators: [
|
||||
moduleMetadata({
|
||||
imports: [
|
||||
CoreStoryModule,
|
||||
DownloadZipDialogModule,
|
||||
MatButtonModule
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: AlfrescoApiService,
|
||||
useClass: AlfrescoApiServiceMock
|
||||
},
|
||||
{
|
||||
provide: DownloadZipService,
|
||||
useClass: DownloadZipMockService
|
||||
},
|
||||
{
|
||||
provide: ContentService,
|
||||
useClass: ContentApiMock
|
||||
},
|
||||
{
|
||||
provide: NodesApiService,
|
||||
useClass: NodesApiMock
|
||||
}
|
||||
]
|
||||
})
|
||||
],
|
||||
argTypes: {
|
||||
showLoading: {
|
||||
control: {
|
||||
type: 'boolean'
|
||||
},
|
||||
table: {
|
||||
category: 'Story controls',
|
||||
type: {
|
||||
summary: 'boolean'
|
||||
}
|
||||
},
|
||||
defaultValue: false
|
||||
}
|
||||
}
|
||||
} as Meta;
|
||||
|
||||
export const downloadZIPDialog: Story<DownloadZipDialogStorybookComponent> = (
|
||||
args: DownloadZipDialogStorybookComponent
|
||||
) => ({
|
||||
props: args
|
||||
});
|
||||
downloadZIPDialog.parameters = { layout: 'centered' };
|
@@ -1,114 +0,0 @@
|
||||
/*!
|
||||
* @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 { Component, Inject, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { DownloadEntry, MinimalNode } from '@alfresco/js-api';
|
||||
import { LogService } from '../../common/services/log.service';
|
||||
import { DownloadZipService } from '../../services/download-zip.service';
|
||||
import { ContentService } from '../../services/content.service';
|
||||
import { NodesApiService } from '../../services/nodes-api.service';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-download-zip-dialog',
|
||||
templateUrl: './download-zip.dialog.html',
|
||||
styleUrls: ['./download-zip.dialog.scss'],
|
||||
host: { class: 'adf-download-zip-dialog' },
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class DownloadZipDialogComponent implements OnInit {
|
||||
|
||||
// flag for async threads
|
||||
cancelled = false;
|
||||
downloadId: string;
|
||||
|
||||
constructor(private dialogRef: MatDialogRef<DownloadZipDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
public data: any,
|
||||
private logService: LogService,
|
||||
private downloadZipService: DownloadZipService,
|
||||
private nodeService: NodesApiService,
|
||||
private contentService: ContentService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.data && this.data.nodeIds && this.data.nodeIds.length > 0) {
|
||||
if (!this.cancelled) {
|
||||
this.downloadZip(this.data.nodeIds);
|
||||
} else {
|
||||
this.logService.log('Cancelled');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cancelDownload() {
|
||||
this.cancelled = true;
|
||||
this.downloadZipService.cancelDownload(this.downloadId);
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
|
||||
downloadZip(nodeIds: string[]) {
|
||||
if (nodeIds && nodeIds.length > 0) {
|
||||
|
||||
this.downloadZipService.createDownload({ nodeIds }).subscribe((data: DownloadEntry) => {
|
||||
if (data && data.entry && data.entry.id) {
|
||||
const url = this.contentService.getContentUrl(data.entry.id, true);
|
||||
|
||||
this.nodeService.getNode(data.entry.id).subscribe((downloadNode: MinimalNode) => {
|
||||
this.logService.log(downloadNode);
|
||||
const fileName = downloadNode.name;
|
||||
this.downloadId = data.entry.id;
|
||||
this.waitAndDownload(data.entry.id, url, fileName);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
waitAndDownload(downloadId: string, url: string, fileName: string) {
|
||||
if (this.cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.downloadZipService.getDownload(downloadId).subscribe((downloadEntry: DownloadEntry) => {
|
||||
if (downloadEntry.entry) {
|
||||
if (downloadEntry.entry.status === 'DONE') {
|
||||
this.download(url, fileName);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this.waitAndDownload(downloadId, url, fileName);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
download(url: string, fileName: string) {
|
||||
if (url && fileName) {
|
||||
const link = document.createElement('a');
|
||||
|
||||
link.style.display = 'none';
|
||||
link.download = fileName;
|
||||
link.href = url;
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
this.dialogRef.close(true);
|
||||
}
|
||||
}
|
@@ -15,8 +15,5 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './download-zip/download-zip.dialog';
|
||||
export * from './download-zip/download-zip.dialog.module';
|
||||
|
||||
export * from './edit-json/edit-json.dialog';
|
||||
export * from './edit-json/edit-json.dialog.module';
|
||||
|
@@ -22,7 +22,6 @@ import { MaterialModule } from '../material.module';
|
||||
import { HighlightDirective } from './highlight.directive';
|
||||
import { LogoutDirective } from './logout.directive';
|
||||
import { UploadDirective } from './upload.directive';
|
||||
import { NodeDownloadDirective } from './node-download.directive';
|
||||
import { TooltipCardDirective } from './tooltip-card/tooltip-card.directive';
|
||||
import { OverlayModule } from '@angular/cdk/overlay';
|
||||
import { TooltipCardComponent } from './tooltip-card/tooltip-card.component';
|
||||
@@ -37,7 +36,6 @@ import { InfiniteSelectScrollDirective } from './infinite-select-scroll.directiv
|
||||
declarations: [
|
||||
HighlightDirective,
|
||||
LogoutDirective,
|
||||
NodeDownloadDirective,
|
||||
UploadDirective,
|
||||
TooltipCardDirective,
|
||||
TooltipCardComponent,
|
||||
@@ -46,7 +44,6 @@ import { InfiniteSelectScrollDirective } from './infinite-select-scroll.directiv
|
||||
exports: [
|
||||
HighlightDirective,
|
||||
LogoutDirective,
|
||||
NodeDownloadDirective,
|
||||
UploadDirective,
|
||||
TooltipCardDirective,
|
||||
InfiniteSelectScrollDirective
|
||||
|
@@ -1,190 +0,0 @@
|
||||
/*!
|
||||
* @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 { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Component, DebugElement, ViewChild } from '@angular/core';
|
||||
import { setupTestBed } from '../testing/setup-test-bed';
|
||||
import { AlfrescoApiService } from '../services/alfresco-api.service';
|
||||
import { NodeDownloadDirective } from './node-download.directive';
|
||||
import { CoreTestingModule } from '../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
template: '<div [adfNodeDownload]="selection" [version]="version"></div>'
|
||||
})
|
||||
class TestComponent {
|
||||
@ViewChild(NodeDownloadDirective, { static: true })
|
||||
downloadDirective: NodeDownloadDirective;
|
||||
|
||||
selection;
|
||||
version;
|
||||
}
|
||||
|
||||
describe('NodeDownloadDirective', () => {
|
||||
let component: TestComponent;
|
||||
let fixture: ComponentFixture<TestComponent>;
|
||||
let element: DebugElement;
|
||||
let dialog: MatDialog;
|
||||
let apiService: AlfrescoApiService;
|
||||
let contentService;
|
||||
let dialogSpy;
|
||||
const mockOauth2Auth: any = {
|
||||
oauth2Auth: {
|
||||
callCustomApi: () => Promise.resolve()
|
||||
},
|
||||
isEcmLoggedIn: jasmine.createSpy('isEcmLoggedIn'),
|
||||
reply: jasmine.createSpy('reply')
|
||||
};
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
],
|
||||
declarations: [
|
||||
TestComponent
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TestComponent);
|
||||
component = fixture.componentInstance;
|
||||
element = fixture.debugElement.query(By.directive(NodeDownloadDirective));
|
||||
dialog = TestBed.inject(MatDialog);
|
||||
apiService = TestBed.inject(AlfrescoApiService);
|
||||
contentService = component.downloadDirective['contentApi'];
|
||||
dialogSpy = spyOn(dialog, 'open');
|
||||
});
|
||||
|
||||
it('should not download node when selection is empty', () => {
|
||||
spyOn(apiService, 'getInstance').and.returnValue(mockOauth2Auth);
|
||||
component.selection = [];
|
||||
|
||||
fixture.detectChanges();
|
||||
element.triggerEventHandler('click', null);
|
||||
|
||||
expect(apiService.getInstance).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not download zip when selection has no nodes', () => {
|
||||
component.selection = [];
|
||||
|
||||
fixture.detectChanges();
|
||||
element.triggerEventHandler('click', null);
|
||||
|
||||
expect(dialogSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should download selected node as file', () => {
|
||||
spyOn(contentService, 'getContentUrl');
|
||||
const node = { entry: { id: 'node-id', isFile: true } };
|
||||
component.selection = [node];
|
||||
|
||||
fixture.detectChanges();
|
||||
element.triggerEventHandler('click', null);
|
||||
|
||||
expect(contentService.getContentUrl).toHaveBeenCalledWith(node.entry.id, true);
|
||||
});
|
||||
|
||||
it('should download selected node version as file', () => {
|
||||
component.version = {
|
||||
entry: {
|
||||
id: '1.0'
|
||||
}
|
||||
};
|
||||
spyOn(contentService, 'getVersionContentUrl');
|
||||
const node = {entry: {id: 'node-id', isFile: true}};
|
||||
component.selection = [node];
|
||||
|
||||
fixture.detectChanges();
|
||||
element.triggerEventHandler('click', null);
|
||||
|
||||
expect(contentService.getVersionContentUrl).toHaveBeenCalledWith(node.entry.id, '1.0', true);
|
||||
});
|
||||
|
||||
it('should download selected shared node as file', () => {
|
||||
spyOn(contentService, 'getContentUrl');
|
||||
const node = { entry: { nodeId: 'shared-node-id', isFile: true } };
|
||||
component.selection = [node];
|
||||
|
||||
fixture.detectChanges();
|
||||
element.triggerEventHandler('click', null);
|
||||
|
||||
expect(contentService.getContentUrl).toHaveBeenCalledWith(node.entry.nodeId, true);
|
||||
});
|
||||
|
||||
it('should download selected files nodes as zip', () => {
|
||||
const node1 = { entry: { id: 'node-1' } };
|
||||
const node2 = { entry: { id: 'node-2' } };
|
||||
component.selection = [node1, node2];
|
||||
|
||||
fixture.detectChanges();
|
||||
element.triggerEventHandler('click', null);
|
||||
|
||||
expect(dialogSpy.calls.argsFor(0)[1].data).toEqual({ nodeIds: [ 'node-1', 'node-2' ] });
|
||||
});
|
||||
|
||||
it('should download selected shared files nodes as zip', () => {
|
||||
const node1 = { entry: { nodeId: 'shared-node-1' } };
|
||||
const node2 = { entry: { nodeId: 'shared-node-2' } };
|
||||
component.selection = [node1, node2];
|
||||
|
||||
fixture.detectChanges();
|
||||
element.triggerEventHandler('click', null);
|
||||
|
||||
expect(dialogSpy.calls.argsFor(0)[1].data).toEqual({ nodeIds: [ 'shared-node-1', 'shared-node-2' ] });
|
||||
});
|
||||
|
||||
it('should download selected folder node as zip', () => {
|
||||
const node = { entry: { isFolder: true, id: 'node-id' } };
|
||||
component.selection = [node];
|
||||
|
||||
fixture.detectChanges();
|
||||
element.triggerEventHandler('click', null);
|
||||
|
||||
expect(dialogSpy.calls.argsFor(0)[1].data).toEqual({ nodeIds: [ 'node-id' ] });
|
||||
});
|
||||
|
||||
it('should create link element to download file node', () => {
|
||||
const dummyLinkElement: any = {
|
||||
download: null,
|
||||
href: null,
|
||||
click: () => null,
|
||||
style: {
|
||||
display: null
|
||||
}
|
||||
};
|
||||
|
||||
const node = { entry: { name: 'dummy', isFile: true, id: 'node-id' } };
|
||||
|
||||
spyOn(contentService, 'getContentUrl').and.returnValue('somewhere-over-the-rainbow');
|
||||
spyOn(document, 'createElement').and.returnValue(dummyLinkElement);
|
||||
spyOn(document.body, 'appendChild').and.stub();
|
||||
spyOn(document.body, 'removeChild').and.stub();
|
||||
|
||||
component.selection = [node];
|
||||
|
||||
fixture.detectChanges();
|
||||
element.triggerEventHandler('click', null);
|
||||
|
||||
expect(document.createElement).toHaveBeenCalled();
|
||||
expect(dummyLinkElement.download).toBe('dummy');
|
||||
expect(dummyLinkElement.href).toContain('somewhere-over-the-rainbow');
|
||||
});
|
||||
});
|
@@ -1,143 +0,0 @@
|
||||
/*!
|
||||
* @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 { Directive, Input, HostListener } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { AlfrescoApiService } from '../services/alfresco-api.service';
|
||||
import { DownloadZipDialogComponent } from '../dialogs/download-zip/download-zip.dialog';
|
||||
import { ContentApi, NodeEntry, VersionEntry } from '@alfresco/js-api';
|
||||
import { DownloadService } from '../services/download.service';
|
||||
|
||||
/**
|
||||
* Directive selectors without adf- prefix will be deprecated on 3.0.0
|
||||
*/
|
||||
@Directive({
|
||||
// eslint-disable-next-line @angular-eslint/directive-selector
|
||||
selector: '[adfNodeDownload]'
|
||||
})
|
||||
export class NodeDownloadDirective {
|
||||
|
||||
_contentApi: ContentApi;
|
||||
get contentApi(): ContentApi {
|
||||
this._contentApi = this._contentApi ?? new ContentApi(this.apiService.getInstance());
|
||||
return this._contentApi;
|
||||
}
|
||||
|
||||
/** Nodes to download. */
|
||||
@Input('adfNodeDownload')
|
||||
nodes: NodeEntry | NodeEntry[];
|
||||
|
||||
/** Node's version to download. */
|
||||
@Input()
|
||||
version: VersionEntry;
|
||||
|
||||
@HostListener('click')
|
||||
onClick() {
|
||||
this.downloadNodes(this.nodes);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private apiService: AlfrescoApiService,
|
||||
private downloadService: DownloadService,
|
||||
private dialog: MatDialog) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads multiple selected nodes.
|
||||
* Packs result into a .ZIP archive if there is more than one node selected.
|
||||
*
|
||||
* @param selection Multiple selected nodes to download
|
||||
*/
|
||||
downloadNodes(selection: NodeEntry | Array<NodeEntry>) {
|
||||
|
||||
if (!this.isSelectionValid(selection)) {
|
||||
return;
|
||||
}
|
||||
if (selection instanceof Array) {
|
||||
if (selection.length === 1) {
|
||||
this.downloadNode(selection[0]);
|
||||
} else {
|
||||
this.downloadZip(selection);
|
||||
}
|
||||
} else {
|
||||
this.downloadNode(selection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a single node.
|
||||
* Packs result into a .ZIP archive is the node is a Folder.
|
||||
*
|
||||
* @param node Node to download
|
||||
*/
|
||||
downloadNode(node: NodeEntry) {
|
||||
if (node && node.entry) {
|
||||
const entry = node.entry;
|
||||
|
||||
if (entry.isFile) {
|
||||
this.downloadFile(node);
|
||||
}
|
||||
|
||||
if (entry.isFolder) {
|
||||
this.downloadZip([node]);
|
||||
}
|
||||
|
||||
// Check if there's nodeId for Shared Files
|
||||
if (!entry.isFile && !entry.isFolder && (entry as any).nodeId) {
|
||||
this.downloadFile(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isSelectionValid(selection: NodeEntry | Array<NodeEntry>) {
|
||||
return selection || (selection instanceof Array && selection.length > 0);
|
||||
}
|
||||
|
||||
private downloadFile(node: NodeEntry) {
|
||||
if (node && node.entry) {
|
||||
// nodeId for Shared node
|
||||
const id = (node.entry as any).nodeId || node.entry.id;
|
||||
|
||||
let url;
|
||||
let fileName;
|
||||
if (this.version) {
|
||||
url = this.contentApi.getVersionContentUrl(id, this.version.entry.id, true);
|
||||
fileName = this.version.entry.name;
|
||||
} else {
|
||||
url = this.contentApi.getContentUrl(id, true);
|
||||
fileName = node.entry.name;
|
||||
}
|
||||
|
||||
this.downloadService.downloadUrl(url, fileName);
|
||||
}
|
||||
}
|
||||
|
||||
private downloadZip(selection: Array<NodeEntry>) {
|
||||
if (selection && selection.length > 0) {
|
||||
// nodeId for Shared node
|
||||
const nodeIds = selection.map((node: any) => (node.entry.nodeId || node.entry.id));
|
||||
|
||||
this.dialog.open(DownloadZipDialogComponent, {
|
||||
width: '600px',
|
||||
disableClose: true,
|
||||
data: {
|
||||
nodeIds
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -17,7 +17,6 @@
|
||||
|
||||
export * from './highlight.directive';
|
||||
export * from './logout.directive';
|
||||
export * from './node-download.directive';
|
||||
export * from './upload.directive';
|
||||
export * from './tooltip-card/tooltip-card.directive';
|
||||
export * from './infinite-select-scroll.directive';
|
||||
|
@@ -1,52 +0,0 @@
|
||||
/*!
|
||||
* @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 { FileModel, FileUploadStatus } from '../models/file.model';
|
||||
|
||||
export class FileUploadEvent {
|
||||
|
||||
constructor(
|
||||
public readonly file: FileModel,
|
||||
public readonly status: FileUploadStatus = FileUploadStatus.Pending,
|
||||
public readonly error: any = null) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class FileUploadCompleteEvent extends FileUploadEvent {
|
||||
|
||||
constructor(file: FileModel, public totalComplete: number = 0, public data?: any, public totalAborted: number = 0) {
|
||||
super(file, FileUploadStatus.Complete);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class FileUploadDeleteEvent extends FileUploadEvent {
|
||||
|
||||
constructor(file: FileModel, public totalComplete: number = 0) {
|
||||
super(file, FileUploadStatus.Deleted);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class FileUploadErrorEvent extends FileUploadEvent {
|
||||
|
||||
constructor(file: FileModel, public error: any, public totalError: number = 0) {
|
||||
super(file, FileUploadStatus.Error);
|
||||
}
|
||||
|
||||
}
|
@@ -17,4 +17,3 @@
|
||||
|
||||
export * from './base.event';
|
||||
export * from './base-ui.event';
|
||||
export * from './file.event';
|
||||
|
@@ -17,9 +17,7 @@
|
||||
|
||||
/* eslint-disable @angular-eslint/component-selector */
|
||||
|
||||
import { RelatedContentRepresentation } from '@alfresco/js-api';
|
||||
|
||||
export class ContentLinkModel implements RelatedContentRepresentation {
|
||||
export class ContentLinkModel {
|
||||
|
||||
contentAvailable: boolean;
|
||||
created: Date;
|
||||
|
@@ -1,97 +0,0 @@
|
||||
/*!
|
||||
* @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 { FormSaveRepresentation } from '@alfresco/js-api';
|
||||
|
||||
export class FormDefinitionModel extends FormSaveRepresentation {
|
||||
reusable: boolean = false;
|
||||
newVersion: boolean = false;
|
||||
formRepresentation: any;
|
||||
formImageBase64: string = '';
|
||||
|
||||
constructor(id: string, name: any, lastUpdatedByFullName: string, lastUpdated: string, metadata: any) {
|
||||
super();
|
||||
this.formRepresentation = {
|
||||
id,
|
||||
name,
|
||||
description: '',
|
||||
version: 1,
|
||||
lastUpdatedBy: 1,
|
||||
lastUpdatedByFullName,
|
||||
lastUpdated,
|
||||
stencilSetId: 0,
|
||||
referenceId: null,
|
||||
formDefinition: {
|
||||
fields: [{
|
||||
name: 'Label',
|
||||
type: 'container',
|
||||
fieldType: 'ContainerRepresentation',
|
||||
numberOfColumns: 2,
|
||||
required: false,
|
||||
readOnly: false,
|
||||
sizeX: 2,
|
||||
sizeY: 1,
|
||||
row: -1,
|
||||
col: -1,
|
||||
fields: {1: this.metadataToFields(metadata)}
|
||||
}],
|
||||
gridsterForm: false,
|
||||
javascriptEvents: [],
|
||||
metadata: {},
|
||||
outcomes: [],
|
||||
className: '',
|
||||
style: '',
|
||||
tabs: [],
|
||||
variables: []
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private metadataToFields(metadata: any): any[] {
|
||||
const fields = [];
|
||||
if (metadata) {
|
||||
metadata.forEach((property) => {
|
||||
if (property) {
|
||||
const field = {
|
||||
type: 'text',
|
||||
id: property.name,
|
||||
name: property.name,
|
||||
required: false,
|
||||
readOnly: false,
|
||||
sizeX: 1,
|
||||
sizeY: 1,
|
||||
row: -1,
|
||||
col: -1,
|
||||
colspan: 1,
|
||||
params: {
|
||||
existingColspan: 1,
|
||||
maxColspan: 2
|
||||
},
|
||||
layout: {
|
||||
colspan: 1,
|
||||
row: -1,
|
||||
column: -1
|
||||
}
|
||||
};
|
||||
fields.push(field);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
@@ -32,5 +32,4 @@ export * from './events';
|
||||
export * from './form-base.module';
|
||||
|
||||
export * from './models/form-rules.model';
|
||||
export * from './models/form-definition.model';
|
||||
export * from './models/task-process-variable.model';
|
||||
|
@@ -1,84 +0,0 @@
|
||||
/*!
|
||||
* @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 { BpmUserModel } from '../models';
|
||||
|
||||
export const fakeBpmUserNoImage = {
|
||||
apps: [],
|
||||
capabilities: 'fake-capability',
|
||||
company: 'fake-company',
|
||||
created: 'fake-create-date',
|
||||
email: 'fakeBpm@fake.com',
|
||||
externalId: 'fake-external-id',
|
||||
firstName: 'fake-first-name',
|
||||
lastName: 'fake-last-name',
|
||||
groups: [],
|
||||
id: 'fake-id',
|
||||
lastUpdate: 'fake-update-date',
|
||||
latestSyncTimeStamp: 'fake-timestamp',
|
||||
password: 'fake-password',
|
||||
pictureId: undefined,
|
||||
status: 'fake-status',
|
||||
tenantId: 'fake-tenant-id',
|
||||
tenantName: 'fake-tenant-name',
|
||||
tenantPictureId: 'fake-tenant-picture-id',
|
||||
type: 'fake-type'
|
||||
};
|
||||
|
||||
export const fakeBpmUser = new BpmUserModel({
|
||||
apps: [],
|
||||
capabilities: null,
|
||||
company: 'fake-company',
|
||||
created: 'fake-create-date',
|
||||
email: 'fakeBpm@fake.com',
|
||||
externalId: 'fake-external-id',
|
||||
firstName: 'fake-bpm-first-name',
|
||||
lastName: 'fake-bpm-last-name',
|
||||
groups: [],
|
||||
id: 'fake-id',
|
||||
lastUpdate: 'fake-update-date',
|
||||
latestSyncTimeStamp: 'fake-timestamp',
|
||||
password: 'fake-password',
|
||||
pictureId: 12,
|
||||
status: 'fake-status',
|
||||
tenantId: 'fake-tenant-id',
|
||||
tenantName: 'fake-tenant-name',
|
||||
tenantPictureId: 'fake-tenant-picture-id',
|
||||
type: 'fake-type'
|
||||
});
|
||||
|
||||
export const fakeBpmEditedUser = {
|
||||
apps: [],
|
||||
capabilities: 'fake-capability',
|
||||
company: 'fake-company',
|
||||
created: 'fake-create-date',
|
||||
email: 'fakeBpm@fake.com',
|
||||
externalId: 'fake-external-id',
|
||||
firstName: 'fake-first-name',
|
||||
lastName: 'fake-last-name',
|
||||
groups: [],
|
||||
id: 'fake-id',
|
||||
lastUpdate: 'fake-update-date',
|
||||
latestSyncTimeStamp: 'fake-timestamp',
|
||||
password: 'fake-password',
|
||||
pictureId: 'src/assets/images/bpmImg.gif',
|
||||
status: 'fake-status',
|
||||
tenantId: 'fake-tenant-id',
|
||||
tenantName: 'fake-tenant-name',
|
||||
tenantPictureId: 'fake-tenant-picture-id',
|
||||
type: 'fake-type'
|
||||
};
|
@@ -1,114 +0,0 @@
|
||||
/*!
|
||||
* @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 { Observable, Subject, of } from 'rxjs';
|
||||
import { BpmProductVersionModel } from '../models/product-version.model';
|
||||
import {
|
||||
RepositoryInfo,
|
||||
SystemPropertiesRepresentation
|
||||
} from '@alfresco/js-api';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class DiscoveryApiServiceMock {
|
||||
|
||||
/**
|
||||
* Gets product information for Content Services.
|
||||
*/
|
||||
ecmProductInfo$ = new Subject<RepositoryInfo>();
|
||||
|
||||
public getEcmProductInfo(): Observable<RepositoryInfo | any> {
|
||||
return of({
|
||||
edition: 'Enterprise',
|
||||
version: {
|
||||
major: '7',
|
||||
minor: '2',
|
||||
patch: '0',
|
||||
hotfix: '0',
|
||||
schema: 16000,
|
||||
label: 'rde8705d0-blocal',
|
||||
display: '7.2.0.0 (rde8705d0-blocal) schema 16000'
|
||||
},
|
||||
license: {
|
||||
issuedAt: '2021-11-10T23:30:30.234+0000',
|
||||
expiresAt: '2021-11-12T00:00:00.000+0000',
|
||||
remainingDays: 1,
|
||||
holder: 'Trial User',
|
||||
mode: 'ENTERPRISE',
|
||||
entitlements: { isClusterEnabled: true, isCryptodocEnabled: false }
|
||||
},
|
||||
status: {
|
||||
isReadOnly: false,
|
||||
isAuditEnabled: true,
|
||||
isQuickShareEnabled: true,
|
||||
isThumbnailGenerationEnabled: true,
|
||||
isDirectAccessUrlEnabled: true
|
||||
},
|
||||
modules: [{
|
||||
id: 'org_alfresco_module_rm',
|
||||
title: 'AGS Repo',
|
||||
description: 'Alfresco Governance Services Repository Extension',
|
||||
version: '14.26',
|
||||
installState: 'UNKNOWN',
|
||||
versionMin: '7.0.0',
|
||||
versionMax: '999'
|
||||
}, {
|
||||
id: 'org_alfresco_integrations_S3Connector',
|
||||
title: 'S3 Connector',
|
||||
description: 'Provides Amazon S3 content storage for the contentstore and deleted contentstore',
|
||||
version: '5.0.0-A1',
|
||||
installDate: '2021-11-10T23:29:19.560+0000',
|
||||
installState: 'INSTALLED',
|
||||
versionMin: '7.2',
|
||||
versionMax: '999'
|
||||
}, {
|
||||
id: 'alfresco-trashcan-cleaner',
|
||||
title: 'alfresco-trashcan-cleaner project',
|
||||
description: 'The Alfresco Trashcan Cleaner (Alfresco Module)',
|
||||
version: '2.4.1',
|
||||
installState: 'UNKNOWN',
|
||||
versionMin: '0',
|
||||
versionMax: '999'
|
||||
}, {
|
||||
id: 'alfresco-content-connector-for-salesforce-repo',
|
||||
title: 'Alfresco Content Connector for Salesforce Repository AMP',
|
||||
description: 'Alfresco Repository artifacts needed for the Alfresco Content Connector for Salesforce Repository Amp',
|
||||
version: '2.3.0.3',
|
||||
installDate: '2021-11-10T23:29:18.918+0000',
|
||||
installState: 'INSTALLED',
|
||||
versionMin: '6.2.0',
|
||||
versionMax: '999'
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
public getBpmProductInfo(): Observable<BpmProductVersionModel> {
|
||||
return of({
|
||||
revisionVersion: '0-RC1',
|
||||
edition: 'Alfresco Process Services (powered by Activiti)',
|
||||
type: 'bpmSuite',
|
||||
majorVersion: '2',
|
||||
minorVersion: '1'
|
||||
});
|
||||
}
|
||||
|
||||
public getBPMSystemProperties(): Observable<SystemPropertiesRepresentation> {
|
||||
return of({} as any);
|
||||
}
|
||||
}
|
@@ -1,33 +0,0 @@
|
||||
/*!
|
||||
* @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 { DownloadEntry } from '@alfresco/js-api';
|
||||
|
||||
export const zipNode = {
|
||||
entry: {
|
||||
name: 'files.zip',
|
||||
contentUrl: './assets/files.zip',
|
||||
id: 'files_in_zip'
|
||||
}
|
||||
};
|
||||
|
||||
export const downloadEntry: DownloadEntry = {
|
||||
entry: {
|
||||
id: 'entryId',
|
||||
status: 'DONE'
|
||||
}
|
||||
};
|
@@ -1,101 +0,0 @@
|
||||
/*!
|
||||
* @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 { DownloadBodyCreate, DownloadEntry } from '@alfresco/js-api';
|
||||
import { from, Observable, of, ReplaySubject, Subject } from 'rxjs';
|
||||
import { catchError } from 'rxjs/internal/operators/catchError';
|
||||
import { zipNode, downloadEntry } from './download-zip-data.mock';
|
||||
|
||||
export class AlfrescoApiServiceMock {
|
||||
nodeUpdated = new Subject<Node>();
|
||||
alfrescoApiInitialized: ReplaySubject<boolean> = new ReplaySubject(1);
|
||||
alfrescoApi = new AlfrescoApiCompatibilityMock();
|
||||
|
||||
load() {}
|
||||
getInstance = () => this.alfrescoApi;
|
||||
}
|
||||
|
||||
class AlfrescoApiCompatibilityMock {
|
||||
core = new CoreMock();
|
||||
content = new ContentApiMock();
|
||||
|
||||
isOauthConfiguration = () => true;
|
||||
isLoggedIn = () => true;
|
||||
isEcmConfiguration = () => true;
|
||||
isEcmLoggedIn = () => true;
|
||||
}
|
||||
|
||||
export class ContentApiMock {
|
||||
getContentUrl = (_: string, _1?: boolean, _2?: string): string =>
|
||||
zipNode.entry.contentUrl;
|
||||
}
|
||||
|
||||
class CoreMock {
|
||||
downloadsApi = new DownloadsApiMock();
|
||||
nodesApi = new NodesApiMock();
|
||||
}
|
||||
|
||||
export class NodesApiMock {
|
||||
getNode = (_: string, _2?: any): any => of(zipNode.entry);
|
||||
}
|
||||
|
||||
class DownloadsApiMock {
|
||||
createDownload = (
|
||||
_: DownloadBodyCreate,
|
||||
_2?: any
|
||||
): Promise<DownloadEntry> => Promise.resolve(downloadEntry);
|
||||
|
||||
getDownload = (_: string, _2?: any): Promise<DownloadEntry> =>
|
||||
Promise.resolve(downloadEntry);
|
||||
cancelDownload(_: string) {}
|
||||
}
|
||||
|
||||
export class DownloadZipMockService {
|
||||
private _downloadsApi: DownloadsApiMock;
|
||||
get downloadsApi(): DownloadsApiMock {
|
||||
this._downloadsApi = this._downloadsApi ?? new DownloadsApiMock();
|
||||
return this._downloadsApi;
|
||||
}
|
||||
|
||||
createDownload(payload: DownloadBodyCreate): Observable<DownloadEntry> {
|
||||
return from(this.downloadsApi.createDownload(payload)).pipe(
|
||||
catchError((err) => of(err))
|
||||
);
|
||||
}
|
||||
|
||||
getDownload(downloadId: string): Observable<DownloadEntry> {
|
||||
return from(this.downloadsApi.getDownload(downloadId));
|
||||
}
|
||||
|
||||
cancelDownload(downloadId: string) {
|
||||
this.downloadsApi.cancelDownload(downloadId);
|
||||
}
|
||||
|
||||
download(url: string, fileName: string) {
|
||||
if (url && fileName) {
|
||||
const link = document.createElement('a');
|
||||
|
||||
link.style.display = 'none';
|
||||
link.download = fileName;
|
||||
link.href = url;
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,162 +0,0 @@
|
||||
/*!
|
||||
* @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 { EcmCompanyModel } from '../models/ecm-company.model';
|
||||
import { PersonEntry, Person, PersonPaging } from '@alfresco/js-api';
|
||||
|
||||
export const fakeEcmCompany: EcmCompanyModel = {
|
||||
organization: 'company-fake-name',
|
||||
address1: 'fake-address-1',
|
||||
address2: 'fake-address-2',
|
||||
address3: 'fake-address-3',
|
||||
postcode: 'fAk1',
|
||||
telephone: '00000000',
|
||||
fax: '11111111',
|
||||
email: 'fakeCompany@fake.com'
|
||||
};
|
||||
|
||||
export const fakeEcmUser = {
|
||||
id: 'fake-id',
|
||||
firstName: 'fake-ecm-first-name',
|
||||
lastName: 'fake-ecm-last-name',
|
||||
description: 'i am a fake user for test',
|
||||
avatarId: 'fake-avatar-id',
|
||||
email: 'fakeEcm@ecmUser.com',
|
||||
skypeId: 'fake-skype-id',
|
||||
googleId: 'fake-googleId-id',
|
||||
instantMessageId: 'fake-instantMessageId-id',
|
||||
company: null,
|
||||
jobTitle: 'job-ecm-test',
|
||||
location: 'fake location',
|
||||
mobile: '000000000',
|
||||
telephone: '11111111',
|
||||
statusUpdatedAt: 'fake-date',
|
||||
userStatus: 'active',
|
||||
enabled: true,
|
||||
emailNotificationsEnabled: true
|
||||
};
|
||||
|
||||
export const fakeEcmAdminUser = {
|
||||
...fakeEcmUser,
|
||||
capabilities: {
|
||||
isAdmin: true
|
||||
}
|
||||
};
|
||||
|
||||
export const fakeEcmUser2 = {
|
||||
id: 'another-fake-id',
|
||||
firstName: 'another-fake-first-name',
|
||||
lastName: 'another',
|
||||
displayName: 'admin.adf User',
|
||||
email: 'admin.adf@alfresco.com',
|
||||
company: null,
|
||||
enabled: true,
|
||||
emailNotificationsEnabled: true
|
||||
};
|
||||
|
||||
export const fakeEcmUserNoImage = {
|
||||
id: 'fake-id',
|
||||
firstName: 'fake-first-name',
|
||||
lastName: 'fake-last-name',
|
||||
description: 'i am a fake user for test',
|
||||
avatarId: null,
|
||||
email: 'fakeEcm@ecmUser.com',
|
||||
skypeId: 'fake-skype-id',
|
||||
googleId: 'fake-googleId-id',
|
||||
instantMessageId: 'fake-instantMessageId-id',
|
||||
company: null,
|
||||
jobTitle: null,
|
||||
location: 'fake location',
|
||||
mobile: '000000000',
|
||||
telephone: '11111111',
|
||||
statusUpdatedAt: 'fake-date',
|
||||
userStatus: 'active',
|
||||
enabled: true,
|
||||
emailNotificationsEnabled: true
|
||||
};
|
||||
|
||||
export const fakeEcmEditedUser = {
|
||||
id: 'fake-id',
|
||||
firstName: null,
|
||||
lastName: 'fake-last-name',
|
||||
description: 'i am a fake user for test',
|
||||
avatarId: 'fake-avatar-id',
|
||||
email: 'fakeEcm@ecmUser.com',
|
||||
skypeId: 'fake-skype-id',
|
||||
googleId: 'fake-googleId-id',
|
||||
instantMessageId: 'fake-instantMessageId-id',
|
||||
company: null,
|
||||
jobTitle: 'test job',
|
||||
location: 'fake location',
|
||||
mobile: '000000000',
|
||||
telephone: '11111111',
|
||||
statusUpdatedAt: 'fake-date',
|
||||
userStatus: 'active',
|
||||
enabled: true,
|
||||
emailNotificationsEnabled: true
|
||||
};
|
||||
|
||||
export const fakeEcmUserList = new PersonPaging({
|
||||
list: {
|
||||
pagination: {
|
||||
count: 2,
|
||||
hasMoreItems: false,
|
||||
totalItems: 2,
|
||||
skipCount: 0,
|
||||
maxItems: 100
|
||||
},
|
||||
entries: [
|
||||
{
|
||||
entry: fakeEcmUser
|
||||
},
|
||||
{
|
||||
entry: fakeEcmUser2
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
export const createNewPersonMock = {
|
||||
id: 'fake-id',
|
||||
firstName: 'fake-ecm-first-name',
|
||||
lastName: 'fake-ecm-last-name',
|
||||
description: 'i am a fake user for test',
|
||||
password: 'fake-avatar-id',
|
||||
email: 'fakeEcm@ecmUser.com'
|
||||
};
|
||||
|
||||
export const getFakeUserWithContentAdminCapability = (): PersonEntry => {
|
||||
const fakeEcmUserWithAdminCapabilities = {
|
||||
...fakeEcmUser,
|
||||
capabilities: {
|
||||
isAdmin: true
|
||||
}
|
||||
};
|
||||
const mockPerson = new Person(fakeEcmUserWithAdminCapabilities);
|
||||
return { entry: mockPerson };
|
||||
};
|
||||
|
||||
export const getFakeUserWithContentUserCapability = (): PersonEntry => {
|
||||
const fakeEcmUserWithAdminCapabilities = {
|
||||
...fakeEcmUser,
|
||||
capabilities: {
|
||||
isAdmin: false
|
||||
}
|
||||
};
|
||||
const mockPerson = new Person(fakeEcmUserWithAdminCapabilities);
|
||||
return { entry: mockPerson };
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@@ -16,11 +16,8 @@
|
||||
*/
|
||||
|
||||
export * from './alfresco-api.mock';
|
||||
export * from './bpm-user.service.mock';
|
||||
export * from './cookie.service.mock';
|
||||
export * from './ecm-user.service.mock';
|
||||
export * from './event.mock';
|
||||
export * from './renditions-service.mock';
|
||||
export * from './translation.service.mock';
|
||||
export * from './alfresco-api.service.mock';
|
||||
|
||||
|
@@ -1,186 +0,0 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
export const fakeRendition = {
|
||||
entry: {
|
||||
id: 'pdf',
|
||||
content: {
|
||||
mimeType: 'application/pdf',
|
||||
mimeTypeName: 'Adobe PDF Document'
|
||||
},
|
||||
status: 'NOT_CREATED'
|
||||
}
|
||||
};
|
||||
|
||||
export const fakeRenditionCreated = {
|
||||
entry: {
|
||||
id: 'pdf',
|
||||
content: {
|
||||
mimeType: 'application/pdf',
|
||||
mimeTypeName: 'Adobe PDF Document'
|
||||
},
|
||||
status: 'CREATED'
|
||||
}
|
||||
};
|
||||
|
||||
export const fakeRenditionsList = {
|
||||
list: {
|
||||
pagination: {
|
||||
count: 6,
|
||||
hasMoreItems: false,
|
||||
totalItems: 6,
|
||||
skipCount: 0,
|
||||
maxItems: 100
|
||||
},
|
||||
entries: [
|
||||
{
|
||||
entry: {
|
||||
id: 'avatar',
|
||||
content: {
|
||||
mimeType: 'image/png',
|
||||
mimeTypeName: 'PNG Image'
|
||||
},
|
||||
status: 'NOT_CREATED'
|
||||
}
|
||||
},
|
||||
{
|
||||
entry: {
|
||||
id: 'avatar32',
|
||||
content: {
|
||||
mimeType: 'image/png',
|
||||
mimeTypeName: 'PNG Image'
|
||||
},
|
||||
status: 'NOT_CREATED'
|
||||
}
|
||||
},
|
||||
{
|
||||
entry: {
|
||||
id: 'doclib',
|
||||
content: {
|
||||
mimeType: 'image/png',
|
||||
mimeTypeName: 'PNG Image'
|
||||
},
|
||||
status: 'NOT_CREATED'
|
||||
}
|
||||
},
|
||||
{
|
||||
entry: {
|
||||
id: 'imgpreview',
|
||||
content: {
|
||||
mimeType: 'image/jpeg',
|
||||
mimeTypeName: 'JPEG Image'
|
||||
},
|
||||
status: 'NOT_CREATED'
|
||||
}
|
||||
},
|
||||
{
|
||||
entry: {
|
||||
id: 'medium',
|
||||
content: {
|
||||
mimeType: 'image/jpeg',
|
||||
mimeTypeName: 'JPEG Image'
|
||||
},
|
||||
status: 'NOT_CREATED'
|
||||
}
|
||||
},
|
||||
{
|
||||
entry: {
|
||||
id: 'pdf',
|
||||
content: {
|
||||
mimeType: 'application/pdf',
|
||||
mimeTypeName: 'Adobe PDF Document'
|
||||
},
|
||||
status: 'NOT_CREATED'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
export const fakeRenditionsListWithACreated = {
|
||||
list: {
|
||||
pagination: {
|
||||
count: 6,
|
||||
hasMoreItems: false,
|
||||
totalItems: 6,
|
||||
skipCount: 0,
|
||||
maxItems: 100
|
||||
},
|
||||
entries: [
|
||||
{
|
||||
entry: {
|
||||
id: 'avatar',
|
||||
content: {
|
||||
mimeType: 'image/png',
|
||||
mimeTypeName: 'PNG Image'
|
||||
},
|
||||
status: 'NOT_CREATED'
|
||||
}
|
||||
},
|
||||
{
|
||||
entry: {
|
||||
id: 'avatar32',
|
||||
content: {
|
||||
mimeType: 'image/png',
|
||||
mimeTypeName: 'PNG Image'
|
||||
},
|
||||
status: 'NOT_CREATED'
|
||||
}
|
||||
},
|
||||
{
|
||||
entry: {
|
||||
id: 'doclib',
|
||||
content: {
|
||||
mimeType: 'image/png',
|
||||
mimeTypeName: 'PNG Image'
|
||||
},
|
||||
status: 'NOT_CREATED'
|
||||
}
|
||||
},
|
||||
{
|
||||
entry: {
|
||||
id: 'imgpreview',
|
||||
content: {
|
||||
mimeType: 'image/jpeg',
|
||||
mimeTypeName: 'JPEG Image'
|
||||
},
|
||||
status: 'NOT_CREATED'
|
||||
}
|
||||
},
|
||||
{
|
||||
entry: {
|
||||
id: 'medium',
|
||||
content: {
|
||||
mimeType: 'image/jpeg',
|
||||
mimeTypeName: 'JPEG Image'
|
||||
},
|
||||
status: 'NOT_CREATED'
|
||||
}
|
||||
},
|
||||
{
|
||||
entry: {
|
||||
id: 'pdf',
|
||||
content: {
|
||||
mimeType: 'application/pdf',
|
||||
mimeTypeName: 'Adobe PDF Document'
|
||||
},
|
||||
status: 'CREATED'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
@@ -1,30 +0,0 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
/* spellchecker: disable */
|
||||
export class AllowableOperationsEnum extends String {
|
||||
static DELETE: string = 'delete';
|
||||
static UPDATE: string = 'update';
|
||||
static CREATE: string = 'create';
|
||||
static COPY: string = 'copy';
|
||||
static LOCK: string = 'lock';
|
||||
static UPDATEPERMISSIONS: string = 'updatePermissions';
|
||||
static NOT_DELETE: string = '!delete';
|
||||
static NOT_UPDATE: string = '!update';
|
||||
static NOT_CREATE: string = '!create';
|
||||
static NOT_UPDATEPERMISSIONS: string = '!updatePermissions';
|
||||
}
|
@@ -1,66 +0,0 @@
|
||||
/*!
|
||||
* @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 { UserRepresentation } from '@alfresco/js-api';
|
||||
|
||||
export class BpmUserModel implements UserRepresentation {
|
||||
apps: any;
|
||||
capabilities: string[];
|
||||
company: string;
|
||||
created: Date;
|
||||
email: string;
|
||||
externalId: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
fullname: string;
|
||||
groups: any;
|
||||
id: number;
|
||||
lastUpdate: Date;
|
||||
latestSyncTimeStamp: Date;
|
||||
password: string;
|
||||
pictureId: number;
|
||||
status: string;
|
||||
tenantId: number;
|
||||
tenantName: string;
|
||||
tenantPictureId: number;
|
||||
type: string;
|
||||
|
||||
constructor(input?: any) {
|
||||
if (input) {
|
||||
this.apps = input.apps;
|
||||
this.capabilities = input.capabilities;
|
||||
this.company = input.company;
|
||||
this.created = input.created;
|
||||
this.email = input.email;
|
||||
this.externalId = input.externalId;
|
||||
this.firstName = input.firstName;
|
||||
this.lastName = input.lastName;
|
||||
this.fullname = input.fullname;
|
||||
this.groups = input.groups;
|
||||
this.id = input.id;
|
||||
this.lastUpdate = input.lastUpdate;
|
||||
this.latestSyncTimeStamp = input.latestSyncTimeStamp;
|
||||
this.password = input.password;
|
||||
this.pictureId = input.pictureId;
|
||||
this.status = input.status;
|
||||
this.tenantId = input.tenantId;
|
||||
this.tenantName = input.tenantName;
|
||||
this.tenantPictureId = input.tenantPictureId;
|
||||
this.type = input.type;
|
||||
}
|
||||
}
|
||||
}
|
@@ -15,13 +15,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { EcmUserModel } from './ecm-user.model';
|
||||
import { User } from './general-user.model';
|
||||
|
||||
export class CommentModel {
|
||||
id: number;
|
||||
message: string;
|
||||
created: Date;
|
||||
createdBy: EcmUserModel;
|
||||
createdBy: User;
|
||||
isSelected: boolean;
|
||||
|
||||
constructor(obj?: any) {
|
||||
|
@@ -1,27 +0,0 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
export class EcmCompanyModel {
|
||||
organization: string;
|
||||
address1: string;
|
||||
address2: string;
|
||||
address3: string;
|
||||
postcode: string;
|
||||
telephone: string;
|
||||
fax: string;
|
||||
email: string;
|
||||
}
|
@@ -1,72 +0,0 @@
|
||||
/*!
|
||||
* @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 { Capabilities } from '@alfresco/js-api';
|
||||
import { EcmCompanyModel } from './ecm-company.model';
|
||||
|
||||
export class EcmUserModel {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName?: string;
|
||||
displayName?: string;
|
||||
description?: string;
|
||||
avatarId?: string;
|
||||
email: string;
|
||||
skypeId?: string;
|
||||
googleId?: string;
|
||||
instantMessageId?: string;
|
||||
jobTitle?: string;
|
||||
location?: string;
|
||||
company: EcmCompanyModel;
|
||||
mobile?: string;
|
||||
telephone?: string;
|
||||
statusUpdatedAt?: Date;
|
||||
userStatus?: string;
|
||||
enabled: boolean;
|
||||
emailNotificationsEnabled?: boolean;
|
||||
aspectNames?: string[];
|
||||
properties?: { [key: string]: string };
|
||||
capabilities?: Capabilities;
|
||||
|
||||
constructor(obj?: any) {
|
||||
this.id = obj && obj.id || null;
|
||||
this.firstName = obj && obj.firstName;
|
||||
this.lastName = obj && obj.lastName;
|
||||
this.description = obj && obj.description || null;
|
||||
this.avatarId = obj && obj.avatarId || null;
|
||||
this.email = obj && obj.email || null;
|
||||
this.skypeId = obj && obj.skypeId;
|
||||
this.googleId = obj && obj.googleId;
|
||||
this.instantMessageId = obj && obj.instantMessageId;
|
||||
this.jobTitle = obj && obj.jobTitle || null;
|
||||
this.location = obj && obj.location || null;
|
||||
this.company = obj && obj.company;
|
||||
this.mobile = obj && obj.mobile;
|
||||
this.telephone = obj && obj.telephone;
|
||||
this.statusUpdatedAt = obj && obj.statusUpdatedAt;
|
||||
this.userStatus = obj && obj.userStatus;
|
||||
this.enabled = obj && obj.enabled;
|
||||
this.emailNotificationsEnabled = obj && obj.emailNotificationsEnabled;
|
||||
this.aspectNames = obj && obj.aspectNames;
|
||||
this.properties = obj && obj.properties;
|
||||
this.capabilities = obj && obj.capabilities;
|
||||
}
|
||||
|
||||
isAdmin(): boolean {
|
||||
return this.capabilities?.isAdmin;
|
||||
}
|
||||
}
|
@@ -1,48 +0,0 @@
|
||||
/*!
|
||||
* @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 { FileModel } from './file.model';
|
||||
|
||||
describe('FileModel', () => {
|
||||
|
||||
describe('extension', () => {
|
||||
|
||||
it('should return the extension if file has it', () => {
|
||||
const file = new FileModel({ name: 'tyrion-lannister.doc' } as File);
|
||||
|
||||
expect(file.extension).toBe('doc');
|
||||
});
|
||||
|
||||
it('should return the empty string if file has NOT got it', () => {
|
||||
const file = new FileModel({ name: 'daenerys-targaryen' } as File);
|
||||
|
||||
expect(file.extension).toBe('');
|
||||
});
|
||||
|
||||
it('should return the empty string if file is starting with . and doesn\'t have extension', () => {
|
||||
const file = new FileModel({ name: '.white-walkers' } as File);
|
||||
|
||||
expect(file.extension).toBe('');
|
||||
});
|
||||
|
||||
it('should return the last extension string if file contains many dot', () => {
|
||||
const file = new FileModel({ name: 'you.know.nothing.jon.snow.exe' } as File);
|
||||
|
||||
expect(file.extension).toBe('exe');
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,123 +0,0 @@
|
||||
/*!
|
||||
* @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 { AssocChildBody, AssociationBody } from '@alfresco/js-api';
|
||||
|
||||
export interface FileUploadProgress {
|
||||
loaded: number;
|
||||
total: number;
|
||||
percent: number;
|
||||
}
|
||||
|
||||
export class FileUploadOptions {
|
||||
/**
|
||||
* Add a version comment which will appear in version history.
|
||||
* Setting this parameter also enables versioning of this node, if it is not already versioned.
|
||||
*/
|
||||
comment?: string;
|
||||
/**
|
||||
* Overwrite the content of the node with a new version.
|
||||
*/
|
||||
newVersion?: boolean;
|
||||
/**
|
||||
* If true, then created node will be version 1.0 MAJOR. If false, then created node will be version 0.1 MINOR.
|
||||
*/
|
||||
majorVersion?: boolean;
|
||||
/**
|
||||
* Root folder id.
|
||||
*/
|
||||
parentId?: string;
|
||||
/**
|
||||
* Defines the **relativePath** value.
|
||||
* The relativePath specifies the folder structure to create relative to the node nodeId.
|
||||
* Folders in the relativePath that do not exist are created before the node is created.
|
||||
*/
|
||||
path?: string;
|
||||
/**
|
||||
* You can use the nodeType field to create a specific type. The default is **cm:content**.
|
||||
*/
|
||||
nodeType?: string;
|
||||
/**
|
||||
* You can set multi-value properties when you create a new node which supports properties of type multiple.
|
||||
*/
|
||||
properties?: any;
|
||||
/**
|
||||
* If the content model allows then it is also possible to create primary children with a different assoc type.
|
||||
*/
|
||||
association?: any;
|
||||
/**
|
||||
* You can optionally specify an array of **secondaryChildren** to create one or more secondary child associations,
|
||||
* such that the newly created node acts as a parent node.
|
||||
*/
|
||||
secondaryChildren?: AssocChildBody[];
|
||||
/**
|
||||
* You can optionally specify an array of **targets** to create one or more peer associations such that the newly created node acts as a source node.
|
||||
*/
|
||||
targets?: AssociationBody[];
|
||||
/**
|
||||
* If true, then created node will be versioned. If false, then created node will be unversioned and auto-versioning disabled.
|
||||
*/
|
||||
versioningEnabled?: boolean;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-shadow
|
||||
export enum FileUploadStatus {
|
||||
Pending = 0,
|
||||
Complete = 1,
|
||||
Starting = 2,
|
||||
Progress = 3,
|
||||
Cancelled = 4,
|
||||
Aborted = 5,
|
||||
Error = 6,
|
||||
Deleted = 7
|
||||
}
|
||||
|
||||
export class FileModel {
|
||||
readonly name: string;
|
||||
readonly size: number;
|
||||
readonly file: File;
|
||||
|
||||
id: string;
|
||||
status: FileUploadStatus = FileUploadStatus.Pending;
|
||||
errorCode: number;
|
||||
progress: FileUploadProgress;
|
||||
options: FileUploadOptions;
|
||||
data: any;
|
||||
|
||||
constructor(file: File, options?: FileUploadOptions, id?: string) {
|
||||
this.file = file;
|
||||
this.id = id;
|
||||
this.name = file.name;
|
||||
this.size = file.size;
|
||||
this.data = null;
|
||||
this.errorCode = null;
|
||||
|
||||
this.progress = {
|
||||
loaded: 0,
|
||||
total: 0,
|
||||
percent: 0
|
||||
};
|
||||
|
||||
this.options = Object.assign({}, {
|
||||
newVersion: false
|
||||
}, options);
|
||||
}
|
||||
|
||||
get extension(): string {
|
||||
return this.name.slice((Math.max(0, this.name.lastIndexOf('.')) || Infinity) + 1);
|
||||
}
|
||||
}
|
@@ -15,8 +15,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { EcmUserModel } from './ecm-user.model';
|
||||
import { IdentityUserModel } from '../auth/models/identity-user.model';
|
||||
import { UserProcessModel } from './user-process.model';
|
||||
|
||||
export type User = (EcmUserModel | UserProcessModel | IdentityUserModel) & { displayName?: string } & { username?: string };
|
||||
export type User = (IdentityUserModel) & { displayName?: string } & { username?: string };
|
||||
|
@@ -1,32 +0,0 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
/* spellchecker: disable */
|
||||
export class PermissionsEnum extends String {
|
||||
static CONTRIBUTOR: string = 'Contributor';
|
||||
static CONSUMER: string = 'Consumer';
|
||||
static COLLABORATOR: string = 'Collaborator';
|
||||
static MANAGER: string = 'Manager';
|
||||
static EDITOR: string = 'Editor';
|
||||
static COORDINATOR: string = 'Coordinator';
|
||||
static NOT_CONTRIBUTOR: string = '!Contributor';
|
||||
static NOT_CONSUMER: string = '!Consumer';
|
||||
static NOT_COLLABORATOR: string = '!Collaborator';
|
||||
static NOT_MANAGER: string = '!Manager';
|
||||
static NOT_EDITOR: string = '!Editor';
|
||||
static NOT_COORDINATOR: string = '!Coordinator';
|
||||
}
|
@@ -15,19 +15,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './file.model';
|
||||
export * from './allowable-operations.enum';
|
||||
export * from './permissions.enum';
|
||||
export * from './product-version.model';
|
||||
export * from './user-process.model';
|
||||
export * from './comment.model';
|
||||
export * from './ecm-company.model';
|
||||
export * from './pagination.model';
|
||||
export * from './request-pagination.model';
|
||||
export * from './decimal-number.model';
|
||||
export * from './bpm-user.model';
|
||||
export * from './ecm-user.model';
|
||||
export * from './node-metadata.model';
|
||||
export * from './application-access.model';
|
||||
export * from './user-access.model';
|
||||
export * from './general-user.model';
|
||||
|
@@ -1,45 +0,0 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This object represent the process service user.*
|
||||
*/
|
||||
|
||||
import { LightUserRepresentation } from '@alfresco/js-api';
|
||||
|
||||
export class UserProcessModel implements LightUserRepresentation {
|
||||
id?: number;
|
||||
email?: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
pictureId?: number;
|
||||
externalId?: string;
|
||||
userImage?: string;
|
||||
|
||||
constructor(input?: any) {
|
||||
if (input) {
|
||||
this.id = input.id;
|
||||
this.email = input.email || null;
|
||||
this.firstName = input.firstName || null;
|
||||
this.lastName = input.lastName || null;
|
||||
this.pictureId = input.pictureId || null;
|
||||
this.externalId = input.externalId || null;
|
||||
this.userImage = input.userImage;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -16,16 +16,16 @@
|
||||
*/
|
||||
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { User } from '../models/general-user.model';
|
||||
import { UserLike } from './user-like.interface';
|
||||
|
||||
@Pipe({ name: 'fullName' })
|
||||
export class FullNamePipe implements PipeTransform {
|
||||
|
||||
transform(user: User): string {
|
||||
transform(user: UserLike): string {
|
||||
return this.buildFullName(user) ? this.buildFullName(user) : this.buildFromUsernameOrEmail(user);
|
||||
}
|
||||
|
||||
buildFullName(user: User): string {
|
||||
buildFullName(user: UserLike): string {
|
||||
const fullName: string[] = [];
|
||||
|
||||
fullName.push(user?.firstName);
|
||||
@@ -34,7 +34,7 @@ export class FullNamePipe implements PipeTransform {
|
||||
return fullName.join(' ').trim();
|
||||
}
|
||||
|
||||
buildFromUsernameOrEmail(user: User): string {
|
||||
buildFromUsernameOrEmail(user: UserLike): string {
|
||||
return (user?.username || user?.email) ?? '' ;
|
||||
}
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@
|
||||
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||
import { User } from '../models/general-user.model';
|
||||
import { UserLike } from './user-like.interface';
|
||||
|
||||
@Pipe({
|
||||
name: 'usernameInitials'
|
||||
@@ -27,7 +27,7 @@ export class InitialUsernamePipe implements PipeTransform {
|
||||
constructor(private sanitized: DomSanitizer) {
|
||||
}
|
||||
|
||||
transform(user: User, className: string = '', delimiter: string = ''): SafeHtml {
|
||||
transform(user: UserLike & { displayName?: string }, className: string = '', delimiter: string = ''): SafeHtml {
|
||||
let safeHtml: SafeHtml = '';
|
||||
if (user) {
|
||||
const initialResult = this.getInitialUserName(user.firstName || user.displayName || user.username, user.lastName, delimiter);
|
||||
|
@@ -15,12 +15,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export class NodeMetadata {
|
||||
metadata: any;
|
||||
nodeType: string;
|
||||
|
||||
constructor(metadata: any, nodeType: string) {
|
||||
this.metadata = metadata;
|
||||
this.nodeType = nodeType;
|
||||
}
|
||||
}
|
||||
export interface UserLike {
|
||||
username?: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
};
|
@@ -1,90 +0,0 @@
|
||||
/*!
|
||||
* @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 { fakeAsync, TestBed } from '@angular/core/testing';
|
||||
import { BpmUserModel } from '../models/bpm-user.model';
|
||||
import { BpmUserService } from './bpm-user.service';
|
||||
import { setupTestBed } from '../testing/setup-test-bed';
|
||||
import { CoreTestingModule } from '../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
describe('Bpm user service', () => {
|
||||
|
||||
let service: BpmUserService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
service = TestBed.inject(BpmUserService);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jasmine.Ajax.install();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
describe('when user is logged in', () => {
|
||||
|
||||
it('should be able to retrieve the user information', (done) => {
|
||||
service.getCurrentUserInfo().subscribe((user: BpmUserModel) => {
|
||||
expect(user).toBeDefined();
|
||||
expect(user.id).toBe(1);
|
||||
expect(user.lastName).toBe('fake-last-name');
|
||||
expect(user.fullname).toBe('fake-full-name');
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({
|
||||
lastName: 'fake-last-name',
|
||||
fullname: 'fake-full-name',
|
||||
groups: [],
|
||||
id: 1
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it('should retrieve avatar url for current user', () => {
|
||||
const path = service.getCurrentUserProfileImage();
|
||||
expect(path).toBeDefined();
|
||||
expect(path).toContain('/app/rest/admin/profile-picture');
|
||||
});
|
||||
|
||||
it('should catch errors on call for profile', fakeAsync(() => {
|
||||
service.getCurrentUserInfo().subscribe(() => {
|
||||
}, (error) => {
|
||||
expect(error).toEqual({ error: new Error('Unsuccessful HTTP response') });
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 403
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
@@ -1,75 +0,0 @@
|
||||
/*!
|
||||
* @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 { Observable, from, throwError } from 'rxjs';
|
||||
import { AlfrescoApiService } from './alfresco-api.service';
|
||||
import { LogService } from '../common/services/log.service';
|
||||
import { BpmUserModel } from '../models/bpm-user.model';
|
||||
import { map, catchError } from 'rxjs/operators';
|
||||
import { UserProfileApi } from '@alfresco/js-api';
|
||||
|
||||
/**
|
||||
*
|
||||
* BPMUserService retrieve all the information of an Ecm user.
|
||||
*
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class BpmUserService {
|
||||
|
||||
private _profileApi: UserProfileApi;
|
||||
get profileApi(): UserProfileApi {
|
||||
this._profileApi = this._profileApi ?? new UserProfileApi(this.apiService.getInstance());
|
||||
return this._profileApi;
|
||||
}
|
||||
|
||||
constructor(private apiService: AlfrescoApiService,
|
||||
private logService: LogService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets information about the current user.
|
||||
*
|
||||
* @returns User information object
|
||||
*/
|
||||
getCurrentUserInfo(): Observable<BpmUserModel> {
|
||||
return from(this.profileApi.getProfile())
|
||||
.pipe(
|
||||
map((userRepresentation) => new BpmUserModel(userRepresentation)),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current user's profile image as a URL.
|
||||
*
|
||||
* @returns URL string
|
||||
*/
|
||||
getCurrentUserProfileImage(): string {
|
||||
return this.profileApi.getProfilePictureUrl();
|
||||
}
|
||||
|
||||
private handleError(error: any) {
|
||||
// in a real world app, we may send the error to some remote logging infrastructure
|
||||
// instead of just logging it to the console
|
||||
this.logService.error(error);
|
||||
return throwError(error || 'Server error');
|
||||
}
|
||||
|
||||
}
|
@@ -1,183 +0,0 @@
|
||||
/*!
|
||||
* @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 { TestBed } from '@angular/core/testing';
|
||||
import { ContentService } from './content.service';
|
||||
import { AppConfigService } from '../app-config/app-config.service';
|
||||
import { AuthenticationService } from '../auth/services/authentication.service';
|
||||
import { StorageService } from '../common/services/storage.service';
|
||||
import { setupTestBed } from '../testing/setup-test-bed';
|
||||
import { Node } from '@alfresco/js-api';
|
||||
import { CoreTestingModule } from '../testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
describe('ContentService', () => {
|
||||
|
||||
let contentService: ContentService;
|
||||
let authService: AuthenticationService;
|
||||
let storage: StorageService;
|
||||
let node: any;
|
||||
|
||||
const nodeId = 'fake-node-id';
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
authService = TestBed.inject(AuthenticationService);
|
||||
contentService = TestBed.inject(ContentService);
|
||||
storage = TestBed.inject(StorageService);
|
||||
storage.clear();
|
||||
|
||||
node = {
|
||||
entry: {
|
||||
id: nodeId
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.Ajax.install();
|
||||
|
||||
const appConfig: AppConfigService = TestBed.inject(AppConfigService);
|
||||
appConfig.config = {
|
||||
ecmHost: 'http://localhost:9876/ecm',
|
||||
provider: 'ECM'
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
it('should return a valid content URL', (done) => {
|
||||
authService.login('fake-username', 'fake-password').subscribe(() => {
|
||||
expect(contentService.getContentUrl(node)).toContain('/ecm/alfresco/api/' +
|
||||
'-default-/public/alfresco/versions/1/nodes/fake-node-id/content?attachment=false&alf_ticket=fake-post-ticket');
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 201,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({ entry: { id: 'fake-post-ticket', userId: 'admin' } })
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a valid thumbnail URL', (done) => {
|
||||
authService.login('fake-username', 'fake-password').subscribe(() => {
|
||||
expect(contentService.getDocumentThumbnailUrl(node))
|
||||
.toContain('/ecm/alfresco/api/-default-/public/alfresco' +
|
||||
'/versions/1/nodes/fake-node-id/renditions/doclib/content?attachment=false&alf_ticket=fake-post-ticket');
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 201,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({ entry: { id: 'fake-post-ticket', userId: 'admin' } })
|
||||
});
|
||||
});
|
||||
|
||||
describe('AllowableOperations', () => {
|
||||
|
||||
it('should hasAllowableOperations be false if allowableOperation is not present in the node', () => {
|
||||
const permissionNode = new Node({});
|
||||
expect(contentService.hasAllowableOperations(permissionNode, 'create')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should hasAllowableOperations be true if allowableOperation is present and you have the permission for the request operation', () => {
|
||||
const permissionNode = new Node({ allowableOperations: ['delete', 'update', 'create', 'updatePermissions'] });
|
||||
|
||||
expect(contentService.hasAllowableOperations(permissionNode, 'create')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should hasAllowableOperations be false if allowableOperation is present but you don\'t have the permission for the request operation', () => {
|
||||
const permissionNode = new Node({ allowableOperations: ['delete', 'update', 'updatePermissions'] });
|
||||
expect(contentService.hasAllowableOperations(permissionNode, 'create')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should hasAllowableOperations works in the opposite way with negate value', () => {
|
||||
const permissionNode = new Node({ allowableOperations: ['delete', 'update', 'updatePermissions'] });
|
||||
expect(contentService.hasAllowableOperations(permissionNode, '!create')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should hasAllowableOperations return false if no permission parameter are passed', () => {
|
||||
const permissionNode = new Node({ allowableOperations: ['delete', 'update', 'updatePermissions'] });
|
||||
expect(contentService.hasAllowableOperations(permissionNode, null)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should havePermission return true if permission parameter is copy', () => {
|
||||
const permissionNode = null;
|
||||
expect(contentService.hasAllowableOperations(permissionNode, 'copy')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Permissions', () => {
|
||||
|
||||
it('should havePermission be false if allowableOperation is not present in the node', () => {
|
||||
const permissionNode = new Node({});
|
||||
expect(contentService.hasPermissions(permissionNode, 'manager')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should havePermission be true if permissions is present and you have the permission for the request operation', () => {
|
||||
const permissionNode = new Node({ permissions: { locallySet: [{ name: 'manager', authorityId: 'user1' }, { name: 'collaborator', authorityId: 'user2' }, { name: 'consumer', authorityId: 'user3' }] } });
|
||||
|
||||
expect(contentService.hasPermissions(permissionNode, 'manager', 'user1')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should havePermission be false if permissions is present but you don\'t have the permission for the request operation', () => {
|
||||
const permissionNode = new Node({ permissions: { locallySet: [{ name: 'collaborator', authorityId: 'user1' }, { name: 'consumer', authorityId: 'user2' }] } });
|
||||
expect(contentService.hasPermissions(permissionNode, 'manager', 'user1')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should havePermission works in the opposite way with negate value', () => {
|
||||
const permissionNode = new Node({ permissions: { locallySet: [{ name: 'collaborator', authorityId: 'user1' }, { name: 'consumer', authorityId: 'user2' }] } });
|
||||
expect(contentService.hasPermissions(permissionNode, '!manager', 'user1')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should havePermission return false if no permission parameter are passed', () => {
|
||||
const permissionNode = new Node({ permissions: { locallySet: [{ name: 'collaborator', authorityId: 'user1' }, { name: 'consumer', authorityId: 'user2' }] } });
|
||||
expect(contentService.hasPermissions(permissionNode, null, 'user1')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should havePermission return true if the permissions is empty and the permission to check is Consumer', () => {
|
||||
const permissionNode = new Node({ permissions: [] });
|
||||
expect(contentService.hasPermissions(permissionNode, 'Consumer', 'user1')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should havePermission return false if the permissions is empty and the permission to check is not Consumer', () => {
|
||||
const permissionNode = new Node({ permissions: [] });
|
||||
expect(contentService.hasPermissions(permissionNode, '!Consumer', 'user1')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should havePermission be true if inherited permissions is present and you have the permission for the request operation', () => {
|
||||
const permissionNode = new Node({ permissions: { inherited: [{ name: 'manager', authorityId: 'user1' }, { name: 'collaborator', authorityId: 'user2' } ] } });
|
||||
expect(contentService.hasPermissions(permissionNode, 'manager', 'user1')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should take current logged user id if userId undefined ', () => {
|
||||
spyOn(authService, 'getEcmUsername').and.returnValue('user1');
|
||||
const permissionNode = new Node({ permissions: { inherited: [{ name: 'manager', authorityId: 'user1' }, { name: 'collaborator', authorityId: 'user2' } ] } });
|
||||
expect(contentService.hasPermissions(permissionNode, 'manager')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,226 +0,0 @@
|
||||
/*!
|
||||
* @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 { DomSanitizer } from '@angular/platform-browser';
|
||||
import { ContentApi, MinimalNode, Node, NodeEntry, NodesApi } from '@alfresco/js-api';
|
||||
import { Observable, Subject, from, throwError } from 'rxjs';
|
||||
import { AlfrescoApiService } from './alfresco-api.service';
|
||||
import { AuthenticationService } from '../auth/services/authentication.service';
|
||||
import { LogService } from '../common/services/log.service';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { PermissionsEnum } from '../models/permissions.enum';
|
||||
import { AllowableOperationsEnum } from '../models/allowable-operations.enum';
|
||||
import { DownloadService } from './download.service';
|
||||
import { ThumbnailService } from '../common/services/thumbnail.service';
|
||||
|
||||
export interface FolderCreatedEvent {
|
||||
name: string;
|
||||
relativePath?: string;
|
||||
parentId?: string;
|
||||
node?: NodeEntry;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ContentService {
|
||||
|
||||
folderCreated: Subject<FolderCreatedEvent> = new Subject<FolderCreatedEvent>();
|
||||
folderCreate: Subject<MinimalNode> = new Subject<MinimalNode>();
|
||||
folderEdit: Subject<MinimalNode> = new Subject<MinimalNode>();
|
||||
|
||||
private _contentApi: ContentApi;
|
||||
get contentApi(): ContentApi {
|
||||
this._contentApi = this._contentApi ?? new ContentApi(this.apiService.getInstance());
|
||||
return this._contentApi;
|
||||
}
|
||||
|
||||
private _nodesApi: NodesApi;
|
||||
get nodesApi(): NodesApi {
|
||||
this._nodesApi = this._nodesApi ?? new NodesApi(this.apiService.getInstance());
|
||||
return this._nodesApi;
|
||||
}
|
||||
|
||||
constructor(public authService: AuthenticationService,
|
||||
public apiService: AlfrescoApiService,
|
||||
private logService: LogService,
|
||||
private sanitizer: DomSanitizer,
|
||||
private downloadService: DownloadService,
|
||||
private thumbnailService: ThumbnailService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated in 3.2.0, use DownloadService instead.
|
||||
* Invokes content download for a Blob with a file name.
|
||||
* @param blob Content to download.
|
||||
* @param fileName Name of the resulting file.
|
||||
*/
|
||||
downloadBlob(blob: Blob, fileName: string): void {
|
||||
this.downloadService.downloadBlob(blob, fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a trusted object URL from the Blob.
|
||||
* WARNING: calling this method with untrusted user data exposes your application to XSS security risks!
|
||||
*
|
||||
* @param blob Data to wrap into object URL
|
||||
* @returns URL string
|
||||
*/
|
||||
createTrustedUrl(blob: Blob): string {
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
return this.sanitizer.bypassSecurityTrustUrl(url) as string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated in 3.2.0, use ThumbnailService instead.
|
||||
* Gets a thumbnail URL for the given document node.
|
||||
* @param node Node or Node ID to get URL for.
|
||||
* @param attachment Toggles whether to retrieve content as an attachment for download
|
||||
* @param ticket Custom ticket to use for authentication
|
||||
* @returns URL string
|
||||
*/
|
||||
getDocumentThumbnailUrl(node: NodeEntry | string, attachment?: boolean, ticket?: string): string {
|
||||
return this.thumbnailService.getDocumentThumbnailUrl(node, attachment, ticket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a content URL for the given node.
|
||||
*
|
||||
* @param node Node or Node ID to get URL for.
|
||||
* @param attachment Toggles whether to retrieve content as an attachment for download
|
||||
* @param ticket Custom ticket to use for authentication
|
||||
* @returns URL string or `null`
|
||||
*/
|
||||
getContentUrl(node: NodeEntry | string, attachment?: boolean, ticket?: string): string {
|
||||
if (node) {
|
||||
let nodeId: string;
|
||||
|
||||
if (typeof node === 'string') {
|
||||
nodeId = node;
|
||||
} else if (node.entry) {
|
||||
nodeId = node.entry.id;
|
||||
}
|
||||
|
||||
return this.contentApi.getContentUrl(nodeId, attachment, ticket);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets content for the given node.
|
||||
*
|
||||
* @param nodeId ID of the target node
|
||||
* @returns Content data
|
||||
*/
|
||||
getNodeContent(nodeId: string): Observable<any> {
|
||||
return from(this.nodesApi.getNodeContent(nodeId))
|
||||
.pipe(
|
||||
catchError((err: any) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Node via its node ID.
|
||||
*
|
||||
* @param nodeId ID of the target node
|
||||
* @param opts Options supported by JS-API
|
||||
* @returns Details of the folder
|
||||
*/
|
||||
getNode(nodeId: string, opts?: any): Observable<NodeEntry> {
|
||||
return from(this.nodesApi.getNode(nodeId, opts));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user has permission on that node
|
||||
*
|
||||
* @param node Node to check permissions
|
||||
* @param permission Required permission type
|
||||
* @param userId Optional current user id will be taken by default
|
||||
* @returns True if the user has the required permissions, false otherwise
|
||||
*/
|
||||
hasPermissions(node: Node, permission: PermissionsEnum | string, userId?: string): boolean {
|
||||
let hasPermissions = false;
|
||||
userId = userId ?? this.authService.getEcmUsername();
|
||||
|
||||
const permissions = [...(node.permissions?.locallySet || []), ...(node.permissions?.inherited || [])]
|
||||
.filter((currentPermission) => currentPermission.authorityId === userId);
|
||||
if (permissions.length) {
|
||||
if (permission && permission.startsWith('!')) {
|
||||
hasPermissions = !permissions.find((currentPermission) => currentPermission.name === permission.replace('!', ''));
|
||||
} else {
|
||||
hasPermissions = !!permissions.find((currentPermission) => currentPermission.name === permission);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (permission === PermissionsEnum.CONSUMER) {
|
||||
hasPermissions = true;
|
||||
} else if (permission === PermissionsEnum.NOT_CONSUMER) {
|
||||
hasPermissions = false;
|
||||
} else if (permission && permission.startsWith('!')) {
|
||||
hasPermissions = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user has permissions on that node
|
||||
*
|
||||
* @param node Node to check allowableOperations
|
||||
* @param allowableOperation Create, delete, update, updatePermissions, !create, !delete, !update, !updatePermissions
|
||||
* @returns True if the user has the required permissions, false otherwise
|
||||
*/
|
||||
hasAllowableOperations(node: Node, allowableOperation: AllowableOperationsEnum | string): boolean {
|
||||
let hasAllowableOperations = false;
|
||||
|
||||
if (node && node.allowableOperations) {
|
||||
if (allowableOperation && allowableOperation.startsWith('!')) {
|
||||
hasAllowableOperations = !node.allowableOperations.find((currentOperation) => currentOperation === allowableOperation.replace('!', ''));
|
||||
} else {
|
||||
hasAllowableOperations = !!node.allowableOperations.find((currentOperation) => currentOperation === allowableOperation);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (allowableOperation && allowableOperation.startsWith('!')) {
|
||||
hasAllowableOperations = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (allowableOperation === AllowableOperationsEnum.COPY) {
|
||||
hasAllowableOperations = true;
|
||||
}
|
||||
|
||||
if (allowableOperation === AllowableOperationsEnum.LOCK) {
|
||||
hasAllowableOperations = node.isFile;
|
||||
|
||||
if (node.isLocked && node.allowableOperations) {
|
||||
hasAllowableOperations = !!~node.allowableOperations.indexOf('updatePermissions');
|
||||
}
|
||||
}
|
||||
|
||||
return hasAllowableOperations;
|
||||
}
|
||||
|
||||
private handleError(error: any) {
|
||||
this.logService.error(error);
|
||||
return throwError(error || 'Server error');
|
||||
}
|
||||
}
|
@@ -1,68 +0,0 @@
|
||||
/*!
|
||||
* @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 { Observable, from, of } from 'rxjs';
|
||||
|
||||
import { NodePaging, NodesApi, TrashcanApi } from '@alfresco/js-api';
|
||||
import { AlfrescoApiService } from './alfresco-api.service';
|
||||
import { UserPreferencesService } from '../common/services/user-preferences.service';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class DeletedNodesApiService {
|
||||
|
||||
private _nodesApi: NodesApi;
|
||||
get nodesApi(): NodesApi {
|
||||
this._nodesApi = this._nodesApi ?? new NodesApi(this.apiService.getInstance());
|
||||
return this._nodesApi;
|
||||
}
|
||||
|
||||
private _trashcanApi: TrashcanApi;
|
||||
get trashcanApi(): TrashcanApi {
|
||||
this._trashcanApi = this._trashcanApi ?? new TrashcanApi(this.apiService.getInstance());
|
||||
return this._trashcanApi;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private apiService: AlfrescoApiService,
|
||||
private preferences: UserPreferencesService
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of nodes in the trash.
|
||||
*
|
||||
* @param options Options for JS-API call
|
||||
* @returns List of nodes in the trash
|
||||
*/
|
||||
getDeletedNodes(options?: any): Observable<NodePaging> {
|
||||
const defaultOptions = {
|
||||
include: [ 'path', 'properties' ],
|
||||
maxItems: this.preferences.paginationSize,
|
||||
skipCount: 0
|
||||
};
|
||||
const queryOptions = Object.assign(defaultOptions, options);
|
||||
const promise = this.trashcanApi.listDeletedNodes(queryOptions);
|
||||
|
||||
return from(promise).pipe(
|
||||
catchError((err) => of(err))
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,97 +0,0 @@
|
||||
/*!
|
||||
* @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 { from, Observable, throwError, Subject } from 'rxjs';
|
||||
import { catchError, map, switchMap, filter, take } from 'rxjs/operators';
|
||||
import { RepositoryInfo, SystemPropertiesRepresentation } from '@alfresco/js-api';
|
||||
|
||||
import { BpmProductVersionModel } from '../models/product-version.model';
|
||||
import { AlfrescoApiService } from './alfresco-api.service';
|
||||
import { AuthenticationService } from '../auth/services/authentication.service';
|
||||
import { ApiClientsService } from '@alfresco/adf-core/api';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class DiscoveryApiService {
|
||||
|
||||
/**
|
||||
* Gets product information for Content Services.
|
||||
*/
|
||||
ecmProductInfo$ = new Subject<RepositoryInfo>();
|
||||
|
||||
constructor(
|
||||
private apiService: AlfrescoApiService,
|
||||
private authenticationService: AuthenticationService,
|
||||
private apiClientsService: ApiClientsService
|
||||
) {
|
||||
this.authenticationService.onLogin
|
||||
.pipe(
|
||||
filter(() => this.apiService.getInstance()?.isEcmLoggedIn()),
|
||||
take(1),
|
||||
switchMap(() => this.getEcmProductInfo())
|
||||
)
|
||||
.subscribe((info) => this.ecmProductInfo$.next(info));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets product information for Content Services.
|
||||
*
|
||||
* @returns ProductVersionModel containing product details
|
||||
*/
|
||||
getEcmProductInfo(): Observable<RepositoryInfo> {
|
||||
const discoveryApi = this.apiClientsService.get('DiscoveryClient.discovery');
|
||||
|
||||
return from(discoveryApi.getRepositoryInformation())
|
||||
.pipe(
|
||||
map((res) => res.entry.repository),
|
||||
catchError((err) => throwError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets product information for Process Services.
|
||||
*
|
||||
* @returns ProductVersionModel containing product details
|
||||
*/
|
||||
getBpmProductInfo(): Observable<BpmProductVersionModel> {
|
||||
const aboutApi = this.apiClientsService.get('ActivitiClient.about');
|
||||
|
||||
return from(aboutApi.getAppVersion())
|
||||
.pipe(
|
||||
map((res) => new BpmProductVersionModel(res)),
|
||||
catchError((err) => throwError(err))
|
||||
);
|
||||
}
|
||||
|
||||
getBPMSystemProperties(): Observable<SystemPropertiesRepresentation> {
|
||||
const systemPropertiesApi = this.apiClientsService.get('ActivitiClient.system-properties');
|
||||
|
||||
return from(systemPropertiesApi.getProperties())
|
||||
.pipe(
|
||||
map((res: any) => {
|
||||
if ('string' === typeof (res)) {
|
||||
throw new Error('Not valid response');
|
||||
}
|
||||
return res;
|
||||
}),
|
||||
catchError((err) => throwError(err.error))
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,75 +0,0 @@
|
||||
/*!
|
||||
* @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 { DownloadEntry, DownloadBodyCreate, DownloadsApi } from '@alfresco/js-api';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, from, throwError } from 'rxjs';
|
||||
import { LogService } from '../common/services/log.service';
|
||||
import { AlfrescoApiService } from './alfresco-api.service';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class DownloadZipService {
|
||||
|
||||
private _downloadsApi: DownloadsApi;
|
||||
get downloadsApi(): DownloadsApi {
|
||||
this._downloadsApi = this._downloadsApi ?? new DownloadsApi(this.apiService.getInstance());
|
||||
return this._downloadsApi;
|
||||
}
|
||||
|
||||
constructor(private apiService: AlfrescoApiService,
|
||||
private logService: LogService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new download.
|
||||
*
|
||||
* @param payload Object containing the node IDs of the items to add to the ZIP file
|
||||
* @returns Status object for the download
|
||||
*/
|
||||
createDownload(payload: DownloadBodyCreate): Observable<DownloadEntry> {
|
||||
return from(this.downloadsApi.createDownload(payload)).pipe(
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets status information for a download node.
|
||||
*
|
||||
* @param downloadId ID of the download node
|
||||
* @returns Status object for the download
|
||||
*/
|
||||
getDownload(downloadId: string): Observable<DownloadEntry> {
|
||||
return from(this.downloadsApi.getDownload(downloadId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a download.
|
||||
*
|
||||
* @param downloadId ID of the target download node
|
||||
*/
|
||||
cancelDownload(downloadId: string) {
|
||||
this.downloadsApi.cancelDownload(downloadId);
|
||||
}
|
||||
|
||||
private handleError(error: any) {
|
||||
this.logService.error(error);
|
||||
return throwError(error || 'Server error');
|
||||
}
|
||||
}
|
@@ -1,71 +0,0 @@
|
||||
/*!
|
||||
* @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 { TestBed } from '@angular/core/testing';
|
||||
import { fakeEcmUser } from '../mock/ecm-user.service.mock';
|
||||
import { EcmUserService } from './ecm-user.service';
|
||||
import { CoreTestingModule } from '../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AuthenticationService } from '../auth/services/authentication.service';
|
||||
import { ContentService } from './content.service';
|
||||
|
||||
describe('EcmUserService', () => {
|
||||
|
||||
let service: EcmUserService;
|
||||
let authService: AuthenticationService;
|
||||
let contentService: ContentService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
service = TestBed.inject(EcmUserService);
|
||||
authService = TestBed.inject(AuthenticationService);
|
||||
contentService = TestBed.inject(ContentService);
|
||||
});
|
||||
|
||||
describe('when user is logged in', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(authService, 'isEcmLoggedIn').and.returnValue(true);
|
||||
});
|
||||
|
||||
it('should be able to retrieve current user info', (done) => {
|
||||
spyOn(service.peopleApi, 'getPerson').and.returnValue(Promise.resolve({ entry: fakeEcmUser } as any));
|
||||
service.getCurrentUserInfo().subscribe(
|
||||
(user) => {
|
||||
expect(user).toBeDefined();
|
||||
expect(user.firstName).toEqual('fake-ecm-first-name');
|
||||
expect(user.lastName).toEqual('fake-ecm-last-name');
|
||||
expect(user.email).toEqual('fakeEcm@ecmUser.com');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should retrieve avatar url for current user', () => {
|
||||
spyOn(contentService, 'getContentUrl').and.returnValue('fake/url/image/for/ecm/user');
|
||||
const urlRs = service.getUserProfileImage('fake-avatar-id');
|
||||
|
||||
expect(urlRs).toEqual('fake/url/image/for/ecm/user');
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,72 +0,0 @@
|
||||
/*!
|
||||
* @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 { Observable, from } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { ContentService } from './content.service';
|
||||
import { AlfrescoApiService } from './alfresco-api.service';
|
||||
import { EcmUserModel } from '../models/ecm-user.model';
|
||||
import { PeopleApi } from '@alfresco/js-api';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class EcmUserService {
|
||||
|
||||
private _peopleApi: PeopleApi;
|
||||
get peopleApi(): PeopleApi {
|
||||
this._peopleApi = this._peopleApi ?? new PeopleApi(this.apiService.getInstance());
|
||||
return this._peopleApi;
|
||||
}
|
||||
|
||||
constructor(private apiService: AlfrescoApiService,
|
||||
private contentService: ContentService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets information about a user identified by their username.
|
||||
*
|
||||
* @param userName Target username
|
||||
* @returns User information
|
||||
*/
|
||||
getUserInfo(userName: string): Observable<EcmUserModel> {
|
||||
return from(this.peopleApi.getPerson(userName))
|
||||
.pipe(
|
||||
map((personEntry) => new EcmUserModel(personEntry.entry))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets information about the user who is currently logged-in.
|
||||
*
|
||||
* @returns User information as for getUserInfo
|
||||
*/
|
||||
getCurrentUserInfo() {
|
||||
return this.getUserInfo('-me-');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a profile image as a URL.
|
||||
*
|
||||
* @param avatarId Target avatar
|
||||
* @returns Image URL
|
||||
*/
|
||||
getUserProfileImage(avatarId: string): string {
|
||||
return this.contentService.getContentUrl(avatarId);
|
||||
}
|
||||
}
|
@@ -1,246 +0,0 @@
|
||||
/*!
|
||||
* @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 { MinimalNode, NodeEntry, NodePaging, NodesApi, TrashcanApi, Node } from '@alfresco/js-api';
|
||||
import { Subject, from, Observable, throwError } from 'rxjs';
|
||||
import { AlfrescoApiService } from './alfresco-api.service';
|
||||
import { UserPreferencesService } from '../common/services/user-preferences.service';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { NodeMetadata } from '../models/node-metadata.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class NodesApiService {
|
||||
|
||||
/**
|
||||
* Publish/subscribe to events related to node updates.
|
||||
*/
|
||||
nodeUpdated = new Subject<Node>();
|
||||
|
||||
private _trashcanApi: TrashcanApi;
|
||||
get trashcanApi(): TrashcanApi {
|
||||
this._trashcanApi = this._trashcanApi ?? new TrashcanApi(this.apiService.getInstance());
|
||||
return this._trashcanApi;
|
||||
}
|
||||
|
||||
private _nodesApi: NodesApi;
|
||||
get nodesApi(): NodesApi {
|
||||
this._nodesApi = this._nodesApi ?? new NodesApi(this.apiService.getInstance());
|
||||
return this._nodesApi;
|
||||
}
|
||||
|
||||
constructor(private apiService: AlfrescoApiService,
|
||||
private preferences: UserPreferencesService) {
|
||||
}
|
||||
|
||||
private getEntryFromEntity(entity: NodeEntry) {
|
||||
return entity.entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stored information about a node.
|
||||
*
|
||||
* @param nodeId ID of the target node
|
||||
* @param options Optional parameters supported by JS-API
|
||||
* @returns Node information
|
||||
*/
|
||||
getNode(nodeId: string, options: any = {}): Observable<MinimalNode> {
|
||||
const defaults = {
|
||||
include: ['path', 'properties', 'allowableOperations', 'permissions']
|
||||
};
|
||||
const queryOptions = Object.assign(defaults, options);
|
||||
|
||||
return from(this.nodesApi.getNode(nodeId, queryOptions)).pipe(
|
||||
map(this.getEntryFromEntity),
|
||||
catchError((err) => throwError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the items contained in a folder node.
|
||||
*
|
||||
* @param nodeId ID of the target node
|
||||
* @param options Optional parameters supported by JS-API
|
||||
* @returns List of child items from the folder
|
||||
*/
|
||||
getNodeChildren(nodeId: string, options: any = {}): Observable<NodePaging> {
|
||||
const defaults = {
|
||||
maxItems: this.preferences.paginationSize,
|
||||
skipCount: 0,
|
||||
include: ['path', 'properties', 'allowableOperations', 'permissions']
|
||||
};
|
||||
const queryOptions = Object.assign(defaults, options);
|
||||
|
||||
return from(this.nodesApi.listNodeChildren(nodeId, queryOptions)).pipe(
|
||||
catchError((err) => throwError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new document node inside a folder.
|
||||
*
|
||||
* @param parentNodeId ID of the parent folder node
|
||||
* @param nodeBody Data for the new node
|
||||
* @param options Optional parameters supported by JS-API
|
||||
* @returns Details of the new node
|
||||
*/
|
||||
createNode(parentNodeId: string, nodeBody: any, options: any = {}): Observable<MinimalNode> {
|
||||
return from(this.nodesApi.createNode(parentNodeId, nodeBody, options)).pipe(
|
||||
map(this.getEntryFromEntity),
|
||||
catchError((err) => throwError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new folder node inside a parent folder.
|
||||
*
|
||||
* @param parentNodeId ID of the parent folder node
|
||||
* @param nodeBody Data for the new folder
|
||||
* @param options Optional parameters supported by JS-API
|
||||
* @returns Details of the new folder
|
||||
*/
|
||||
createFolder(parentNodeId: string, nodeBody: any, options: any = {}): Observable<MinimalNode> {
|
||||
const body = Object.assign({ nodeType: 'cm:folder' }, nodeBody);
|
||||
return this.createNode(parentNodeId, body, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the information about a node.
|
||||
*
|
||||
* @param nodeId ID of the target node
|
||||
* @param nodeBody New data for the node
|
||||
* @param options Optional parameters supported by JS-API
|
||||
* @returns Updated node information
|
||||
*/
|
||||
updateNode(nodeId: string, nodeBody: any, options: any = {}): Observable<MinimalNode> {
|
||||
const defaults = {
|
||||
include: ['path', 'properties', 'allowableOperations', 'permissions', 'definition']
|
||||
};
|
||||
const queryOptions = Object.assign(defaults, options);
|
||||
|
||||
return from(this.nodesApi.updateNode(nodeId, nodeBody, queryOptions)).pipe(
|
||||
map(this.getEntryFromEntity),
|
||||
catchError((err) => throwError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves a node to the trashcan.
|
||||
*
|
||||
* @param nodeId ID of the target node
|
||||
* @param options Optional parameters supported by JS-API
|
||||
* @returns Empty result that notifies when the deletion is complete
|
||||
*/
|
||||
deleteNode(nodeId: string, options: any = {}): Observable<any> {
|
||||
return from(this.nodesApi.deleteNode(nodeId, options)).pipe(
|
||||
catchError((err) => throwError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores a node previously moved to the trashcan.
|
||||
*
|
||||
* @param nodeId ID of the node to restore
|
||||
* @returns Details of the restored node
|
||||
*/
|
||||
restoreNode(nodeId: string): Observable<MinimalNode> {
|
||||
return from(this.trashcanApi.restoreDeletedNode(nodeId)).pipe(
|
||||
map(this.getEntryFromEntity),
|
||||
catchError((err) => throwError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the metadata and the nodeType for a nodeId cleaned by the prefix.
|
||||
*
|
||||
* @param nodeId ID of the target node
|
||||
* @returns Node metadata
|
||||
*/
|
||||
public getNodeMetadata(nodeId: string): Observable<NodeMetadata> {
|
||||
return from(this.nodesApi.getNode(nodeId))
|
||||
.pipe(map(this.cleanMetadataFromSemicolon));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Node from form metadata.
|
||||
*
|
||||
* @param path Path to the node
|
||||
* @param nodeType Node type
|
||||
* @param name Node name
|
||||
* @param nameSpace Namespace for properties
|
||||
* @param data Property data to store in the node under namespace
|
||||
* @returns The created node
|
||||
*/
|
||||
public createNodeMetadata(nodeType: string, nameSpace: any, data: any, path: string, name?: string): Observable<NodeEntry> {
|
||||
const properties = {};
|
||||
for (const key in data) {
|
||||
if (data[key]) {
|
||||
properties[nameSpace + ':' + key] = data[key];
|
||||
}
|
||||
}
|
||||
|
||||
return this.createNodeInsideRoot(name || this.generateUuid(), nodeType, properties, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Node inside `-root-` folder
|
||||
*
|
||||
* @param name Node name
|
||||
* @param nodeType Node type
|
||||
* @param properties Node body properties
|
||||
* @param path Path to the node
|
||||
* @returns The created node
|
||||
*/
|
||||
public createNodeInsideRoot(name: string, nodeType: string, properties: any, path: string): Observable<NodeEntry> {
|
||||
const body = {
|
||||
name,
|
||||
nodeType,
|
||||
properties,
|
||||
relativePath: path
|
||||
};
|
||||
return from(this.nodesApi.createNode('-root-', body, {}));
|
||||
}
|
||||
|
||||
private generateUuid() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||
const r = Math.random() * 16 | 0;
|
||||
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
private cleanMetadataFromSemicolon(nodeEntry: NodeEntry): NodeMetadata {
|
||||
const metadata = {};
|
||||
|
||||
if (nodeEntry && nodeEntry.entry.properties) {
|
||||
for (const key in nodeEntry.entry.properties) {
|
||||
if (key) {
|
||||
if (key.indexOf(':') !== -1) {
|
||||
metadata [key.split(':')[1]] = nodeEntry.entry.properties[key];
|
||||
} else {
|
||||
metadata [key] = nodeEntry.entry.properties[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new NodeMetadata(metadata, nodeEntry.entry.nodeType);
|
||||
}
|
||||
|
||||
}
|
@@ -1,142 +0,0 @@
|
||||
/*!
|
||||
* @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 { fakeEcmUserList, createNewPersonMock, fakeEcmUser, fakeEcmAdminUser } from '../mock/ecm-user.service.mock';
|
||||
import { AlfrescoApiServiceMock } from '../mock/alfresco-api.service.mock';
|
||||
import { CoreTestingModule } from '../testing/core.testing.module';
|
||||
import { PeopleContentService, PeopleContentQueryRequestModel } from './people-content.service';
|
||||
import { AlfrescoApiService } from './alfresco-api.service';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { LogService } from '../common/services/log.service';
|
||||
import { AuthenticationService } from '../auth/services/authentication.service';
|
||||
|
||||
describe('PeopleContentService', () => {
|
||||
|
||||
let peopleContentService: PeopleContentService;
|
||||
let logService: LogService;
|
||||
let authenticationService: AuthenticationService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
],
|
||||
providers: [
|
||||
{ provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock }
|
||||
]
|
||||
});
|
||||
|
||||
authenticationService = TestBed.inject(AuthenticationService);
|
||||
peopleContentService = TestBed.inject(PeopleContentService);
|
||||
logService = TestBed.inject(LogService);
|
||||
});
|
||||
|
||||
it('should be able to fetch person details based on id', async () => {
|
||||
spyOn(peopleContentService.peopleApi, 'getPerson').and.returnValue(Promise.resolve({ entry: fakeEcmUser } as any));
|
||||
const person = await peopleContentService.getPerson('fake-id').toPromise();
|
||||
expect(person.id).toEqual('fake-id');
|
||||
expect(person.email).toEqual('fakeEcm@ecmUser.com');
|
||||
});
|
||||
|
||||
it('should be able to list people', async () => {
|
||||
spyOn(peopleContentService.peopleApi, 'listPeople').and.returnValue(Promise.resolve(fakeEcmUserList));
|
||||
const response = await peopleContentService.listPeople().toPromise();
|
||||
const people = response.entries;
|
||||
const pagination = response.pagination;
|
||||
|
||||
expect(people).toBeDefined();
|
||||
expect(people[0].id).toEqual('fake-id');
|
||||
expect(people[1].id).toEqual('another-fake-id');
|
||||
expect(pagination.count).toEqual(2);
|
||||
expect(pagination.totalItems).toEqual(2);
|
||||
expect(pagination.hasMoreItems).toBeFalsy();
|
||||
expect(pagination.skipCount).toEqual(0);
|
||||
|
||||
});
|
||||
|
||||
it('should call listPeople api with requested sorting params', async () => {
|
||||
const listPeopleSpy = spyOn(peopleContentService.peopleApi, 'listPeople').and.returnValue(Promise.resolve(fakeEcmUserList));
|
||||
const requestQueryParams: PeopleContentQueryRequestModel = {
|
||||
skipCount: 10,
|
||||
maxItems: 20,
|
||||
sorting: { orderBy: 'firstName', direction: 'asc' }
|
||||
};
|
||||
const expectedValue = { skipCount: 10, maxItems: 20, orderBy: ['firstName ASC'] } as any;
|
||||
|
||||
await peopleContentService.listPeople(requestQueryParams).toPromise();
|
||||
|
||||
expect(listPeopleSpy).toHaveBeenCalledWith(expectedValue);
|
||||
});
|
||||
|
||||
it('should not call listPeople api with sorting params if sorting is not defined', async () => {
|
||||
const listPeopleSpy = spyOn(peopleContentService.peopleApi, 'listPeople').and.returnValue(Promise.resolve(fakeEcmUserList));
|
||||
const requestQueryParams: PeopleContentQueryRequestModel = { skipCount: 10, maxItems: 20, sorting: undefined };
|
||||
const expectedValue = { skipCount: 10, maxItems: 20 };
|
||||
|
||||
await peopleContentService.listPeople(requestQueryParams).toPromise();
|
||||
|
||||
expect(listPeopleSpy).toHaveBeenCalledWith(expectedValue);
|
||||
});
|
||||
|
||||
it('should be able to create new person', async () => {
|
||||
spyOn(peopleContentService.peopleApi, 'createPerson').and.returnValue(Promise.resolve({ entry: fakeEcmUser } as any));
|
||||
const newUser = await peopleContentService.createPerson(createNewPersonMock).toPromise();
|
||||
expect(newUser.id).toEqual('fake-id');
|
||||
expect(newUser.email).toEqual('fakeEcm@ecmUser.com');
|
||||
});
|
||||
|
||||
it('should be able to throw an error if createPerson api failed', (done) => {
|
||||
spyOn(peopleContentService.peopleApi, 'createPerson').and.returnValue(Promise.reject('failed to create new person'));
|
||||
const logErrorSpy = spyOn(logService, 'error');
|
||||
peopleContentService.createPerson(createNewPersonMock).subscribe(
|
||||
() => {
|
||||
},
|
||||
(error) => {
|
||||
expect(logErrorSpy).toHaveBeenCalledWith('failed to create new person');
|
||||
expect(error).toEqual('failed to create new person');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('Should make the api call to check if the user is a content admin only once', async () => {
|
||||
const getCurrentPersonSpy = spyOn(peopleContentService.peopleApi, 'getPerson').and.returnValue(Promise.resolve({ entry: fakeEcmAdminUser } as any));
|
||||
|
||||
const user = await peopleContentService.getCurrentUserInfo().toPromise();
|
||||
expect(user.id).toEqual('fake-id');
|
||||
expect(peopleContentService.isCurrentUserAdmin()).toBe(true);
|
||||
expect(getCurrentPersonSpy.calls.count()).toEqual(1);
|
||||
|
||||
await peopleContentService.getCurrentUserInfo().toPromise();
|
||||
|
||||
expect(await peopleContentService.isCurrentUserAdmin()).toBe(true);
|
||||
expect(getCurrentPersonSpy.calls.count()).toEqual(1);
|
||||
});
|
||||
|
||||
it('should reset the admin cache upon logout', async () => {
|
||||
spyOn(peopleContentService.peopleApi, 'getPerson').and.returnValue(Promise.resolve({ entry: fakeEcmAdminUser } as any));
|
||||
|
||||
const user = await peopleContentService.getCurrentUserInfo().toPromise();
|
||||
expect(user.id).toEqual('fake-id');
|
||||
expect(peopleContentService.isCurrentUserAdmin()).toBe(true);
|
||||
|
||||
authenticationService.onLogout.next(true);
|
||||
expect(peopleContentService.isCurrentUserAdmin()).toBe(false);
|
||||
});
|
||||
});
|
@@ -1,188 +0,0 @@
|
||||
/*!
|
||||
* @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 { Observable, from, throwError, of } from 'rxjs';
|
||||
import { AlfrescoApiService } from './alfresco-api.service';
|
||||
import { catchError, map, tap } from 'rxjs/operators';
|
||||
import { PeopleApi, PersonBodyCreate, Pagination, PersonBodyUpdate } from '@alfresco/js-api';
|
||||
import { EcmUserModel } from '../models/ecm-user.model';
|
||||
import { LogService } from '../common/services/log.service';
|
||||
import { AuthenticationService } from '../auth/services/authentication.service';
|
||||
import { ContentService } from './content.service';
|
||||
|
||||
// eslint-disable-next-line no-shadow
|
||||
export enum ContentGroups {
|
||||
ALFRESCO_ADMINISTRATORS = 'ALFRESCO_ADMINISTRATORS'
|
||||
}
|
||||
|
||||
export interface PeopleContentQueryResponse {
|
||||
pagination: Pagination;
|
||||
entries: EcmUserModel[];
|
||||
}
|
||||
|
||||
export interface PeopleContentSortingModel {
|
||||
orderBy: string;
|
||||
direction: string;
|
||||
}
|
||||
|
||||
export interface PeopleContentQueryRequestModel {
|
||||
skipCount?: number;
|
||||
maxItems?: number;
|
||||
sorting?: PeopleContentSortingModel;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class PeopleContentService {
|
||||
private currentUser: EcmUserModel;
|
||||
|
||||
private _peopleApi: PeopleApi;
|
||||
get peopleApi(): PeopleApi {
|
||||
this._peopleApi = this._peopleApi ?? new PeopleApi(this.apiService.getInstance());
|
||||
return this._peopleApi;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private apiService: AlfrescoApiService,
|
||||
authenticationService: AuthenticationService,
|
||||
private logService: LogService,
|
||||
private contentService: ContentService
|
||||
) {
|
||||
authenticationService.onLogout.subscribe(() => {
|
||||
this.resetLocalCurrentUser();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets information about a user identified by their username.
|
||||
*
|
||||
* @param personId ID of the target user
|
||||
* @returns User information
|
||||
*/
|
||||
getPerson(personId: string): Observable<EcmUserModel> {
|
||||
return from(this.peopleApi.getPerson(personId))
|
||||
.pipe(
|
||||
map((personEntry) => new EcmUserModel(personEntry.entry)),
|
||||
tap( user => this.currentUser = user),
|
||||
catchError((error) => this.handleError(error)));
|
||||
}
|
||||
|
||||
getCurrentPerson(): Observable<EcmUserModel> {
|
||||
return this.getCurrentUserInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets information about the current user alias -me-
|
||||
*
|
||||
* @returns User information
|
||||
*/
|
||||
getCurrentUserInfo(): Observable<EcmUserModel> {
|
||||
if (this.currentUser) {
|
||||
return of(this.currentUser);
|
||||
}
|
||||
return this.getPerson('-me-');
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to know if the current user has the admin capability
|
||||
*
|
||||
* @returns true or false
|
||||
*/
|
||||
isCurrentUserAdmin(): boolean {
|
||||
return this.currentUser?.isAdmin() ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the local current user object
|
||||
*/
|
||||
resetLocalCurrentUser() {
|
||||
this.currentUser = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of people.
|
||||
*
|
||||
* @param requestQuery maxItems and skipCount parameters supported by JS-API
|
||||
* @returns Response containing pagination and list of entries
|
||||
*/
|
||||
listPeople(requestQuery?: PeopleContentQueryRequestModel): Observable<PeopleContentQueryResponse> {
|
||||
const requestQueryParams = { skipCount: requestQuery?.skipCount, maxItems: requestQuery?.maxItems };
|
||||
const orderBy = this.buildOrderArray(requestQuery?.sorting);
|
||||
if (orderBy.length) {
|
||||
requestQueryParams['orderBy'] = orderBy;
|
||||
}
|
||||
|
||||
const promise = this.peopleApi.listPeople(requestQueryParams);
|
||||
return from(promise).pipe(
|
||||
map(response => ({
|
||||
pagination: response.list.pagination,
|
||||
entries: response.list.entries.map((person) => person.entry as EcmUserModel)
|
||||
})),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new person.
|
||||
*
|
||||
* @param newPerson Object containing the new person details.
|
||||
* @param opts Optional parameters
|
||||
* @returns Created new person
|
||||
*/
|
||||
createPerson(newPerson: PersonBodyCreate, opts?: any): Observable<EcmUserModel> {
|
||||
return from(this.peopleApi.createPerson(newPerson, opts)).pipe(
|
||||
map((res) => res?.entry as EcmUserModel),
|
||||
catchError((error) => this.handleError(error))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the person details
|
||||
*
|
||||
* @param personId The identifier of a person
|
||||
* @param details The person details
|
||||
* @param opts Optional parameters
|
||||
* @returns Updated person model
|
||||
*/
|
||||
updatePerson(personId: string, details: PersonBodyUpdate, opts?: any): Observable<EcmUserModel> {
|
||||
return from(this.peopleApi.updatePerson(personId, details, opts)).pipe(
|
||||
map((res) => res?.entry as EcmUserModel),
|
||||
catchError((error) => this.handleError(error))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a profile image as a URL.
|
||||
*
|
||||
* @param avatarId Target avatar
|
||||
* @returns Image URL
|
||||
*/
|
||||
getUserProfileImage(avatarId: string): string {
|
||||
return this.contentService.getContentUrl(avatarId);
|
||||
}
|
||||
|
||||
private buildOrderArray(sorting: PeopleContentSortingModel): string[] {
|
||||
return sorting?.orderBy && sorting?.direction ? [`${sorting.orderBy} ${sorting.direction.toUpperCase()}`] : [];
|
||||
}
|
||||
|
||||
private handleError(error: any) {
|
||||
this.logService.error(error);
|
||||
return throwError(error || 'Server error');
|
||||
}
|
||||
}
|
@@ -1,181 +0,0 @@
|
||||
/*!
|
||||
* @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 { fakeAsync, TestBed } from '@angular/core/testing';
|
||||
import { UserProcessModel } from '../models';
|
||||
import { PeopleProcessService } from './people-process.service';
|
||||
import { setupTestBed } from '../testing/setup-test-bed';
|
||||
import { CoreTestingModule } from '../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
const firstInvolvedUser: UserProcessModel = new UserProcessModel({
|
||||
id: 1,
|
||||
email: 'fake-user1@fake.com',
|
||||
firstName: 'fakeName1',
|
||||
lastName: 'fakeLast1'
|
||||
});
|
||||
|
||||
const secondInvolvedUser: UserProcessModel = new UserProcessModel({
|
||||
id: 2,
|
||||
email: 'fake-user2@fake.com',
|
||||
firstName: 'fakeName2',
|
||||
lastName: 'fakeLast2'
|
||||
});
|
||||
|
||||
const fakeInvolveUserList: UserProcessModel[] = [firstInvolvedUser, secondInvolvedUser];
|
||||
|
||||
const errorResponse = { error: new Error('Unsuccessful HTTP response') };
|
||||
|
||||
describe('PeopleProcessService', () => {
|
||||
|
||||
let service: PeopleProcessService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
service = TestBed.inject(PeopleProcessService);
|
||||
});
|
||||
|
||||
describe('when user is logged in', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jasmine.Ajax.install();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
it('should be able to retrieve people to involve in the task', fakeAsync(() => {
|
||||
service.getWorkflowUsers('fake-task-id', 'fake-filter').subscribe(
|
||||
(users: UserProcessModel[]) => {
|
||||
expect(users).toBeDefined();
|
||||
expect(users.length).toBe(2);
|
||||
expect(users[0].id).toEqual(1);
|
||||
expect(users[0].email).toEqual('fake-user1@fake.com');
|
||||
expect(users[0].firstName).toEqual('fakeName1');
|
||||
expect(users[0].lastName).toEqual('fakeLast1');
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: {data: fakeInvolveUserList}
|
||||
});
|
||||
}));
|
||||
|
||||
it('should be able to get people images for people retrieved', fakeAsync(() => {
|
||||
service.getWorkflowUsers('fake-task-id', 'fake-filter').subscribe(
|
||||
(users: UserProcessModel[]) => {
|
||||
expect(users).toBeDefined();
|
||||
expect(users.length).toBe(2);
|
||||
expect(service.getUserImage(users[0])).toContain('/users/' + users[0].id + '/picture');
|
||||
expect(service.getUserImage(users[1])).toContain('/users/' + users[1].id + '/picture');
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: {data: fakeInvolveUserList}
|
||||
});
|
||||
}));
|
||||
|
||||
it('should return user image url', () => {
|
||||
const url = service.getUserImage(firstInvolvedUser);
|
||||
|
||||
expect(url).toContain('/users/' + firstInvolvedUser.id + '/picture');
|
||||
});
|
||||
|
||||
it('should return empty list when there are no users to involve', fakeAsync(() => {
|
||||
service.getWorkflowUsers('fake-task-id', 'fake-filter').subscribe(
|
||||
(users: UserProcessModel[]) => {
|
||||
expect(users).toBeDefined();
|
||||
expect(users.length).toBe(0);
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: {}
|
||||
});
|
||||
}));
|
||||
|
||||
it('getWorkflowUsers catch errors call', fakeAsync(() => {
|
||||
service.getWorkflowUsers('fake-task-id', 'fake-filter').subscribe(() => {
|
||||
}, (error) => {
|
||||
expect(error).toEqual(errorResponse);
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 403
|
||||
});
|
||||
}));
|
||||
|
||||
it('should be able to involve people in the task', fakeAsync(() => {
|
||||
service.involveUserWithTask('fake-task-id', 'fake-user-id').subscribe(
|
||||
() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().method).toBe('PUT');
|
||||
expect(jasmine.Ajax.requests.mostRecent().url).toContain('tasks/fake-task-id/action/involve');
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200
|
||||
});
|
||||
}));
|
||||
|
||||
it('involveUserWithTask catch errors call', fakeAsync(() => {
|
||||
service.involveUserWithTask('fake-task-id', 'fake-user-id').subscribe(() => {
|
||||
}, (error) => {
|
||||
expect(error).toEqual(errorResponse);
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 403
|
||||
});
|
||||
}));
|
||||
|
||||
it('should be able to remove involved people from task', fakeAsync(() => {
|
||||
service.removeInvolvedUser('fake-task-id', 'fake-user-id').subscribe(
|
||||
() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().method).toBe('PUT');
|
||||
expect(jasmine.Ajax.requests.mostRecent().url).toContain('tasks/fake-task-id/action/remove-involved');
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200
|
||||
});
|
||||
}));
|
||||
|
||||
it('removeInvolvedUser catch errors call', fakeAsync(() => {
|
||||
service.removeInvolvedUser('fake-task-id', 'fake-user-id').subscribe(() => {
|
||||
}, (error) => {
|
||||
expect(error).toEqual(errorResponse);
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 403
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
@@ -1,159 +0,0 @@
|
||||
/*!
|
||||
* @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 { Observable, from, throwError, of } from 'rxjs';
|
||||
import { UserProcessModel } from '../models/user-process.model';
|
||||
import { AlfrescoApiService } from './alfresco-api.service';
|
||||
import { LogService } from '../common/services/log.service';
|
||||
import { catchError, combineAll, defaultIfEmpty, map, switchMap } from 'rxjs/operators';
|
||||
import {
|
||||
TaskActionsApi,
|
||||
UsersApi,
|
||||
ResultListDataRepresentationLightUserRepresentation, ActivitiGroupsApi
|
||||
} from '@alfresco/js-api';
|
||||
import { GroupModel } from '../form';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class PeopleProcessService {
|
||||
|
||||
private _taskActionsApi: TaskActionsApi;
|
||||
get taskActionsApi(): TaskActionsApi {
|
||||
this._taskActionsApi = this._taskActionsApi ?? new TaskActionsApi(this.apiService.getInstance());
|
||||
return this._taskActionsApi;
|
||||
}
|
||||
|
||||
private _userApi: UsersApi;
|
||||
get userApi(): UsersApi {
|
||||
this._userApi = this._userApi ?? new UsersApi(this.apiService.getInstance());
|
||||
return this._userApi;
|
||||
}
|
||||
|
||||
private _groupsApi: ActivitiGroupsApi;
|
||||
get groupsApi(): ActivitiGroupsApi {
|
||||
this._groupsApi = this._groupsApi ?? new ActivitiGroupsApi(this.apiService.getInstance());
|
||||
return this._groupsApi;
|
||||
}
|
||||
|
||||
constructor(private apiService: AlfrescoApiService,
|
||||
private logService: LogService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of groups in a workflow.
|
||||
*
|
||||
* @param filter Filter to select specific groups
|
||||
* @param groupId Group ID for the search
|
||||
* @returns Array of groups
|
||||
*/
|
||||
getWorkflowGroups(filter: string, groupId?: string): Observable<GroupModel[]> {
|
||||
const option: any = { filter };
|
||||
if (groupId) {
|
||||
option.groupId = groupId;
|
||||
}
|
||||
return from(this.groupsApi.getGroups(option))
|
||||
.pipe(
|
||||
map((response: any) => response.data || []),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets information about users across all tasks.
|
||||
*
|
||||
* @param taskId ID of the task
|
||||
* @param searchWord Filter text to search for
|
||||
* @returns Array of user information objects
|
||||
*/
|
||||
getWorkflowUsers(taskId?: string, searchWord?: string, groupId?: string): Observable<UserProcessModel[]> {
|
||||
const option = { excludeTaskId: taskId, filter: searchWord, groupId };
|
||||
|
||||
return from(this.getWorkflowUserApi(option))
|
||||
.pipe(
|
||||
switchMap(response => response.data as UserProcessModel[] || []),
|
||||
map((user) => {
|
||||
user.userImage = this.getUserProfileImageApi(user.id.toString());
|
||||
return of(user);
|
||||
}),
|
||||
combineAll(),
|
||||
defaultIfEmpty([]),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Gets the profile picture URL for the specified user.
|
||||
*
|
||||
* @param user The target user
|
||||
* @returns Profile picture URL
|
||||
*/
|
||||
getUserImage(user: UserProcessModel): string {
|
||||
return this.getUserProfileImageApi(user.id.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a user to be involved with a task.
|
||||
*
|
||||
* @param taskId ID of the target task
|
||||
* @param idToInvolve ID of the user to involve
|
||||
* @returns Empty response when the update completes
|
||||
*/
|
||||
involveUserWithTask(taskId: string, idToInvolve: string): Observable<UserProcessModel[]> {
|
||||
const node = { userId: idToInvolve };
|
||||
return from(this.involveUserToTaskApi(taskId, node))
|
||||
.pipe(
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a user who is currently involved with a task.
|
||||
*
|
||||
* @param taskId ID of the target task
|
||||
* @param idToRemove ID of the user to remove
|
||||
* @returns Empty response when the update completes
|
||||
*/
|
||||
removeInvolvedUser(taskId: string, idToRemove: string): Observable<UserProcessModel[]> {
|
||||
const node = { userId: idToRemove };
|
||||
return from(this.removeInvolvedUserFromTaskApi(taskId, node))
|
||||
.pipe(
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
private getWorkflowUserApi(options: any): Promise<ResultListDataRepresentationLightUserRepresentation> {
|
||||
return this.userApi.getUsers(options);
|
||||
}
|
||||
|
||||
private involveUserToTaskApi(taskId: string, node: any) {
|
||||
return this.taskActionsApi.involveUser(taskId, node);
|
||||
}
|
||||
|
||||
private removeInvolvedUserFromTaskApi(taskId: string, node: any) {
|
||||
return this.taskActionsApi.removeInvolvedUser(taskId, node);
|
||||
}
|
||||
|
||||
private getUserProfileImageApi(userId: string): string {
|
||||
return this.userApi.getUserProfilePictureUrl(userId);
|
||||
}
|
||||
|
||||
private handleError(error: any) {
|
||||
this.logService.error(error);
|
||||
return throwError(error || 'Server error');
|
||||
}
|
||||
}
|
@@ -16,17 +16,3 @@
|
||||
*/
|
||||
|
||||
export * from './alfresco-api.service';
|
||||
export * from './content.service';
|
||||
export * from './renditions.service';
|
||||
export * from './upload.service';
|
||||
export * from './deleted-nodes-api.service';
|
||||
export * from './nodes-api.service';
|
||||
export * from './people-content.service';
|
||||
export * from './people-process.service';
|
||||
export * from './discovery-api.service';
|
||||
export * from './download-zip.service';
|
||||
export * from './download.service';
|
||||
export * from './bpm-user.service';
|
||||
export * from './ecm-user.service';
|
||||
export * from './user-access.service';
|
||||
export * from './user-info-resolver.service';
|
||||
|
@@ -1,191 +0,0 @@
|
||||
/*!
|
||||
* @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 { fakeAsync, TestBed } from '@angular/core/testing';
|
||||
import { fakeRendition, fakeRenditionCreated, fakeRenditionsList, fakeRenditionsListWithACreated } from '../mock/renditions-service.mock';
|
||||
import { RenditionsService } from './renditions.service';
|
||||
import { setupTestBed } from '../testing/setup-test-bed';
|
||||
import { RenditionEntry } from '@alfresco/js-api';
|
||||
import { CoreTestingModule } from '../testing/core.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
const errorResponse = { error: new Error('Parser is unable to parse the response') };
|
||||
|
||||
describe('RenditionsService', () => {
|
||||
let service: RenditionsService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jasmine.Ajax.install();
|
||||
service = TestBed.inject(RenditionsService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
it('Should return the image rendition for the file if no rendition is already available', (done) => {
|
||||
service.getAvailableRenditionForNode('fake-node-id').subscribe((res: RenditionEntry) => {
|
||||
expect(res.entry.status).toBe('NOT_CREATED');
|
||||
expect(res.entry.id).toBe('imgpreview');
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeRenditionsList)
|
||||
});
|
||||
});
|
||||
|
||||
it('Should return the available rendition for the file', (done) => {
|
||||
service.getAvailableRenditionForNode('fake-node-id').subscribe((res: RenditionEntry) => {
|
||||
expect(res.entry.status).toBe('CREATED');
|
||||
expect(res.entry.id).toBe('pdf');
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeRenditionsListWithACreated)
|
||||
});
|
||||
});
|
||||
|
||||
it('Get rendition list service should return the list', (done) => {
|
||||
service.getRenditionsListByNodeId('fake-node-id').subscribe((res) => {
|
||||
expect(res.list.entries[0].entry.id).toBe('avatar');
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeRenditionsList)
|
||||
});
|
||||
});
|
||||
|
||||
it('Create rendition service should call the server with the ID passed and the asked encoding', (done) => {
|
||||
service.createRendition('fake-node-id', 'pdf').subscribe(() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().method).toBe('POST');
|
||||
expect(jasmine.Ajax.requests.mostRecent().url).toContain('/ecm/alfresco/api/-default-/public/alfresco/versions/1/nodes/fake-node-id/renditions');
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: ''
|
||||
});
|
||||
});
|
||||
|
||||
describe('convert', () => {
|
||||
|
||||
it('should call the server with the ID passed and the asked encoding for creation', (done) => {
|
||||
service.convert('fake-node-id', 'pdf', 1000);
|
||||
|
||||
expect(jasmine.Ajax.requests.mostRecent().method).toBe('POST');
|
||||
expect(jasmine.Ajax.requests.mostRecent().url).toContain('/ecm/alfresco/api/-default-/public/alfresco/versions/1/nodes/fake-node-id/renditions');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Get rendition service should catch the error', fakeAsync(() => {
|
||||
service.getRenditionsListByNodeId('fake-node-id').subscribe(() => {
|
||||
}, (error) => {
|
||||
expect(error).toEqual(errorResponse);
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 403,
|
||||
contentType: 'application/json',
|
||||
responseText: 'error'
|
||||
});
|
||||
}));
|
||||
|
||||
it('isConversionPossible should return true if is possible convert', (done) => {
|
||||
service.isConversionPossible('fake-node-id', 'pdf').subscribe((res) => {
|
||||
expect(res).toBe(true);
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeRendition)
|
||||
});
|
||||
});
|
||||
|
||||
it('isConversionPossible should return false if is not possible to convert', (done) => {
|
||||
service.isConversionPossible('fake-node-id', 'pdf').subscribe((res) => {
|
||||
expect(res).toBe(false);
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 403,
|
||||
contentType: 'application/json'
|
||||
});
|
||||
});
|
||||
|
||||
it('isRenditionsAvailable should return true if the conversion exist', (done) => {
|
||||
service.isRenditionAvailable('fake-node-id', 'pdf').subscribe((res) => {
|
||||
expect(res).toBe(true);
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeRenditionCreated)
|
||||
});
|
||||
});
|
||||
|
||||
it('isRenditionsAvailable should return false if the conversion not exist', (done) => {
|
||||
service.isRenditionAvailable('fake-node-id', 'pdf').subscribe((res) => {
|
||||
expect(res).toBe(false);
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeRendition)
|
||||
});
|
||||
});
|
||||
|
||||
it('isRenditionsAvailable should return false if the conversion get error', (done) => {
|
||||
service.isRenditionAvailable('fake-node-id', 'pdf').subscribe((res) => {
|
||||
expect(res).toBe(false);
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 400,
|
||||
contentType: 'application/json'
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,199 +0,0 @@
|
||||
/*!
|
||||
* @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 { RenditionEntry, RenditionPaging, RenditionsApi, ContentApi } from '@alfresco/js-api';
|
||||
import { Observable, from, interval, empty } from 'rxjs';
|
||||
import { AlfrescoApiService } from './alfresco-api.service';
|
||||
import { concatMap, switchMap, takeWhile, map } from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class RenditionsService {
|
||||
|
||||
private _renditionsApi: RenditionsApi;
|
||||
get renditionsApi(): RenditionsApi {
|
||||
this._renditionsApi = this._renditionsApi ?? new RenditionsApi(this.apiService.getInstance());
|
||||
return this._renditionsApi;
|
||||
}
|
||||
|
||||
private _contentApi: ContentApi;
|
||||
get contentApi(): ContentApi {
|
||||
this._contentApi = this._contentApi ?? new ContentApi(this.apiService.getInstance());
|
||||
return this._contentApi;
|
||||
}
|
||||
|
||||
constructor(private apiService: AlfrescoApiService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first available rendition found for a node.
|
||||
*
|
||||
* @param nodeId ID of the target node
|
||||
* @returns Information object for the rendition
|
||||
*/
|
||||
getAvailableRenditionForNode(nodeId: string): Observable<RenditionEntry> {
|
||||
return from(this.renditionsApi.listRenditions(nodeId)).pipe(
|
||||
map((availableRenditions: RenditionPaging) => {
|
||||
const renditionsAvailable: RenditionEntry[] = availableRenditions.list.entries.filter(
|
||||
(rendition) => (rendition.entry.id === 'pdf' || rendition.entry.id === 'imgpreview'));
|
||||
const existingRendition = renditionsAvailable.find((rend) => rend.entry.status === 'CREATED');
|
||||
return existingRendition ? existingRendition : renditionsAvailable[0];
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a rendition for a node using the first available encoding.
|
||||
*
|
||||
* @param nodeId ID of the target node
|
||||
* @returns Null response to indicate completion
|
||||
*/
|
||||
generateRenditionForNode(nodeId: string): Observable<any> {
|
||||
return this.getAvailableRenditionForNode(nodeId).pipe(
|
||||
map((rendition: RenditionEntry) => {
|
||||
if (rendition.entry.status !== 'CREATED') {
|
||||
return from(this.renditionsApi.createRendition(nodeId, { id: rendition.entry.id }));
|
||||
} else {
|
||||
return empty();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified rendition is available for a node.
|
||||
*
|
||||
* @param nodeId ID of the target node
|
||||
* @param encoding Name of the rendition encoding
|
||||
* @returns True if the rendition is available, false otherwise
|
||||
*/
|
||||
isRenditionAvailable(nodeId: string, encoding: string): Observable<boolean> {
|
||||
return new Observable((observer) => {
|
||||
this.getRendition(nodeId, encoding).subscribe(
|
||||
(res) => {
|
||||
let isAvailable = true;
|
||||
if (res.entry.status.toString() === 'NOT_CREATED') {
|
||||
isAvailable = false;
|
||||
}
|
||||
observer.next(isAvailable);
|
||||
observer.complete();
|
||||
},
|
||||
() => {
|
||||
observer.next(false);
|
||||
observer.complete();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the node can be converted using the specified rendition.
|
||||
*
|
||||
* @param nodeId ID of the target node
|
||||
* @param encoding Name of the rendition encoding
|
||||
* @returns True if the node can be converted, false otherwise
|
||||
*/
|
||||
isConversionPossible(nodeId: string, encoding: string): Observable<boolean> {
|
||||
return new Observable((observer) => {
|
||||
this.getRendition(nodeId, encoding).subscribe(
|
||||
() => {
|
||||
observer.next(true);
|
||||
observer.complete();
|
||||
},
|
||||
() => {
|
||||
observer.next(false);
|
||||
observer.complete();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a URL linking to the specified rendition of a node.
|
||||
*
|
||||
* @param nodeId ID of the target node
|
||||
* @param encoding Name of the rendition encoding
|
||||
* @returns URL string
|
||||
*/
|
||||
getRenditionUrl(nodeId: string, encoding: string): string {
|
||||
return this.contentApi.getRenditionUrl(nodeId, encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets information about a rendition of a node.
|
||||
*
|
||||
* @param nodeId ID of the target node
|
||||
* @param encoding Name of the rendition encoding
|
||||
* @returns Information object about the rendition
|
||||
*/
|
||||
getRendition(nodeId: string, encoding: string): Observable<RenditionEntry> {
|
||||
return from(this.renditionsApi.getRendition(nodeId, encoding));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of all renditions for a node.
|
||||
*
|
||||
* @param nodeId ID of the target node
|
||||
* @returns Paged list of rendition details
|
||||
*/
|
||||
getRenditionsListByNodeId(nodeId: string): Observable<RenditionPaging> {
|
||||
return from(this.renditionsApi.listRenditions(nodeId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a rendition for a node.
|
||||
*
|
||||
* @param nodeId ID of the target node
|
||||
* @param encoding Name of the rendition encoding
|
||||
* @returns Null response to indicate completion
|
||||
*/
|
||||
createRendition(nodeId: string, encoding: string): Observable<any> {
|
||||
return from(this.renditionsApi.createRendition(nodeId, { id: encoding }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Repeatedly attempts to create a rendition, through to success or failure.
|
||||
*
|
||||
* @param nodeId ID of the target node
|
||||
* @param encoding Name of the rendition encoding
|
||||
* @param pollingInterval Time interval (in milliseconds) between checks for completion
|
||||
* @param retries Number of attempts to make before declaring failure
|
||||
* @returns True if the rendition was created, false otherwise
|
||||
*/
|
||||
convert(nodeId: string, encoding: string, pollingInterval: number = 1000, retries: number = 5) {
|
||||
return this.createRendition(nodeId, encoding)
|
||||
.pipe(
|
||||
concatMap(() => this.pollRendition(nodeId, encoding, pollingInterval, retries))
|
||||
);
|
||||
}
|
||||
|
||||
private pollRendition(nodeId: string, encoding: string, intervalSize: number = 1000, retries: number = 5) {
|
||||
let attempts = 0;
|
||||
return interval(intervalSize)
|
||||
.pipe(
|
||||
switchMap(() => this.getRendition(nodeId, encoding)),
|
||||
takeWhile((renditionEntry: RenditionEntry) => {
|
||||
attempts += 1;
|
||||
if (attempts > retries) {
|
||||
return false;
|
||||
}
|
||||
return (renditionEntry.entry.status.toString() !== 'CREATED');
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,565 +0,0 @@
|
||||
/*!
|
||||
* @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 { EventEmitter } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { FileModel, FileUploadStatus } from '../models/file.model';
|
||||
import { AppConfigModule } from '../app-config/app-config.module';
|
||||
import { UploadService } from './upload.service';
|
||||
import { AppConfigService } from '../app-config/app-config.service';
|
||||
|
||||
import { setupTestBed } from '../testing/setup-test-bed';
|
||||
import { CoreTestingModule } from '../testing/core.testing.module';
|
||||
import { RepositoryInfo } from '@alfresco/js-api';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { DiscoveryApiService } from './discovery-api.service';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
describe('UploadService', () => {
|
||||
let service: UploadService;
|
||||
let appConfigService: AppConfigService;
|
||||
let uploadFileSpy: jasmine.Spy;
|
||||
|
||||
const mockProductInfo = new BehaviorSubject<RepositoryInfo>(null);
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
CoreTestingModule,
|
||||
AppConfigModule
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: DiscoveryApiService,
|
||||
useValue: {
|
||||
ecmProductInfo$: mockProductInfo
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
appConfigService = TestBed.inject(AppConfigService);
|
||||
appConfigService.config = {
|
||||
ecmHost: 'http://localhost:9876/ecm',
|
||||
files: {
|
||||
excluded: ['.DS_Store', 'desktop.ini', '.git', '*.git', '*.SWF'],
|
||||
'match-options': {
|
||||
/* cspell:disable-next-line */
|
||||
nocase: true
|
||||
}
|
||||
},
|
||||
folders: {
|
||||
excluded: ['ROLLINGPANDA'],
|
||||
'match-options': {
|
||||
/* cspell:disable-next-line */
|
||||
nocase: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
service = TestBed.inject(UploadService);
|
||||
service.queue = [];
|
||||
service.clearCache();
|
||||
|
||||
uploadFileSpy = spyOn(service.uploadApi, 'uploadFile').and.callThrough();
|
||||
|
||||
jasmine.Ajax.install();
|
||||
mockProductInfo.next({ status: { isThumbnailGenerationEnabled: true } } as RepositoryInfo);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
it('should return an empty queue if no elements are added', () => {
|
||||
expect(service.getQueue().length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should add an element in the queue and returns it', () => {
|
||||
const filesFake = new FileModel({ name: 'fake-name', size: 10 } as File);
|
||||
service.addToQueue(filesFake);
|
||||
expect(service.getQueue().length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should add two elements in the queue and returns them', () => {
|
||||
const filesFake = [
|
||||
new FileModel({ name: 'fake-name', size: 10 } as File),
|
||||
new FileModel({ name: 'fake-name2', size: 20 } as File)
|
||||
];
|
||||
service.addToQueue(...filesFake);
|
||||
expect(service.getQueue().length).toEqual(2);
|
||||
});
|
||||
|
||||
it('should not have the queue uploading if all files are complete, cancelled, aborted, errored or deleted', () => {
|
||||
const file1 = new FileModel({ name: 'fake-file-1', size: 10 } as File);
|
||||
const file2 = new FileModel({ name: 'fake-file-2', size: 20 } as File);
|
||||
const file3 = new FileModel({ name: 'fake-file-3', size: 30 } as File);
|
||||
const file4 = new FileModel({ name: 'fake-file-4', size: 40 } as File);
|
||||
const file5 = new FileModel({ name: 'fake-file-5', size: 50 } as File);
|
||||
|
||||
file1.status = FileUploadStatus.Complete;
|
||||
file2.status = FileUploadStatus.Cancelled;
|
||||
file3.status = FileUploadStatus.Aborted;
|
||||
file4.status = FileUploadStatus.Error;
|
||||
file5.status = FileUploadStatus.Deleted;
|
||||
|
||||
service.addToQueue(file1, file2, file3, file4, file5);
|
||||
|
||||
expect(service.isUploading()).toBe(false);
|
||||
});
|
||||
|
||||
it('should have the queue still uploading if some files are still pending, starting or in progress', () => {
|
||||
const file1 = new FileModel({ name: 'fake-file-1', size: 10 } as File);
|
||||
const file2 = new FileModel({ name: 'fake-file-2', size: 20 } as File);
|
||||
|
||||
service.addToQueue(file1, file2);
|
||||
|
||||
file1.status = FileUploadStatus.Complete;
|
||||
file2.status = FileUploadStatus.Pending;
|
||||
expect(service.isUploading()).toBe(true);
|
||||
|
||||
file2.status = FileUploadStatus.Starting;
|
||||
expect(service.isUploading()).toBe(true);
|
||||
|
||||
file2.status = FileUploadStatus.Progress;
|
||||
expect(service.isUploading()).toBe(true);
|
||||
});
|
||||
|
||||
it('should skip hidden macOS files', () => {
|
||||
const file1 = new FileModel(new File([''], '.git'));
|
||||
const file2 = new FileModel(new File([''], 'readme.md'));
|
||||
const result = service.addToQueue(file1, file2);
|
||||
expect(result.length).toBe(1);
|
||||
expect(result[0]).toBe(file2);
|
||||
});
|
||||
|
||||
it('should match the extension in case insensitive way', () => {
|
||||
const file1 = new FileModel(new File([''], 'test.swf'));
|
||||
const file2 = new FileModel(new File([''], 'readme.md'));
|
||||
const result = service.addToQueue(file1, file2);
|
||||
expect(result.length).toBe(1);
|
||||
expect(result[0]).toBe(file2);
|
||||
});
|
||||
|
||||
it('should make XHR done request after the file is added in the queue', (done) => {
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
const emitterDisposable = emitter.subscribe((e) => {
|
||||
expect(e.value).toBe('File uploaded');
|
||||
emitterDisposable.unsubscribe();
|
||||
done();
|
||||
});
|
||||
const fileFake = new FileModel(
|
||||
{ name: 'fake-name', size: 10 } as File,
|
||||
{ parentId: '-root-', path: 'fake-dir' }
|
||||
);
|
||||
service.addToQueue(fileFake);
|
||||
service.uploadFilesInTheQueue(emitter);
|
||||
|
||||
const request = jasmine.Ajax.requests.mostRecent();
|
||||
expect(request.url).toBe('http://localhost:9876/ecm/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root-/children?autoRename=true&include=allowableOperations');
|
||||
expect(request.method).toBe('POST');
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'text/plain',
|
||||
responseText: 'File uploaded'
|
||||
});
|
||||
});
|
||||
|
||||
it('should make XHR error request after an error occur', (done) => {
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
const emitterDisposable = emitter.subscribe((e) => {
|
||||
expect(e.value).toBe('Error file uploaded');
|
||||
emitterDisposable.unsubscribe();
|
||||
done();
|
||||
});
|
||||
const fileFake = new FileModel(
|
||||
{ name: 'fake-name', size: 10 } as File,
|
||||
{ parentId: '-root-' }
|
||||
);
|
||||
service.addToQueue(fileFake);
|
||||
service.uploadFilesInTheQueue(null, emitter);
|
||||
expect(jasmine.Ajax.requests.mostRecent().url)
|
||||
.toBe('http://localhost:9876/ecm/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root-/children?autoRename=true&include=allowableOperations');
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 404,
|
||||
contentType: 'text/plain',
|
||||
responseText: 'Error file uploaded'
|
||||
});
|
||||
});
|
||||
|
||||
it('should abort file only if it is safe to abort', (done) => {
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
const emitterDisposable = emitter.subscribe((event) => {
|
||||
expect(event.value).toEqual('File aborted');
|
||||
emitterDisposable.unsubscribe();
|
||||
done();
|
||||
});
|
||||
|
||||
const fileFake = new FileModel({ name: 'fake-name', size: 10000000 } as File);
|
||||
service.addToQueue(fileFake);
|
||||
service.uploadFilesInTheQueue(emitter);
|
||||
|
||||
const file = service.getQueue();
|
||||
service.cancelUpload(...file);
|
||||
});
|
||||
|
||||
it('should let file complete and then delete node if it is not safe to abort', (done) => {
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
const emitterDisposable = emitter.subscribe((event) => {
|
||||
expect(event.value).toEqual('File deleted');
|
||||
emitterDisposable.unsubscribe();
|
||||
|
||||
const deleteRequest = jasmine.Ajax.requests.mostRecent();
|
||||
expect(deleteRequest.url).toBe('http://localhost:9876/ecm/alfresco/api/-default-/public/alfresco/versions/1/nodes/myNodeId?permanent=true');
|
||||
expect(deleteRequest.method).toBe('DELETE');
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'text/plain',
|
||||
responseText: 'File deleted'
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
const fileFake = new FileModel({ name: 'fake-name', size: 10 } as File);
|
||||
service.addToQueue(fileFake);
|
||||
service.uploadFilesInTheQueue(emitter);
|
||||
|
||||
const file = service.getQueue();
|
||||
service.cancelUpload(...file);
|
||||
|
||||
const request = jasmine.Ajax.requests.mostRecent();
|
||||
expect(request.url).toBe('http://localhost:9876/ecm/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root-/children?autoRename=true&include=allowableOperations');
|
||||
expect(request.method).toBe('POST');
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: {
|
||||
entry: {
|
||||
id: 'myNodeId'
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete node version when cancelling the upload of the new file version', (done) => {
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
const emitterDisposable = emitter.subscribe((event) => {
|
||||
expect(event.value).toEqual('File deleted');
|
||||
emitterDisposable.unsubscribe();
|
||||
|
||||
const deleteRequest = jasmine.Ajax.requests.mostRecent();
|
||||
expect(deleteRequest.url).toBe('http://localhost:9876/ecm/alfresco/api/-default-/public/alfresco/versions/1/nodes/myNodeId/versions/1.1');
|
||||
expect(deleteRequest.method).toBe('DELETE');
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'text/plain',
|
||||
responseText: 'File deleted'
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
const fileFake = new FileModel({name: 'fake-name', size: 10} as File, null, 'fakeId');
|
||||
service.addToQueue(fileFake);
|
||||
service.uploadFilesInTheQueue(emitter);
|
||||
|
||||
const file = service.getQueue();
|
||||
service.cancelUpload(...file);
|
||||
|
||||
const request = jasmine.Ajax.requests.mostRecent();
|
||||
expect(request.url).toBe('http://localhost:9876/ecm/alfresco/api/-default-/public/alfresco/versions/1/nodes/fakeId/content?include=allowableOperations');
|
||||
expect(request.method).toBe('PUT');
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: {
|
||||
entry: {
|
||||
id: 'myNodeId',
|
||||
properties: {
|
||||
'cm:versionLabel': '1.1'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('If newVersion is set, name should be a param', () => {
|
||||
const emitter = new EventEmitter();
|
||||
const filesFake = new FileModel(
|
||||
{ name: 'fake-name', size: 10 } as File,
|
||||
{ newVersion: true }
|
||||
);
|
||||
service.addToQueue(filesFake);
|
||||
service.uploadFilesInTheQueue(emitter);
|
||||
|
||||
expect(uploadFileSpy).toHaveBeenCalledWith(
|
||||
{
|
||||
name: 'fake-name',
|
||||
size: 10
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
{ newVersion: true },
|
||||
{
|
||||
renditions: 'doclib',
|
||||
include: ['allowableOperations'],
|
||||
overwrite: true,
|
||||
majorVersion: undefined,
|
||||
comment: undefined,
|
||||
name: 'fake-name'
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should use custom root folder ID given to the service', (done) => {
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
const emitterDisposable = emitter.subscribe((e) => {
|
||||
expect(e.value).toBe('File uploaded');
|
||||
emitterDisposable.unsubscribe();
|
||||
done();
|
||||
});
|
||||
const filesFake = new FileModel(
|
||||
{ name: 'fake-file-name', size: 10 } as File,
|
||||
{ parentId: '123', path: 'fake-dir' }
|
||||
);
|
||||
service.addToQueue(filesFake);
|
||||
service.uploadFilesInTheQueue(emitter);
|
||||
|
||||
const request = jasmine.Ajax.requests.mostRecent();
|
||||
expect(request.url).toBe('http://localhost:9876/ecm/alfresco/api/-default-/public/alfresco/versions/1/nodes/123/children?autoRename=true&include=allowableOperations');
|
||||
expect(request.method).toBe('POST');
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'text/plain',
|
||||
responseText: 'File uploaded'
|
||||
});
|
||||
});
|
||||
|
||||
describe('versioningEnabled', () => {
|
||||
it('should upload with "versioningEnabled" parameter taken from file options', () => {
|
||||
const model = new FileModel(
|
||||
{ name: 'file-name', size: 10 } as File,
|
||||
{ versioningEnabled: true }
|
||||
);
|
||||
|
||||
service.addToQueue(model);
|
||||
service.uploadFilesInTheQueue();
|
||||
|
||||
expect(uploadFileSpy).toHaveBeenCalledWith(
|
||||
{
|
||||
name: 'file-name',
|
||||
size: 10
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
{ newVersion: false },
|
||||
{
|
||||
include: [ 'allowableOperations' ],
|
||||
renditions: 'doclib',
|
||||
versioningEnabled: true,
|
||||
autoRename: true
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should not use "versioningEnabled" if not explicitly provided', () => {
|
||||
const model = new FileModel(
|
||||
{ name: 'file-name', size: 10 } as File,
|
||||
{}
|
||||
);
|
||||
|
||||
service.addToQueue(model);
|
||||
service.uploadFilesInTheQueue();
|
||||
|
||||
expect(uploadFileSpy).toHaveBeenCalledWith(
|
||||
{
|
||||
name: 'file-name',
|
||||
size: 10
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
{ newVersion: false },
|
||||
{
|
||||
include: [ 'allowableOperations' ],
|
||||
renditions: 'doclib',
|
||||
autoRename: true
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should append the extra upload options to the request', () => {
|
||||
const filesFake = new FileModel(
|
||||
{ name: 'fake-name', size: 10 } as File,
|
||||
{
|
||||
parentId: '123',
|
||||
path: 'fake-dir',
|
||||
secondaryChildren: [{ assocType: 'assoc-1', childId: 'child-id' }],
|
||||
association: { assocType: 'fake-assoc' },
|
||||
targets: [{ assocType: 'target-assoc', targetId: 'fake-target-id' }]
|
||||
});
|
||||
service.addToQueue(filesFake);
|
||||
service.uploadFilesInTheQueue();
|
||||
|
||||
expect(uploadFileSpy).toHaveBeenCalledWith(
|
||||
{
|
||||
name: 'fake-name',
|
||||
size: 10
|
||||
},
|
||||
'fake-dir',
|
||||
'123',
|
||||
{
|
||||
newVersion: false,
|
||||
parentId: '123',
|
||||
path: 'fake-dir',
|
||||
secondaryChildren: [ { assocType: 'assoc-1', childId: 'child-id' }],
|
||||
association: { assocType: 'fake-assoc' },
|
||||
targets: [{ assocType: 'target-assoc', targetId: 'fake-target-id' }]
|
||||
},
|
||||
{
|
||||
renditions: 'doclib',
|
||||
include: ['allowableOperations'],
|
||||
autoRename: true
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should start downloading the next one if a file of the list is aborted', (done) => {
|
||||
service.fileUploadAborted.subscribe((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
});
|
||||
|
||||
service.fileUploadCancelled.subscribe((e) => {
|
||||
expect(e).not.toBeNull();
|
||||
done();
|
||||
});
|
||||
|
||||
const fileFake1 = new FileModel({ name: 'fake-name1', size: 10 } as File);
|
||||
const fileFake2 = new FileModel({ name: 'fake-name2', size: 10 } as File);
|
||||
const fileList = [fileFake1, fileFake2];
|
||||
service.addToQueue(...fileList);
|
||||
service.uploadFilesInTheQueue();
|
||||
|
||||
const file = service.getQueue();
|
||||
service.cancelUpload(...file);
|
||||
});
|
||||
|
||||
it('should remove from the queue all the files in the excluded list', () => {
|
||||
const file1 = new FileModel(new File([''], '.git'));
|
||||
const file2 = new FileModel(new File([''], '.DS_Store'));
|
||||
const file3 = new FileModel(new File([''], 'desktop.ini'));
|
||||
const file4 = new FileModel(new File([''], 'readme.md'));
|
||||
const file5 = new FileModel(new File([''], 'test.git'));
|
||||
const result = service.addToQueue(file1, file2, file3, file4, file5);
|
||||
expect(result.length).toBe(1);
|
||||
expect(result[0]).toBe(file4);
|
||||
});
|
||||
|
||||
it('should skip files if they are in an excluded folder', () => {
|
||||
const file1: any = { name: 'readmetoo.md', file : { webkitRelativePath: '/rollingPanda/' }};
|
||||
const file2: any = { name: 'readme.md', file : { webkitRelativePath: '/test/' }};
|
||||
const result = service.addToQueue(file1, file2);
|
||||
expect(result.length).toBe(1);
|
||||
expect(result[0]).toBe(file2);
|
||||
});
|
||||
|
||||
it('should match the folder in case insensitive way', () => {
|
||||
const file1: any = { name: 'readmetoo.md', file : { webkitRelativePath: '/rollingPanda/' }};
|
||||
const file2: any = { name: 'readme.md', file : { webkitRelativePath: '/test/' }};
|
||||
const result = service.addToQueue(file1, file2);
|
||||
expect(result.length).toBe(1);
|
||||
expect(result[0]).toBe(file2);
|
||||
});
|
||||
|
||||
it('should skip files if they are in an excluded folder when path is in options', () => {
|
||||
const file1: any = { name: 'readmetoo.md', file : {}, options: { path: '/rollingPanda/'}};
|
||||
const file2: any = { name: 'readme.md', file : { webkitRelativePath: '/test/' }};
|
||||
const result = service.addToQueue(file1, file2);
|
||||
expect(result.length).toBe(1);
|
||||
expect(result[0]).toBe(file2);
|
||||
});
|
||||
|
||||
it('should call onUploadDeleted if file was deleted', () => {
|
||||
const file = { status: FileUploadStatus.Deleted } as FileModel;
|
||||
spyOn(service.fileUploadDeleted, 'next');
|
||||
|
||||
service.cancelUpload(file);
|
||||
|
||||
expect(service.fileUploadDeleted.next).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call fileUploadError if file has error status', () => {
|
||||
const file = { status: FileUploadStatus.Error } as FileModel;
|
||||
spyOn(service.fileUploadError, 'next');
|
||||
|
||||
service.cancelUpload(file);
|
||||
|
||||
expect(service.fileUploadError.next).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call fileUploadCancelled if file is in pending', () => {
|
||||
const file = { status: FileUploadStatus.Pending } as FileModel;
|
||||
spyOn(service.fileUploadCancelled, 'next');
|
||||
|
||||
service.cancelUpload(file);
|
||||
|
||||
expect(service.fileUploadCancelled.next).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Should not pass rendition if it is disabled', () => {
|
||||
mockProductInfo.next({ status: { isThumbnailGenerationEnabled: false } } as RepositoryInfo);
|
||||
|
||||
const filesFake = new FileModel(
|
||||
{ name: 'fake-name', size: 10 } as File,
|
||||
{ newVersion: true}
|
||||
);
|
||||
service.addToQueue(filesFake);
|
||||
service.uploadFilesInTheQueue();
|
||||
|
||||
expect(uploadFileSpy).toHaveBeenCalledWith(
|
||||
{
|
||||
name: 'fake-name',
|
||||
size: 10
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
{ newVersion: true },
|
||||
{
|
||||
include: ['allowableOperations'],
|
||||
overwrite: true,
|
||||
majorVersion: undefined,
|
||||
comment: undefined,
|
||||
name: 'fake-name'
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
@@ -1,476 +0,0 @@
|
||||
/*!
|
||||
* @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 { EventEmitter, Injectable } from '@angular/core';
|
||||
import { Minimatch } from 'minimatch';
|
||||
import { Subject } from 'rxjs';
|
||||
import { AppConfigService } from '../app-config/app-config.service';
|
||||
import {
|
||||
FileUploadCompleteEvent,
|
||||
FileUploadDeleteEvent,
|
||||
FileUploadErrorEvent,
|
||||
FileUploadEvent
|
||||
} from '../events/file.event';
|
||||
import { FileModel, FileUploadProgress, FileUploadStatus } from '../models/file.model';
|
||||
import { AlfrescoApiService } from './alfresco-api.service';
|
||||
import { DiscoveryApiService } from './discovery-api.service';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import { NodesApi, UploadApi, VersionsApi } from '@alfresco/js-api';
|
||||
|
||||
const MIN_CANCELLABLE_FILE_SIZE = 1000000;
|
||||
const MAX_CANCELLABLE_FILE_PERCENTAGE = 50;
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UploadService {
|
||||
queue: FileModel[] = [];
|
||||
queueChanged: Subject<FileModel[]> = new Subject<FileModel[]>();
|
||||
fileUpload: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
|
||||
fileUploadStarting: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
|
||||
fileUploadCancelled: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
|
||||
fileUploadProgress: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
|
||||
fileUploadAborted: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
|
||||
fileUploadError: Subject<FileUploadErrorEvent> = new Subject<FileUploadErrorEvent>();
|
||||
fileUploadComplete: Subject<FileUploadCompleteEvent> = new Subject<FileUploadCompleteEvent>();
|
||||
fileUploadDeleted: Subject<FileUploadDeleteEvent> = new Subject<FileUploadDeleteEvent>();
|
||||
fileDeleted: Subject<string> = new Subject<string>();
|
||||
|
||||
private cache: { [key: string]: any } = {};
|
||||
private totalComplete: number = 0;
|
||||
private totalAborted: number = 0;
|
||||
private totalError: number = 0;
|
||||
private excludedFileList: string[] = [];
|
||||
private excludedFoldersList: string[] = [];
|
||||
private matchingOptions: any = null;
|
||||
private folderMatchingOptions: any = null;
|
||||
private abortedFile: string;
|
||||
private isThumbnailGenerationEnabled: boolean;
|
||||
|
||||
private _uploadApi: UploadApi;
|
||||
get uploadApi(): UploadApi {
|
||||
this._uploadApi = this._uploadApi ?? new UploadApi(this.apiService.getInstance());
|
||||
return this._uploadApi;
|
||||
}
|
||||
|
||||
private _nodesApi: NodesApi;
|
||||
get nodesApi(): NodesApi {
|
||||
this._nodesApi = this._nodesApi ?? new NodesApi(this.apiService.getInstance());
|
||||
return this._nodesApi;
|
||||
}
|
||||
|
||||
private _versionsApi: VersionsApi;
|
||||
get versionsApi(): VersionsApi {
|
||||
this._versionsApi = this._versionsApi ?? new VersionsApi(this.apiService.getInstance());
|
||||
return this._versionsApi;
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected apiService: AlfrescoApiService,
|
||||
private appConfigService: AppConfigService,
|
||||
private discoveryApiService: DiscoveryApiService) {
|
||||
|
||||
this.discoveryApiService.ecmProductInfo$.pipe(filter(info => !!info))
|
||||
.subscribe(({ status }) => {
|
||||
this.isThumbnailGenerationEnabled = status.isThumbnailGenerationEnabled;
|
||||
});
|
||||
}
|
||||
|
||||
clearCache() {
|
||||
this.cache = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of concurrent threads for uploading.
|
||||
*
|
||||
* @returns Number of concurrent threads (default 1)
|
||||
*/
|
||||
getThreadsCount(): number {
|
||||
return this.appConfigService.get<number>('upload.threads', 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the service still has files uploading or awaiting upload.
|
||||
*
|
||||
* @returns True if files in the queue are still uploading, false otherwise
|
||||
*/
|
||||
isUploading(): boolean {
|
||||
const finishedFileStates = [FileUploadStatus.Complete, FileUploadStatus.Cancelled, FileUploadStatus.Aborted, FileUploadStatus.Error, FileUploadStatus.Deleted];
|
||||
return this.queue.reduce((stillUploading: boolean, currentFile: FileModel) => stillUploading || finishedFileStates.indexOf(currentFile.status) === -1, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file Queue
|
||||
*
|
||||
* @returns Array of files that form the queue
|
||||
*/
|
||||
getQueue(): FileModel[] {
|
||||
return this.queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds files to the uploading queue to be uploaded
|
||||
*
|
||||
* @param files One or more separate parameters or an array of files to queue
|
||||
* @returns Array of files that were not blocked from upload by the ignore list
|
||||
*/
|
||||
addToQueue(...files: FileModel[]): FileModel[] {
|
||||
const allowedFiles = files.filter((currentFile) =>
|
||||
this.filterElement(currentFile)
|
||||
);
|
||||
this.queue = this.queue.concat(allowedFiles);
|
||||
this.queueChanged.next(this.queue);
|
||||
return allowedFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all the files in the queue that are not yet uploaded and uploads them into the directory folder.
|
||||
*
|
||||
* @param successEmitter Emitter to invoke on file success status change
|
||||
* @param errorEmitter Emitter to invoke on file error status change
|
||||
*/
|
||||
uploadFilesInTheQueue(successEmitter?: EventEmitter<any>, errorEmitter?: EventEmitter<any>): void {
|
||||
const files = this.getFilesToUpload();
|
||||
|
||||
if (files && files.length > 0) {
|
||||
for (const file of files) {
|
||||
this.onUploadStarting(file);
|
||||
|
||||
const promise = this.beginUpload(file, successEmitter, errorEmitter);
|
||||
this.cache[file.name] = promise;
|
||||
|
||||
const next = () => {
|
||||
setTimeout(() => this.uploadFilesInTheQueue(successEmitter, errorEmitter), 100);
|
||||
};
|
||||
|
||||
promise.next = next;
|
||||
|
||||
promise.then(
|
||||
() => next(),
|
||||
() => next()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels uploading of files.
|
||||
* If the file is smaller than 1 MB the file will be uploaded and then the node deleted
|
||||
* to prevent having files that were aborted but still uploaded.
|
||||
*
|
||||
* @param files One or more separate parameters or an array of files specifying uploads to cancel
|
||||
*/
|
||||
cancelUpload(...files: FileModel[]) {
|
||||
files.forEach((file) => {
|
||||
const promise = this.cache[file.name];
|
||||
if (promise) {
|
||||
if (this.isSaveToAbortFile(file)) {
|
||||
promise.abort();
|
||||
} else {
|
||||
this.abortedFile = file.name;
|
||||
}
|
||||
delete this.cache[file.name];
|
||||
promise.next();
|
||||
} else {
|
||||
const performAction = this.getAction(file);
|
||||
|
||||
if (performAction) {
|
||||
performAction();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Clears the upload queue */
|
||||
clearQueue() {
|
||||
this.queue = [];
|
||||
this.totalComplete = 0;
|
||||
this.totalAborted = 0;
|
||||
this.totalError = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an upload promise for a file.
|
||||
*
|
||||
* @param file The target file
|
||||
* @returns Promise that is resolved if the upload is successful or error otherwise
|
||||
*/
|
||||
getUploadPromise(file: FileModel): any {
|
||||
const opts: any = {
|
||||
include: ['allowableOperations']
|
||||
};
|
||||
|
||||
if (this.isThumbnailGenerationEnabled) {
|
||||
opts.renditions = 'doclib';
|
||||
}
|
||||
|
||||
if (file.options && file.options.versioningEnabled !== undefined) {
|
||||
opts.versioningEnabled = file.options.versioningEnabled;
|
||||
}
|
||||
|
||||
if (file.options.newVersion === true) {
|
||||
opts.overwrite = true;
|
||||
opts.majorVersion = file.options.majorVersion;
|
||||
opts.comment = file.options.comment;
|
||||
opts.name = file.name;
|
||||
} else {
|
||||
opts.autoRename = true;
|
||||
}
|
||||
|
||||
if (file.options.nodeType) {
|
||||
opts.nodeType = file.options.nodeType;
|
||||
}
|
||||
|
||||
if (file.id) {
|
||||
return this.nodesApi.updateNodeContent(file.id, file.file as any, opts);
|
||||
} else {
|
||||
const nodeBody = { ... file.options };
|
||||
delete nodeBody['versioningEnabled'];
|
||||
|
||||
return this.uploadApi.uploadFile(
|
||||
file.file,
|
||||
file.options.path,
|
||||
file.options.parentId,
|
||||
nodeBody,
|
||||
opts
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private getFilesToUpload(): FileModel[] {
|
||||
const cached = Object.keys(this.cache);
|
||||
const threadsCount = this.getThreadsCount();
|
||||
|
||||
if (cached.length >= threadsCount) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const files = this.queue
|
||||
.filter(toUpload => !cached.includes(toUpload.name) && toUpload.status === FileUploadStatus.Pending)
|
||||
.slice(0, threadsCount);
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
private beginUpload(file: FileModel, successEmitter?: EventEmitter<any>, errorEmitter?: EventEmitter<any>): any {
|
||||
const promise = this.getUploadPromise(file);
|
||||
promise
|
||||
.on('progress', (progress: FileUploadProgress) => {
|
||||
this.onUploadProgress(file, progress);
|
||||
})
|
||||
.on('abort', () => {
|
||||
this.onUploadAborted(file);
|
||||
if (successEmitter) {
|
||||
successEmitter.emit({ value: 'File aborted' });
|
||||
}
|
||||
})
|
||||
.on('error', (err) => {
|
||||
this.onUploadError(file, err);
|
||||
if (errorEmitter) {
|
||||
errorEmitter.emit({ value: 'Error file uploaded' });
|
||||
}
|
||||
})
|
||||
.on('success', (data) => {
|
||||
if (this.abortedFile === file.name) {
|
||||
this.onUploadAborted(file);
|
||||
if (file.id === undefined) {
|
||||
this.deleteAbortedNode(data.entry.id);
|
||||
} else {
|
||||
this.deleteAbortedNodeVersion(data.entry.id, data.entry.properties['cm:versionLabel']);
|
||||
}
|
||||
if (successEmitter) {
|
||||
successEmitter.emit({ value: 'File deleted' });
|
||||
}
|
||||
} else {
|
||||
this.onUploadComplete(file, data);
|
||||
if (successEmitter) {
|
||||
successEmitter.emit({ value: data });
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
private onUploadStarting(file: FileModel): void {
|
||||
if (file) {
|
||||
file.status = FileUploadStatus.Starting;
|
||||
const event = new FileUploadEvent(file, FileUploadStatus.Starting);
|
||||
this.fileUpload.next(event);
|
||||
this.fileUploadStarting.next(event);
|
||||
}
|
||||
}
|
||||
|
||||
private onUploadProgress(
|
||||
file: FileModel,
|
||||
progress: FileUploadProgress
|
||||
): void {
|
||||
if (file) {
|
||||
file.progress = progress;
|
||||
file.status = FileUploadStatus.Progress;
|
||||
|
||||
const event = new FileUploadEvent(file, FileUploadStatus.Progress);
|
||||
this.fileUpload.next(event);
|
||||
this.fileUploadProgress.next(event);
|
||||
}
|
||||
}
|
||||
|
||||
private onUploadError(file: FileModel, error: any): void {
|
||||
if (file) {
|
||||
file.errorCode = (error || {}).status;
|
||||
file.status = FileUploadStatus.Error;
|
||||
this.totalError++;
|
||||
|
||||
const promise = this.cache[file.name];
|
||||
if (promise) {
|
||||
delete this.cache[file.name];
|
||||
}
|
||||
|
||||
const event = new FileUploadErrorEvent(
|
||||
file,
|
||||
error,
|
||||
this.totalError
|
||||
);
|
||||
this.fileUpload.next(event);
|
||||
this.fileUploadError.next(event);
|
||||
}
|
||||
}
|
||||
|
||||
private onUploadComplete(file: FileModel, data: any): void {
|
||||
if (file) {
|
||||
file.status = FileUploadStatus.Complete;
|
||||
file.data = data;
|
||||
this.totalComplete++;
|
||||
const promise = this.cache[file.name];
|
||||
if (promise) {
|
||||
delete this.cache[file.name];
|
||||
}
|
||||
|
||||
const event = new FileUploadCompleteEvent(
|
||||
file,
|
||||
this.totalComplete,
|
||||
data,
|
||||
this.totalAborted
|
||||
);
|
||||
this.fileUpload.next(event);
|
||||
this.fileUploadComplete.next(event);
|
||||
}
|
||||
}
|
||||
|
||||
private onUploadAborted(file: FileModel): void {
|
||||
if (file) {
|
||||
file.status = FileUploadStatus.Aborted;
|
||||
this.totalAborted++;
|
||||
|
||||
const event = new FileUploadEvent(file, FileUploadStatus.Aborted);
|
||||
this.fileUpload.next(event);
|
||||
this.fileUploadAborted.next(event);
|
||||
}
|
||||
}
|
||||
|
||||
private onUploadCancelled(file: FileModel): void {
|
||||
if (file) {
|
||||
file.status = FileUploadStatus.Cancelled;
|
||||
|
||||
const event = new FileUploadEvent(file, FileUploadStatus.Cancelled);
|
||||
this.fileUpload.next(event);
|
||||
this.fileUploadCancelled.next(event);
|
||||
}
|
||||
}
|
||||
|
||||
private onUploadDeleted(file: FileModel): void {
|
||||
if (file) {
|
||||
file.status = FileUploadStatus.Deleted;
|
||||
this.totalComplete--;
|
||||
|
||||
const event = new FileUploadDeleteEvent(file, this.totalComplete);
|
||||
this.fileUpload.next(event);
|
||||
this.fileUploadDeleted.next(event);
|
||||
}
|
||||
}
|
||||
|
||||
private getAction(file: FileModel) {
|
||||
const actions = {
|
||||
[FileUploadStatus.Pending]: () => this.onUploadCancelled(file),
|
||||
[FileUploadStatus.Deleted]: () => this.onUploadDeleted(file),
|
||||
[FileUploadStatus.Error]: () => this.onUploadError(file, null)
|
||||
};
|
||||
|
||||
return actions[file.status];
|
||||
}
|
||||
|
||||
private deleteAbortedNode(nodeId: string) {
|
||||
this.nodesApi.deleteNode(nodeId, { permanent: true })
|
||||
.then(() => (this.abortedFile = undefined));
|
||||
}
|
||||
|
||||
private deleteAbortedNodeVersion(nodeId: string, versionId: string) {
|
||||
this.versionsApi.deleteVersion(nodeId, versionId)
|
||||
.then(() => (this.abortedFile = undefined));
|
||||
}
|
||||
|
||||
private isSaveToAbortFile(file: FileModel): boolean {
|
||||
return (
|
||||
file.size > MIN_CANCELLABLE_FILE_SIZE &&
|
||||
file.progress.percent < MAX_CANCELLABLE_FILE_PERCENTAGE
|
||||
);
|
||||
}
|
||||
|
||||
private filterElement(file: FileModel) {
|
||||
this.excludedFileList = this.appConfigService.get<string[]>('files.excluded');
|
||||
this.excludedFoldersList = this.appConfigService.get<string[]>('folders.excluded');
|
||||
let isAllowed = true;
|
||||
|
||||
if (this.excludedFileList) {
|
||||
this.matchingOptions = this.appConfigService.get('files.match-options');
|
||||
isAllowed = this.isFileNameAllowed(file);
|
||||
}
|
||||
|
||||
if (isAllowed && this.excludedFoldersList) {
|
||||
this.folderMatchingOptions = this.appConfigService.get('folders.match-options');
|
||||
isAllowed = this.isParentFolderAllowed(file);
|
||||
}
|
||||
return isAllowed;
|
||||
}
|
||||
|
||||
private isParentFolderAllowed(file: FileModel): boolean {
|
||||
let isAllowed: boolean = true;
|
||||
const currentFile: any = file.file;
|
||||
const fileRelativePath = currentFile.webkitRelativePath ? currentFile.webkitRelativePath : file.options.path;
|
||||
if (currentFile && fileRelativePath) {
|
||||
isAllowed =
|
||||
this.excludedFoldersList.filter((folderToExclude) => fileRelativePath
|
||||
.split('/')
|
||||
.some((pathElement) => {
|
||||
const minimatch = new Minimatch(folderToExclude, this.folderMatchingOptions);
|
||||
return minimatch.match(pathElement);
|
||||
})).length === 0;
|
||||
}
|
||||
return isAllowed;
|
||||
}
|
||||
|
||||
private isFileNameAllowed(file: FileModel): boolean {
|
||||
return (
|
||||
this.excludedFileList.filter((pattern) => {
|
||||
const minimatch = new Minimatch(pattern, this.matchingOptions);
|
||||
return minimatch.match(file.name);
|
||||
}).length === 0
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,68 +0,0 @@
|
||||
/*!
|
||||
* @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 { CoreTestingModule, setupTestBed } from '../testing';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { of } from 'rxjs';
|
||||
import { AppConfigService } from '../app-config';
|
||||
import { UserContentAccessService } from './user-content-access.service';
|
||||
import { PeopleContentService } from './people-content.service';
|
||||
|
||||
describe('UserContentAccessService', () => {
|
||||
let userContentAccessService: UserContentAccessService;
|
||||
let peopleContentService: PeopleContentService;
|
||||
let appConfigService: AppConfigService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [CoreTestingModule],
|
||||
providers: [UserContentAccessService]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
userContentAccessService = TestBed.inject(UserContentAccessService);
|
||||
peopleContentService = TestBed.inject(PeopleContentService);
|
||||
appConfigService = TestBed.inject(AppConfigService);
|
||||
});
|
||||
|
||||
it('should return true if user is content admin and provider is ECM', async () => {
|
||||
appConfigService.config.providers = 'ECM';
|
||||
spyOn(peopleContentService, 'getCurrentUserInfo').and.returnValue(of({}as any));
|
||||
spyOn(peopleContentService, 'isCurrentUserAdmin').and.returnValue(true);
|
||||
const isContentAdmin = await userContentAccessService.isCurrentUserAdmin();
|
||||
|
||||
expect(isContentAdmin).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return true if user is content admin and provider is ALL', async () => {
|
||||
appConfigService.config.providers = 'ALL';
|
||||
spyOn(peopleContentService, 'getCurrentUserInfo').and.returnValue(of({} as any));
|
||||
spyOn(peopleContentService, 'isCurrentUserAdmin').and.returnValue(true);
|
||||
const isContentAdmin = await userContentAccessService.isCurrentUserAdmin();
|
||||
|
||||
expect(isContentAdmin).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false if provider is BPM', async () => {
|
||||
appConfigService.config.providers = 'BPM';
|
||||
const isCurrentUserAdminSpy = spyOn(peopleContentService, 'isCurrentUserAdmin').and.returnValue(true);
|
||||
const isContentAdmin = await userContentAccessService.isCurrentUserAdmin();
|
||||
|
||||
expect(isContentAdmin).toEqual(false);
|
||||
expect(isCurrentUserAdminSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
@@ -1,44 +0,0 @@
|
||||
/*!
|
||||
* @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';
|
||||
import { PeopleContentService } from './people-content.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserContentAccessService {
|
||||
|
||||
constructor(
|
||||
private appConfigService: AppConfigService,
|
||||
private peopleContentService: PeopleContentService) {
|
||||
}
|
||||
|
||||
private hasContentProvider(): boolean {
|
||||
return this.appConfigService.get('providers') === 'ECM' || this.appConfigService.get('providers') === 'ALL';
|
||||
}
|
||||
|
||||
async isCurrentUserAdmin(): Promise<boolean> {
|
||||
if( this.hasContentProvider()) {
|
||||
await this.peopleContentService.getCurrentUserInfo().toPromise();
|
||||
return this.peopleContentService.isCurrentUserAdmin();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@@ -1,41 +0,0 @@
|
||||
/*!
|
||||
* @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 {
|
||||
Resolve,
|
||||
ActivatedRouteSnapshot,
|
||||
RouterStateSnapshot
|
||||
} from '@angular/router';
|
||||
import { EcmUserModel } from '../models/ecm-user.model';
|
||||
import { Observable } from 'rxjs';
|
||||
import { PeopleContentService } from './people-content.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserInfoResolverService implements Resolve<EcmUserModel> {
|
||||
constructor(private peopleContentService: PeopleContentService) {}
|
||||
|
||||
resolve(
|
||||
_route: ActivatedRouteSnapshot,
|
||||
_state: RouterStateSnapshot
|
||||
): Observable<EcmUserModel> {
|
||||
return this.peopleContentService.getCurrentUserInfo();
|
||||
}
|
||||
|
||||
}
|
@@ -17,7 +17,7 @@
|
||||
|
||||
import { SimpleChange } from '@angular/core';
|
||||
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { UrlService } from '../../services/url.service';
|
||||
import { UrlService } from '../../common/services/url.service';
|
||||
import { ImgViewerComponent } from './img-viewer.component';
|
||||
import { setupTestBed, CoreTestingModule } from '../../testing';
|
||||
import { AppConfigService } from '../../app-config/app-config.service';
|
||||
|
@@ -26,7 +26,7 @@ import {
|
||||
EventEmitter, AfterViewInit, ViewChild, HostListener, OnDestroy
|
||||
} from '@angular/core';
|
||||
import { AppConfigService } from '../../app-config/app-config.service';
|
||||
import { UrlService } from '../../services/url.service';
|
||||
import { UrlService } from '../../common/services/url.service';
|
||||
import Cropper from 'cropperjs';
|
||||
|
||||
@Component({
|
||||
|
@@ -16,8 +16,8 @@
|
||||
*/
|
||||
|
||||
import { Component, Input, OnChanges, SimpleChanges, ViewEncapsulation, Output, EventEmitter } from '@angular/core';
|
||||
import { ContentService } from '../../services/content.service';
|
||||
import { Track } from '../models/viewer.model';
|
||||
import { UrlService } from '../../common/services/url.service';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-media-player',
|
||||
@@ -47,14 +47,14 @@ export class MediaPlayerComponent implements OnChanges {
|
||||
@Output()
|
||||
error = new EventEmitter<any>();
|
||||
|
||||
constructor(private contentService: ContentService) {
|
||||
constructor(private urlService: UrlService) {
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
const blobFile = changes['blobFile'];
|
||||
|
||||
if (blobFile && blobFile.currentValue) {
|
||||
this.urlFile = this.contentService.createTrustedUrl(this.blobFile);
|
||||
this.urlFile = this.urlService.createTrustedUrl(this.blobFile);
|
||||
return;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user