[ADF-3550] Migration - Application List Component (#3902)

* [ADF-3538] start creating new folder for cloud components

* [ADF-3538] added new package to the script and the builds

* [ADF-3538] added some more changes to scripts

* [ADF-3538] - starting the new package

* fix package

* Add a cloud component as example

* Skip the scss style

* remove useless codes

* Add i18n example

* remove useless code

* Simplify the hello component
Fix the wrong path

* add the app-list-cloud-component
add the app-details-cloud-component

* Expose and use the new component

* Consume the new package and component from the demoshell

* Fix unit test app list cloud

* Fix process service cloud path

* Remove useless file

* * Added documentation to the appListcloud component

* Fix failing unit tests
This commit is contained in:
Maurizio Vitale
2018-10-18 17:11:32 +01:00
committed by Eugenio Romano
parent 987c01f1f4
commit 4c9629c2d6
33 changed files with 2357 additions and 965 deletions

View File

@@ -63,7 +63,7 @@ import { ContentModule } from '@alfresco/adf-content-services';
import { InsightsModule } from '@alfresco/adf-insights';
import { ProcessModule } from '@alfresco/adf-process-services';
import { AuthBearerInterceptor } from './services';
import { ProcessServicesCloudModule } from '@alfresco/adf-process-services-cloud';
import { AppListCloudModule } from '@alfresco/adf-process-services-cloud';
import { CloudComponent } from './components/cloud/cloud.component';
@NgModule({
@@ -83,7 +83,7 @@ import { CloudComponent } from './components/cloud/cloud.component';
ThemePickerModule,
ChartsModule,
MonacoEditorModule.forRoot(),
ProcessServicesCloudModule
AppListCloudModule
],
declarations: [
AppComponent,

View File

@@ -44,7 +44,6 @@ import { CloudComponent } from './components/cloud/cloud.component';
export const appRoutes: Routes = [
{ path: 'login', component: LoginComponent },
{ path: 'cloud', component: CloudComponent },
{ path: 'logout', component: LogoutComponent },
{
path: 'settings',
@@ -134,6 +133,10 @@ export const appRoutes: Routes = [
path: 'home',
component: HomeComponent
},
{
path: 'cloud',
component: CloudComponent
},
{
path: 'node-selector',
loadChildren: 'app/components/content-node-selector/content-node-selector.module#AppContentNodeSelectorModule'

View File

@@ -1,2 +1,2 @@
<adf-cloud-hello></adf-cloud-hello>
<adf-cloud-app-list></adf-cloud-app-list>

View File

@@ -5,8 +5,6 @@
<h2>{{ 'APP.HOME.TITLE' | translate}}</h2>
</div>
<adf-cloud-hello></adf-cloud-hello>
<div class="adf-home-start">
<a mat-raised-button class="adf-home-docs-button adf-primary-color" href="https://github.com/Alfresco/alfresco-ng2-components/tree/master/docs">{{ 'APP.HOME.DOCUMENTATION' | translate}}</a>
</div>

View File

@@ -0,0 +1,45 @@
---
Added: v3.0.0
Status: Active
Last reviewed: 2018-18-10
---
# App List Cloud Component
Shows all deployed cloud application instances.
## Basic Usage
```html
<adf-cloud-app-list
[layoutType]="'GRID'">
</adf-cloud-app-list>
```
### [Transclusions](../user-guide/transclusion.md)
You can show custom content when there are no apps available by supplying an
`<adf-empty-custom-content>` section:
```html
<adf-cloud-app-list
[layoutType]="'GRID'">
<adf-empty-custom-content>
No Apps present
</adf-empty-custom-content>
</adf-cloud-app-list>
```
## Class members
### Properties
| Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- |
| layoutType | `string` | | (**required**) Defines the layout of the apps. There are two possible values, "GRID" and "LIST". |
### Events
| Name | Type | Description |
| ---- | ---- | ----------- |
| appClick | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`ApplicationInstanceModel`](../../lib/process-services-cloud/apps-list/models/application-instance.model.ts)`>` | Emitted when an app entry is clicked. |

View File

@@ -16,7 +16,7 @@
*/
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TestBed, fakeAsync } from '@angular/core/testing';
import { TestBed, fakeAsync, async } from '@angular/core/testing';
import { MatDialogRef, MAT_DIALOG_DATA, MatDialog } from '@angular/material';
import { of } from 'rxjs';
import {
@@ -96,7 +96,7 @@ describe('ShareDialogComponent', () => {
expect(fixture.nativeElement.querySelector('.mat-slide-toggle').classList).toContain('mat-checked');
});
it(`should not toggle share action when file has 'sharedId' property`, () => {
it(`should not toggle share action when file has 'sharedId' property`, async(() => {
spyOn(sharedLinksApiService, 'createSharedLinks');
node.entry.properties['qshare:sharedId'] = 'sharedId';
@@ -108,12 +108,17 @@ describe('ShareDialogComponent', () => {
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(sharedLinksApiService.createSharedLinks).not.toHaveBeenCalled();
expect(fixture.nativeElement.querySelector('input[formcontrolname="sharedUrl"]').value).toBe('some-url/sharedId');
expect(fixture.nativeElement.querySelector('.mat-slide-toggle').classList).toContain('mat-checked');
});
it(`should copy shared link and notify on button event`, (done) => {
});
}));
it(`should copy shared link and notify on button event`, async(() => {
node.entry.properties['qshare:sharedId'] = 'sharedId';
spyOn(document, 'execCommand').and.callThrough();
@@ -134,9 +139,8 @@ describe('ShareDialogComponent', () => {
expect(document.execCommand).toHaveBeenCalledWith('copy');
expect(notificationServiceMock.openSnackMessage).toHaveBeenCalledWith('SHARE.CLIPBOARD-MESSAGE');
done();
});
});
}));
it('should open a confirmation dialog when unshare button is triggered', () => {
spyOn(matDialog, 'open').and.returnValue({ beforeClose: () => of(false) });

View File

@@ -0,0 +1,30 @@
npm-debug.log
.idea
.npmrc
/.editorconfig
/.travis.yml
/*.json
/karma-test-shim.js
/karma.conf.js
/gulpfile.ts
/.npmignore
/.happypack
**/*.html
**/*.js
**/*.ts
!**/*.d.ts
!**/adf-process-services-cloud.js
**/*.scss
**/*.css
!**/_theming.scss
coverage/
demo/
dist/
node_modules
typings/
fonts/
i18n/
assets/

View File

@@ -0,0 +1,44 @@
/*!
* @license
* Copyright 2016 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 {
MAT_LABEL_GLOBAL_OPTIONS, MatAutocompleteModule, MatButtonModule, MatCardModule, MatCheckboxModule,
MatChipsModule, MatDatepickerModule, MatDialogModule, MatGridListModule, MatIconModule,
MatInputModule, MatListModule, MatNativeDateModule, MatOptionModule, MatProgressSpinnerModule, MatRadioModule,
MatRippleModule, MatSelectModule, MatSlideToggleModule, MatTableModule, MatTabsModule,
MatTooltipModule, MatMenuModule
} from '@angular/material';
export function modules() {
return [
MatAutocompleteModule, MatButtonModule, MatCardModule, MatDialogModule,
MatCheckboxModule, MatDatepickerModule, MatGridListModule, MatIconModule, MatInputModule,
MatListModule, MatOptionModule, MatRadioModule, MatSelectModule, MatSlideToggleModule, MatTableModule,
MatTabsModule, MatProgressSpinnerModule, MatNativeDateModule, MatRippleModule, MatTooltipModule,
MatChipsModule, MatMenuModule
];
}
@NgModule({
providers: [
{provide: MAT_LABEL_GLOBAL_OPTIONS, useValue: { float: 'never' }}
],
imports: modules(),
exports: modules()
})
export class MaterialModule {}

View File

@@ -0,0 +1,99 @@
/*!
* @license
* Copyright 2016 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 { CommonModule } from '@angular/common';
import { NgModule, ModuleWithProviders } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { CoreModule, TRANSLATION_PROVIDER } from '@alfresco/adf-core';
import { MaterialModule } from './material.module';
import { HelloCloudModule } from './hello/hello.module';
export function providers() {
return [
];
}
@NgModule({
imports: [
CoreModule.forChild(),
CommonModule,
FormsModule,
ReactiveFormsModule,
MaterialModule,
HelloCloudModule
],
providers: [
...providers(),
{
provide: TRANSLATION_PROVIDER,
multi: true,
useValue: {
name: 'adf-process-services-cloud',
source: 'assets/adf-process-services-cloud'
}
}
],
exports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
HelloCloudModule
]
})
export class ProcessCloudModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: ProcessCloudModule,
providers: [
...providers(),
{
provide: TRANSLATION_PROVIDER,
multi: true,
useValue: {
name: 'adf-process-services-cloud',
source: 'assets/adf-process-services-cloud'
}
}
]
};
}
static forChild(): ModuleWithProviders {
return {
ngModule: ProcessCloudModuleLazy
};
}
}
@NgModule({
imports: [
CoreModule.forChild(),
CommonModule,
FormsModule,
ReactiveFormsModule,
MaterialModule,
HelloCloudModule
],
exports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
HelloCloudModule
]
})
export class ProcessCloudModuleLazy {}

View File

@@ -0,0 +1,18 @@
/*!
* @license
* Copyright 2016 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 * from './index';

View File

@@ -0,0 +1,13 @@
import { AppListCloudModule } from './app-list-cloud.module';
describe('AppListCloudModule', () => {
let appListCloudModule: AppListCloudModule;
beforeEach(() => {
appListCloudModule = new AppListCloudModule();
});
it('should create an instance', () => {
expect(appListCloudModule).toBeTruthy();
});
});

View File

@@ -0,0 +1,53 @@
/*!
* @license
* Copyright 2016 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 { FlexLayoutModule } from '@angular/flex-layout';
import { MaterialModule } from '../material.module';
import { CommonModule } from '@angular/common';
import { AppDetailsCloudComponent } from './components/app-details-cloud.component';
import { AppListCloudComponent } from './components/app-list-cloud.component';
import { AppsProcessCloudService } from './services/apps-process-cloud.service';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TemplateModule, TranslateLoaderService } from '@alfresco/adf-core';
@NgModule({
imports: [
CommonModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateLoaderService
}
}),
TemplateModule,
MaterialModule,
FlexLayoutModule
],
declarations: [
AppListCloudComponent,
AppDetailsCloudComponent
],
providers: [
AppsProcessCloudService
],
exports: [
AppListCloudComponent,
AppDetailsCloudComponent
]
})
export class AppListCloudModule { }

View File

@@ -0,0 +1,22 @@
<div class="adf-app-listgrid">
<div class="adf-app-listgrid-item">
<mat-card tabindex="0"
fxLayout="column"
role="button"
class="adf-app-listgrid-item-card"
title="{{applicationInstance.name}}"
[ngClass]="[applicationInstance.theme]"
(click)="onSelectApp(applicationInstance)"
(keyup.enter)="onSelectApp(applicationInstance)">
<div class="adf-app-listgrid-item-card-logo">
<mat-icon class="adf-app-listgrid-item-card-logo-icon">{{applicationInstance.icon}}</mat-icon>
</div>
<div mat-card-title class="adf-app-listgrid-item-card-title">
<h1>{{applicationInstance.name}}</h1>
</div>
<mat-card-subtitle class="adf-app-listgrid-item-card-subtitle" fxFlex="1 0 auto">
<div class="line-clamp">{{applicationInstance.description}}</div>
</mat-card-subtitle>
</mat-card>
</div>
</div>

View File

@@ -0,0 +1,114 @@
@mixin adf-cloud-app-details-theme($theme) {
$tile-themes: (
theme-1: (bg: #269abc, color: #168aac),
theme-2: (bg: #7da9b0, color: #6d99a0),
theme-3: (bg: #7689ab, color: #66799b),
theme-4: (bg: #c74e3e, color: #b73e2e),
theme-5: (bg: #fab96c, color: #eaa95c),
theme-6: (bg: #759d4c, color: #658d3c),
theme-7: (bg: #b1b489, color: #a1a479),
theme-8: (bg: #a17299, color: #916289),
theme-9: (bg: #696c67, color: #595c57),
theme-10: (bg: #cabb33, color: #baab23)
);
.adf-app-listgrid {
padding: 8px;
&-item {
outline: none;
padding: 8px;
box-sizing: border-box;
&-card {
@for $i from 1 through 10 {
&.theme-#{$i} {
$tile-theme: map-get($tile-themes, theme-#{$i});
background-color: map-get($tile-theme, bg);
}
}
outline: none;
transition: transform 280ms cubic-bezier(.4,0,.2,1), box-shadow 280ms cubic-bezier(.4,0,.2,1);
min-height: 200px;
padding: 0 !important;
&:hover {
box-shadow: 0 8px 10px 1px rgba(0, 0, 0, .14), 0 3px 14px 2px rgba(0, 0, 0, .12), 0 5px 5px -3px rgba(0, 0, 0, .2);
cursor: pointer;
transform: scale(1.015);
}
&-logo {
position: absolute;
right: 20px;
top: 20px;
padding: 16px;
z-index: 9;
&-icon {
font-size: 70px;
width: 1em !important;
height: 1em !important;
@for $i from 1 through 10 {
.theme-#{$i} & {
$tile-theme: map-get($tile-themes, theme-#{$i});
color: map-get($tile-theme, color);
}
}
}
}
&-title {
padding: 16px;
margin-bottom: 0 !important;
z-index: 9999;
h1 {
color: white;
width: 80%;
font-size: 24px;
margin: 0;
line-height: normal;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
&-subtitle {
color: white;
z-index: 9999;
padding: 16px;
.line-clamp {
@include line-clamp(1.25, 3);
}
}
&-actions {
padding: 0 16px 16px 16px !important;
border-top: 1px solid rgba(0,0,0,.1);
min-height: 48px;
box-sizing: border-box;
&-icon {
color: #e9f1f3;
}
&.mat-card-actions {
margin-left: 0px;
margin-right: 0px;
&:last-child {
margin-bottom: 0 !important;
}
}
}
}
}
}
}

View File

@@ -0,0 +1,57 @@
/*!
* @license
* Copyright 2016 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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { setupTestBed } from '@alfresco/adf-core';
import { fakeApplicationInstance } from '../mock/app-model.mock';
import { AppDetailsCloudComponent } from './app-details-cloud.component';
import { AppListTestingModule } from '../testing/app-list.testing.module';
describe('AppDetailsCloudComponent', () => {
let component: AppDetailsCloudComponent;
let fixture: ComponentFixture<AppDetailsCloudComponent>;
setupTestBed({
imports: [AppListTestingModule]
});
beforeEach(() => {
fixture = TestBed.createComponent(AppDetailsCloudComponent);
component = fixture.componentInstance;
component.applicationInstance = fakeApplicationInstance[0];
});
it('should create AppDetailsCloudComponent ', async(() => {
expect(component instanceof AppDetailsCloudComponent).toBe(true);
}));
it('should display application name', () => {
fixture.detectChanges();
const appName = fixture.nativeElement.querySelector('.adf-app-listgrid-item-card-title');
expect(appName.innerText.trim()).toEqual(fakeApplicationInstance[0].name);
});
it('should emit a click event when app selected', () => {
spyOn(component.selectedApp, 'emit');
fixture.detectChanges();
const app = fixture.nativeElement.querySelector('.mat-card');
app.click();
expect(component.selectedApp.emit).toHaveBeenCalledWith(fakeApplicationInstance[0]);
});
});

View File

@@ -0,0 +1,44 @@
/*!
* @license
* Copyright 2016 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, Output, EventEmitter } from '@angular/core';
import { ApplicationInstanceModel } from '../models/application-instance.model';
@Component({
selector: 'adf-cloud-app-details',
templateUrl: './app-details-cloud.component.html',
styleUrls: ['./app-details-cloud.component.scss']
})
export class AppDetailsCloudComponent {
@Input()
applicationInstance: ApplicationInstanceModel;
@Output()
selectedApp: EventEmitter<ApplicationInstanceModel> = new EventEmitter<ApplicationInstanceModel>();
constructor() {}
/**
* Pass the selected app as next
* @param app
*/
public onSelectApp(app: ApplicationInstanceModel): void {
this.selectedApp.emit(app);
}
}

View File

@@ -0,0 +1,38 @@
<div class="menu-container" *ngIf="apps$ | async as appsList; else loading">
<ng-container *ngIf="appsList.length > 0; else noApps">
<div *ngIf="isGrid(); else appList" fxLayout="row wrap">
<adf-cloud-app-details fxFlex="33.33333%" fxFlex.lt-md="50%" fxFlex.lt-sm="100%" *ngFor="let app of appsList"
[applicationInstance]="app" (selectedApp)="onSelectApp($event)">
</adf-cloud-app-details>
</div>
<ng-template #appList>
<mat-list class="adf-app-list">
<mat-list-item class="adf-app-list-item" (click)="onSelectApp(app)" (keyup.enter)="onSelectApp(app)"
*ngFor="let app of appsList" tabindex="0" role="button" title="{{app.name}}">
<mat-icon matListIcon>touch_app</mat-icon>
<span matLine>{{app.name}}</span>
</mat-list-item>
</mat-list>
</ng-template>
</ng-container>
</div>
<ng-template #noApps>
<div class="adf-app-list-empty">
<ng-content select="adf-empty-custom-content" *ngIf="hasEmptyCustomContentTemplate; else defaultEmptyTemplate"
class="adf-custom-empty-template">
</ng-content>
<ng-template #defaultEmptyTemplate>
<adf-empty-content icon="apps" [title]="'ADF_TASK_LIST.APPS.TITLE' | translate" [subtitle]="'ADF_TASK_LIST.APPS.SUBTITLE' | translate">
</adf-empty-content>
</ng-template>
</div>
</ng-template>
<ng-template #loading>
<ng-container>
<div class="adf-app-list-spinner">
<mat-spinner></mat-spinner>
</div>
</ng-container>
</ng-template>

View File

@@ -0,0 +1,21 @@
@mixin adf-cloud-app-list-theme($theme) {
:host {
width: 100%;
}
.adf-app-list-item {
cursor: pointer;
}
.adf-app-list-spinner, .adf-app-list-empty {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
height: 85vh;
.mat-spinner {
margin: 0 auto;
}
}
}

View File

@@ -0,0 +1,207 @@
/*!
* @license
* Copyright 2016 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, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { setupTestBed } from '@alfresco/adf-core';
import { of } from 'rxjs';
import { fakeApplicationInstance } from '../mock/app-model.mock';
import { AppListCloudComponent } from './app-list-cloud.component';
import { AppsProcessCloudService } from '../services/apps-process-cloud.service';
import { AppListTestingModule } from '../testing/app-list.testing.module';
import { ApplicationInstanceModel } from '../models/application-instance.model';
describe('AppListCloudComponent', () => {
let component: AppListCloudComponent;
let fixture: ComponentFixture<AppListCloudComponent>;
let service: AppsProcessCloudService;
let getAppsSpy: jasmine.Spy;
setupTestBed({
imports: [AppListTestingModule],
providers: [AppsProcessCloudService]
});
beforeEach(() => {
fixture = TestBed.createComponent(AppListCloudComponent);
component = fixture.componentInstance;
service = TestBed.get(AppsProcessCloudService);
getAppsSpy = spyOn(service, 'getDeployedApplicationsByStatus').and.returnValue(of(fakeApplicationInstance));
});
it('should create AppListCloudComponent ', async(() => {
expect(component instanceof AppListCloudComponent).toBe(true);
}));
it('should define layoutType with the default value', () => {
component.layoutType = '';
fixture.detectChanges();
expect(component.isGrid()).toBe(true);
});
it('Should fetch deployed apps', async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
component.apps$.subscribe((response: ApplicationInstanceModel[]) => {
expect(response).toBeDefined();
expect(response.length).toEqual(2);
expect(response[0].name).toEqual('application-new-1');
expect(response[0].status).toEqual('Running');
expect(response[0].icon).toEqual('favorite_border');
expect(response[0].theme).toEqual('theme-2');
expect(response[1].name).toEqual('application-new-2');
expect(response[1].status).toEqual('Pending');
expect(response[1].icon).toEqual('favorite_border');
expect(response[1].theme).toEqual('theme-2');
});
expect(getAppsSpy).toHaveBeenCalled();
});
}));
it('should display default adf-empty-content template when response empty', () => {
getAppsSpy.and.returnValue(of([]));
fixture.detectChanges();
const defaultEmptyTemplate = fixture.nativeElement.querySelector('.adf-app-list-empty');
const emptyContent = fixture.debugElement.nativeElement.querySelector('.adf-empty-content');
const emptyTitle = fixture.debugElement.nativeElement.querySelector('.adf-empty-content__title');
const emptySubtitle = fixture.debugElement.nativeElement.querySelector('.adf-empty-content__subtitle');
expect(defaultEmptyTemplate).toBeDefined();
expect(defaultEmptyTemplate).not.toBeNull();
expect(emptyContent).not.toBeNull();
expect(emptyTitle.innerText).toBe('ADF_TASK_LIST.APPS.TITLE');
expect(emptySubtitle.innerText).toBe('ADF_TASK_LIST.APPS.SUBTITLE');
expect(getAppsSpy).toHaveBeenCalled();
});
describe('Grid Layout ', () => {
it('should display a grid by default', () => {
fixture.detectChanges();
expect(component.isGrid()).toBe(true);
expect(component.isList()).toBe(false);
});
it('should defined adf-cloud-app-details when layout type is grid', () => {
fixture.detectChanges();
const adfCloudDetailsElement = fixture.nativeElement.querySelectorAll('adf-cloud-app-details');
const appName = fixture.nativeElement.querySelector('.adf-app-listgrid-item-card-title');
expect(adfCloudDetailsElement).toBeDefined();
expect(adfCloudDetailsElement).not.toBeNull();
expect(adfCloudDetailsElement.length).toEqual(2);
expect(component.isGrid()).toBe(true);
expect(component.isList()).toBe(false);
expect(appName.innerText.trim()).toEqual(fakeApplicationInstance[0].name);
});
it('should display a grid when configured to', () => {
component.layoutType = AppListCloudComponent.LAYOUT_GRID;
fixture.detectChanges();
expect(component.isGrid()).toBe(true);
expect(component.isList()).toBe(false);
});
it('should throw an exception on init if unknown type configured', () => {
component.layoutType = 'unknown';
expect(component.ngOnInit).toThrowError();
});
});
describe('List Layout ', () => {
beforeEach(() => {
component.layoutType = AppListCloudComponent.LAYOUT_LIST;
});
it('should display a LIST when configured to', () => {
fixture.detectChanges();
expect(component.isGrid()).toBe(false);
expect(component.isList()).toBe(true);
});
it('should defined mat-list when layout type is LIST', () => {
fixture.detectChanges();
const appListElement = fixture.nativeElement.querySelectorAll('mat-list');
const appListItemElement = fixture.nativeElement.querySelectorAll('mat-list-item');
const appName = fixture.nativeElement.querySelector('.mat-list-text');
expect(appListElement).toBeDefined();
expect(appListElement).not.toBeNull();
expect(appListItemElement.length).toEqual(2);
expect(component.isGrid()).toBe(false);
expect(component.isList()).toBe(true);
expect(appName.innerText.trim()).toEqual(fakeApplicationInstance[0].name);
});
it('should throw an exception on init if unknown type configured', () => {
component.layoutType = 'unknown';
expect(component.ngOnInit).toThrowError();
});
});
it('should emit a click event when app selected', () => {
spyOn(component.appClick, 'emit');
fixture.detectChanges();
const onAppClick = fixture.nativeElement.querySelector('.mat-card');
onAppClick.click();
expect(component.appClick.emit).toHaveBeenCalledWith(fakeApplicationInstance[0]);
});
});
@Component({
template: `
<adf-cloud-app-list>
<adf-empty-custom-content>
<mat-icon>apps</mat-icon>
<p id="custom-id">No Apps Found</p>
</adf-empty-custom-content>
</adf-cloud-app-list>
`
})
class CustomEmptyAppListCloudTemplateComponent {
}
describe('Custom CustomEmptyAppListCloudTemplateComponent', () => {
let fixture: ComponentFixture<CustomEmptyAppListCloudTemplateComponent>;
setupTestBed({
imports: [AppListTestingModule],
declarations: [CustomEmptyAppListCloudTemplateComponent],
schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
});
beforeEach(() => {
fixture = TestBed.createComponent(CustomEmptyAppListCloudTemplateComponent);
});
afterEach(() => {
fixture.destroy();
});
it('should render the custom empty template', async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
const title: any = fixture.nativeElement.querySelector('#custom-id');
expect(title.innerText).toBe('No Apps Found');
});
}));
});

View File

@@ -0,0 +1,103 @@
/*!
* @license
* Copyright 2016 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 { EmptyCustomContentDirective } from '@alfresco/adf-core';
import { AfterContentInit, Component, EventEmitter, Input, OnInit, Output, ContentChild } from '@angular/core';
import { Observable } from 'rxjs';
import { AppsProcessCloudService } from '../services/apps-process-cloud.service';
import { ApplicationInstanceModel } from '../models/application-instance.model';
@Component({
selector: 'adf-cloud-app-list',
templateUrl: './app-list-cloud.component.html',
styleUrls: ['./app-list-cloud.component.scss']
})
export class AppListCloudComponent implements OnInit, AfterContentInit {
public static LAYOUT_LIST: string = 'LIST';
public static LAYOUT_GRID: string = 'GRID';
public static RUNNING_STATUS: string = 'Running';
@ContentChild(EmptyCustomContentDirective)
emptyCustomContent: EmptyCustomContentDirective;
/** (**required**) Defines the layout of the apps. There are two possible
* values, "GRID" and "LIST".
*/
@Input()
layoutType: string = AppListCloudComponent.LAYOUT_GRID;
/** Emitted when an app entry is clicked. */
@Output()
appClick: EventEmitter<ApplicationInstanceModel> = new EventEmitter<ApplicationInstanceModel>();
apps$: Observable<any>;
hasEmptyCustomContentTemplate: boolean = false;
constructor(private appsProcessCloudService: AppsProcessCloudService) { }
ngOnInit() {
if (!this.isValidType()) {
this.setDefaultLayoutType();
}
this.apps$ = this.appsProcessCloudService.getDeployedApplicationsByStatus(AppListCloudComponent.RUNNING_STATUS);
}
ngAfterContentInit() {
if (this.emptyCustomContent) {
this.hasEmptyCustomContentTemplate = true;
}
}
onSelectApp(app: ApplicationInstanceModel): void {
this.appClick.emit(app);
}
/**
* Check if the value of the layoutType property is an allowed value
*/
isValidType(): boolean {
if (this.layoutType && (this.layoutType === AppListCloudComponent.LAYOUT_LIST || this.layoutType === AppListCloudComponent.LAYOUT_GRID)) {
return true;
}
return false;
}
/**
* Assign the default value to LayoutType
*/
setDefaultLayoutType(): void {
this.layoutType = AppListCloudComponent.LAYOUT_GRID;
}
/**
* Return true if the layout type is LIST
*/
isList(): boolean {
return this.layoutType === AppListCloudComponent.LAYOUT_LIST;
}
/**
* Return true if the layout type is GRID
*/
isGrid(): boolean {
return this.layoutType === AppListCloudComponent.LAYOUT_GRID;
}
}

View File

@@ -0,0 +1,23 @@
/*!
* @license
* Copyright 2016 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 { ApplicationInstanceModel } from '../models/application-instance.model';
export let fakeApplicationInstance = [
new ApplicationInstanceModel(
{ name: 'application-new-1', createdAt: '2018-09-21T12:31:39.000Z', status: 'Running', theme: 'theme-2', icon: 'favorite_border' }),
new ApplicationInstanceModel(
{ name: 'application-new-2', createdAt: '2018-09-21T12:31:39.000Z', status: 'Pending', theme: 'theme-2', icon: 'favorite_border' })
];

View File

@@ -0,0 +1,42 @@
/*!
* @license
* Copyright 2016 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 ApplicationInstanceModel {
public static DEFAULT_THEME: string = 'theme-2';
public static DEFAULT_ICON: string = 'favorite_border';
name: string;
createdAt: any;
status: string;
theme?: string;
icon?: string;
description?: string;
connectors?: any;
constructor(obj?: any) {
if (obj) {
this.name = obj.name ? obj.name : null;
this.status = obj.status ? obj.status : null;
this.createdAt = obj.createdAt ? obj.createdAt : null;
this.theme = obj.theme ? obj.theme : ApplicationInstanceModel.DEFAULT_THEME;
this.icon = obj.icon ? obj.icon : ApplicationInstanceModel.DEFAULT_ICON;
this.description = obj.description ? obj.description : null;
this.connectors = obj.connectors ? obj.connectors : null;
}
}
}

View File

@@ -0,0 +1,20 @@
/*!
* @license
* Copyright 2016 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 * from './components/app-list-cloud.component';
export * from './models/application-instance.model';
export * from './apps-list-cloud.module';

View File

@@ -0,0 +1,73 @@
/*!
* @license
* Copyright 2016 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 { of, throwError } from 'rxjs';
import { setupTestBed } from '@alfresco/adf-core';
import { HttpErrorResponse } from '@angular/common/http';
import { AppsProcessCloudService } from './apps-process-cloud.service';
import { fakeApplicationInstance } from '../mock/app-model.mock';
import { ApplicationInstanceModel } from '../models/application-instance.model';
import { AppListTestingModule } from '../testing/app-list.testing.module';
describe('AppsProcessCloudService', () => {
let service: AppsProcessCloudService;
setupTestBed({
imports: [AppListTestingModule],
providers: [AppsProcessCloudService]
});
beforeEach(() => {
service = TestBed.get(AppsProcessCloudService);
});
it('should get the deployed applications ', (done) => {
spyOn(service, 'getDeployedApplicationsByStatus').and.returnValue(of(fakeApplicationInstance));
service.getDeployedApplicationsByStatus('fake').subscribe(
(res: ApplicationInstanceModel[]) => {
expect(res).toBeDefined();
expect(res.length).toEqual(2);
expect(res).toEqual(fakeApplicationInstance);
expect(res[0]).toEqual(fakeApplicationInstance[0]);
expect(res[0].name).toEqual('application-new-1');
expect(res[1]).toEqual(fakeApplicationInstance[1]);
expect(res[1].name).toEqual('application-new-2');
done();
}
);
});
it('Should not fetch deployed applications if error occurred', () => {
const errorResponse = new HttpErrorResponse({
error: 'Mock Error',
status: 404, statusText: 'Not Found'
});
spyOn(service, 'getDeployedApplicationsByStatus').and.returnValue(throwError(errorResponse));
service.getDeployedApplicationsByStatus('fake')
.subscribe(
users => fail('expected an error, not applications'),
error => {
expect(error.status).toEqual(404);
expect(error.statusText).toEqual('Not Found');
expect(error.error).toEqual('Mock Error');
}
);
});
});

View File

@@ -0,0 +1,69 @@
/*!
* @license
* Copyright 2016 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 { map, catchError } from 'rxjs/operators';
import { AlfrescoApiService } from '@alfresco/adf-core';
import { AppConfigService, LogService } from '@alfresco/adf-core';
import { ApplicationInstanceModel } from '../models/application-instance.model';
@Injectable()
export class AppsProcessCloudService {
contextRoot = '';
constructor(
private apiService: AlfrescoApiService,
private logService: LogService,
private appConfig: AppConfigService) {
this.contextRoot = this.appConfig.get('bpmHost', '');
}
/**
* Gets a list of deployed apps for this user by status.
* @returns The list of deployed apps
*/
getDeployedApplicationsByStatus(status: string): Observable<ApplicationInstanceModel[]> {
const api: any = this.apiService.getInstance().oauth2Auth;
api.basePath = this.contextRoot;
const path = 'alfresco-deployment-service/v1/applications';
const httpMethod = 'GET', pathParams = {}, queryParams = {},
headerParams = {}, formParams = {}, bodyParam = {}, authNames = [],
contentTypes = ['application/json'], accepts = ['application/json'];
return from(api.callApi(
path, httpMethod,
pathParams, queryParams, headerParams, formParams, bodyParam,
authNames, contentTypes, accepts, [], ''
))
.pipe(
map((apps: Array<{}>) => {
return apps.filter((app: ApplicationInstanceModel) => app.status === status)
.map((app) => {
return new ApplicationInstanceModel(app);
});
}
),
catchError(err => this.handleError(err))
);
}
private handleError(error?: any) {
this.logService.error(error);
return throwError(error || 'Server error');
}
}

View File

@@ -0,0 +1,47 @@
/*!
* @license
* Copyright 2016 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 { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { HttpClientModule } from '@angular/common/http';
import { AppListCloudModule } from '../app-list-cloud.module';
import {
AlfrescoApiService,
AlfrescoApiServiceMock,
AppConfigService,
AppConfigServiceMock,
StorageService,
LogService,
TranslationService,
TranslationMock
} from '@alfresco/adf-core';
@NgModule({
imports: [
HttpClientModule,
NoopAnimationsModule,
AppListCloudModule
],
providers: [
{ provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock },
{ provide: AppConfigService, useClass: AppConfigServiceMock },
{ provide: TranslationService, useClass: TranslationMock },
StorageService,
LogService
]
})
export class AppListTestingModule {}

View File

@@ -0,0 +1,44 @@
/*!
* @license
* Copyright 2016 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 {
MAT_LABEL_GLOBAL_OPTIONS, MatAutocompleteModule, MatButtonModule, MatCardModule, MatCheckboxModule,
MatChipsModule, MatDatepickerModule, MatDialogModule, MatGridListModule, MatIconModule,
MatInputModule, MatListModule, MatNativeDateModule, MatOptionModule, MatProgressSpinnerModule, MatRadioModule,
MatRippleModule, MatSelectModule, MatSlideToggleModule, MatTableModule, MatTabsModule,
MatTooltipModule, MatMenuModule
} from '@angular/material';
export function modules() {
return [
MatAutocompleteModule, MatButtonModule, MatCardModule, MatDialogModule,
MatCheckboxModule, MatDatepickerModule, MatGridListModule, MatIconModule, MatInputModule,
MatListModule, MatOptionModule, MatRadioModule, MatSelectModule, MatSlideToggleModule, MatTableModule,
MatTabsModule, MatProgressSpinnerModule, MatNativeDateModule, MatRippleModule, MatTooltipModule,
MatChipsModule, MatMenuModule
];
}
@NgModule({
providers: [
{provide: MAT_LABEL_GLOBAL_OPTIONS, useValue: { float: 'never' }}
],
imports: modules(),
exports: modules()
})
export class MaterialModule {}

View File

@@ -1,9 +1,10 @@
import { NgModule } from '@angular/core';
import { HelloModule } from './hello/hello.module';
import { TRANSLATION_PROVIDER } from '@alfresco/adf-core';
import { AppListCloudModule } from './app-list-cloud/app-list-cloud.module';
@NgModule({
imports: [
HelloModule
AppListCloudModule
],
providers: [
{
@@ -16,6 +17,6 @@ import { TRANSLATION_PROVIDER } from '@alfresco/adf-core';
}
],
declarations: [],
exports: [HelloModule]
exports: [AppListCloudModule]
})
export class ProcessServicesCloudModule { }

View File

@@ -1,2 +1,7 @@
@import './../app-list-cloud/components/app-details-cloud.component';
@import './../app-list-cloud/components/app-list-cloud.component';
@mixin adf-process-services-cloud-theme($theme) {
@include adf-cloud-app-list-theme($theme);
@include adf-cloud-app-details-theme($theme);
}

View File

@@ -16,4 +16,4 @@
*/
export * from './lib/process-services-cloud.module';
export * from './lib/hello/hello.module';
export * from './lib/app-list-cloud/app-list-cloud.module';

View File

@@ -0,0 +1,41 @@
/*!
* @license
* Copyright 2016 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 'core-js/es7/reflect';
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
declare const pdfjsLib: any;
pdfjsLib.GlobalWorkerOptions.workerSrc = 'node_modules/pdfjs-dist/build/pdf.worker.min.js';
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@@ -0,0 +1,42 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"skipLibCheck": false,
"noLib": false,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"noImplicitAny": false,
"noImplicitReturns": false,
"noImplicitUseStrict": false,
"noFallthroughCasesInSwitch": true,
"removeComments": true,
"declaration": true,
"outDir": "../dist/process-services-cloud/",
"baseUrl" : "./",
"paths": {
"@alfresco/adf-content-services": ["../content-services"],
"@alfresco/adf-insights": ["../analytics"],
"@alfresco/adf-core": ["../core"]
},
"lib": [
"es2016",
"dom"
],
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true
},
"exclude": [
"demo",
"node_modules",
"dist"
],
"angularCompilerOptions": {
"strictMetadataEmit": false,
"skipTemplateCodegen": true
}
}

File diff suppressed because it is too large Load Diff