diff --git a/demo-shell-ng2/app/app.module.ts b/demo-shell-ng2/app/app.module.ts index 1726859603..a1d7fcf93e 100644 --- a/demo-shell-ng2/app/app.module.ts +++ b/demo-shell-ng2/app/app.module.ts @@ -31,6 +31,7 @@ import { ActivitiFormModule } from 'ng2-activiti-form'; import { ActivitiTaskListModule } from 'ng2-activiti-tasklist'; import { ActivitiProcessListModule } from 'ng2-activiti-processlist'; import { UserInfoComponentModule } from 'ng2-alfresco-userinfo'; +import { AnalyticsModule } from 'ng2-activiti-analytics'; import { AppComponent } from './app.component'; import { routing } from './app.routes'; @@ -65,7 +66,8 @@ import { ActivitiFormModule.forRoot(), ActivitiTaskListModule.forRoot(), ActivitiProcessListModule.forRoot(), - UserInfoComponentModule.forRoot() + UserInfoComponentModule.forRoot(), + AnalyticsModule.forRoot() ], declarations: [ AppComponent, diff --git a/demo-shell-ng2/app/components/activiti/activiti-demo.component.html b/demo-shell-ng2/app/components/activiti/activiti-demo.component.html index fd462fa8bd..a9a9a45019 100644 --- a/demo-shell-ng2/app/components/activiti/activiti-demo.component.html +++ b/demo-shell-ng2/app/components/activiti/activiti-demo.component.html @@ -63,7 +63,16 @@
-
+
+
+
+ +
+
+ +
+
+
diff --git a/demo-shell-ng2/app/components/activiti/activiti-demo.component.ts b/demo-shell-ng2/app/components/activiti/activiti-demo.component.ts index 107d8d525a..19fcc8b356 100644 --- a/demo-shell-ng2/app/components/activiti/activiti-demo.component.ts +++ b/demo-shell-ng2/app/components/activiti/activiti-demo.component.ts @@ -79,6 +79,7 @@ export class ActivitiDemoComponent implements AfterViewChecked { processSchemaColumns: any [] = []; taskFilter: any; + report: any; processFilter: any; sub: Subscription; @@ -140,6 +141,10 @@ export class ActivitiDemoComponent implements AfterViewChecked { this.taskFilter = event; } + onReportClick(event: any) { + this.report = event; + } + onSuccessTaskFilterList(event: any) { this.taskFilter = this.activitifilter.getCurrentFilter(); } diff --git a/demo-shell-ng2/index.html b/demo-shell-ng2/index.html index f108c3dfa6..3294099a58 100644 --- a/demo-shell-ng2/index.html +++ b/demo-shell-ng2/index.html @@ -14,6 +14,9 @@ + + @@ -34,6 +37,9 @@ + + + diff --git a/demo-shell-ng2/systemjs.config.js b/demo-shell-ng2/systemjs.config.js index 8726219617..73016570b9 100644 --- a/demo-shell-ng2/systemjs.config.js +++ b/demo-shell-ng2/systemjs.config.js @@ -23,6 +23,8 @@ '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', // other libraries 'rxjs': 'npm:rxjs', + 'moment': 'npm:moment/min/moment.min.js', + 'ng2-charts' : 'npm:ng2-charts', 'ng2-translate': 'npm:ng2-translate', 'ng2-alfresco-core': 'npm:ng2-alfresco-core/dist', 'ng2-alfresco-datatable': 'npm:ng2-alfresco-datatable/dist', @@ -37,7 +39,8 @@ 'ng2-activiti-tasklist': 'npm:ng2-activiti-tasklist/dist', 'alfresco-js-api': 'npm:alfresco-js-api/dist', 'ng2-activiti-processlist': 'npm:ng2-activiti-processlist/dist', - 'ng2-alfresco-userinfo': 'npm:ng2-alfresco-userinfo/dist' + 'ng2-alfresco-userinfo': 'npm:ng2-alfresco-userinfo/dist', + 'ng2-activiti-analytics': 'npm:ng2-activiti-analytics/dist' }, // packages tells the System loader how to load when no filename and/or no extension packages: { @@ -49,6 +52,7 @@ defaultExtension: 'js' }, 'ng2-translate': { defaultExtension: 'js' }, + 'ng2-charts': { defaultExtension: 'js' }, 'ng2-alfresco-core': { main: './index.js', defaultExtension: 'js'}, 'ng2-alfresco-datatable': { main: './index.js', defaultExtension: 'js'}, @@ -63,7 +67,8 @@ 'ng2-alfresco-webscript': { main: './index.js', defaultExtension: 'js'}, 'ng2-alfresco-tag': { main: './index.js', defaultExtension: 'js'}, 'alfresco-js-api': { main: './alfresco-js-api.js', defaultExtension: 'js'}, - 'ng2-alfresco-userinfo': { main: './index.js', defaultExtension: 'js'} + 'ng2-alfresco-userinfo': { main: './index.js', defaultExtension: 'js'}, + 'ng2-activiti-analytics': { main: './index.js', defaultExtension: 'js'} } }); })(this); diff --git a/ng2-components/ng2-activiti-analytics/index.ts b/ng2-components/ng2-activiti-analytics/index.ts index 279de053d7..c06595d22a 100644 --- a/ng2-components/ng2-activiti-analytics/index.ts +++ b/ng2-components/ng2-activiti-analytics/index.ts @@ -17,13 +17,27 @@ import { NgModule, ModuleWithProviders } from '@angular/core'; import { CoreModule } from 'ng2-alfresco-core'; -import { AnalyticsComponent } from './src/components/analytics.component'; + +import { AnalyticsReportListComponent } from './src/components/analytics-report-list.component'; +import { AnalyticsComponent } from './src/components/analytics.component'; +import { AnalyticsService } from './src/services/analytics.service'; import { CHART_DIRECTIVES } from 'ng2-charts/ng2-charts'; +import { WIDGET_DIRECTIVES } from './src/components/widgets/index'; + export * from './src/components/analytics.component'; +export * from './src/components/analytics-report-list.component'; +export * from './src/services/analytics.service'; +export * from './src/components/widgets/index'; export const ANALYTICS_DIRECTIVES: any[] = [ - AnalyticsComponent + AnalyticsComponent, + AnalyticsReportListComponent, + WIDGET_DIRECTIVES +]; + +export const ANALYTICS_PROVIDERS: any[] = [ + AnalyticsService ]; @NgModule({ @@ -32,7 +46,10 @@ export const ANALYTICS_DIRECTIVES: any[] = [ ], declarations: [ ...ANALYTICS_DIRECTIVES, - CHART_DIRECTIVES + ...CHART_DIRECTIVES + ], + providers: [ + ...ANALYTICS_PROVIDERS ], exports: [ ...ANALYTICS_DIRECTIVES @@ -43,7 +60,7 @@ export class AnalyticsModule { return { ngModule: AnalyticsModule, providers: [ - ...ANALYTICS_DIRECTIVES + ...ANALYTICS_PROVIDERS ] }; } diff --git a/ng2-components/ng2-activiti-analytics/karma-test-shim.js b/ng2-components/ng2-activiti-analytics/karma-test-shim.js index 688f465ff2..c9d37f3aa2 100644 --- a/ng2-components/ng2-activiti-analytics/karma-test-shim.js +++ b/ng2-components/ng2-activiti-analytics/karma-test-shim.js @@ -52,10 +52,11 @@ var map = { // other libraries 'rxjs': 'npm:rxjs', 'ng2-translate': 'npm:ng2-translate', - 'ng2-charts' : 'npm:ng2-charts', + 'moment' : 'npm:moment/min/moment.min.js', 'alfresco-js-api': 'npm:alfresco-js-api/dist', + 'ng2-activiti-analytics': 'npm:ng2-activiti-analytics/dist', 'ng2-alfresco-core': 'npm:ng2-alfresco-core/dist' }; @@ -63,9 +64,11 @@ var packages = { 'app': { main: 'main.js', defaultExtension: 'js' }, 'rxjs': { defaultExtension: 'js' }, 'ng2-translate': { defaultExtension: 'js' }, + 'ng2-charts': { defaultExtension: 'js' }, + 'moment': { defaultExtension: 'js' }, - 'ng2-charts' : '/base/node_modules/ng2-charts', 'alfresco-js-api': { main: './alfresco-js-api.js', defaultExtension: 'js'}, + 'ng2-activiti-analytics': { main: './index.js', defaultExtension: 'js'}, 'ng2-alfresco-core': { main: './index.js', defaultExtension: 'js'} }; @@ -83,17 +86,17 @@ System.import('@angular/core/testing') function initTestBed(){ return Promise.all([ - System.import('@angular/core/testing'), - System.import('@angular/platform-browser-dynamic/testing') - ]) - .then(function (providers) { - var coreTesting = providers[0]; - var browserTesting = providers[1]; + System.import('@angular/core/testing'), + System.import('@angular/platform-browser-dynamic/testing') + ]) + .then(function (providers) { + var coreTesting = providers[0]; + var browserTesting = providers[1]; - coreTesting.TestBed.initTestEnvironment( - browserTesting.BrowserDynamicTestingModule, - browserTesting.platformBrowserDynamicTesting()); - }) + coreTesting.TestBed.initTestEnvironment( + browserTesting.BrowserDynamicTestingModule, + browserTesting.platformBrowserDynamicTesting()); + }) } // Import all spec files and start karma @@ -102,6 +105,6 @@ function initTesting () { allSpecFiles.map(function (moduleName) { return System.import(moduleName); }) - ) - .then(__karma__.start, __karma__.error); + ) + .then(__karma__.start, __karma__.error); } diff --git a/ng2-components/ng2-activiti-analytics/karma.conf.js b/ng2-components/ng2-activiti-analytics/karma.conf.js index 2e7c9d4430..49636788ba 100644 --- a/ng2-components/ng2-activiti-analytics/karma.conf.js +++ b/ng2-components/ng2-activiti-analytics/karma.conf.js @@ -34,7 +34,6 @@ module.exports = function (config) { 'node_modules/alfresco-js-api/dist/alfresco-js-api.js', {pattern: 'node_modules/ng2-translate/**/*.js', included: false, watched: false}, - {pattern: 'node_modules/ng2-translate/**/*.js.map', included: false, watched: false}, 'karma-test-shim.js', @@ -44,9 +43,9 @@ module.exports = function (config) { {pattern: 'dist/**/*.css', included: true, served: true, watched: true}, // ng2-components - {pattern: 'node_modules/ng2-alfresco-core/dist/**/*.*', included: false, served: true, watched: false}, - - {pattern: 'node_modules/ng2-charts/**/*.js', included: false, served: true, watched: false}, + { pattern: 'node_modules/ng2-alfresco-core/dist/**/*.*', included: false, served: true, watched: false }, + { pattern: 'node_modules/ng2-charts/**/*.js', included: false, served: true, watched: false }, + { pattern: 'node_modules/moment/**/*.js', included: false, served: true, watched: false }, // paths to support debugging with source maps in dev tools {pattern: 'src/**/*.ts', included: false, watched: false}, diff --git a/ng2-components/ng2-activiti-analytics/package.json b/ng2-components/ng2-activiti-analytics/package.json index 76ae3079e9..6aa9bfd540 100644 --- a/ng2-components/ng2-activiti-analytics/package.json +++ b/ng2-components/ng2-activiti-analytics/package.json @@ -3,6 +3,8 @@ "description": "Activiti Angular2 Analytics Component", "version": "0.3.3", "author": "Alfresco Software, Ltd.", + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", "scripts": { "clean": "npm install rimraf && rimraf dist node_modules typings", "build": "npm run tslint && rimraf dist && tsc && npm run copy-dist && license-check", @@ -21,8 +23,16 @@ "prepublish": "npm run build", "travis": "echo 'placeholder'" }, - "main": "./dist/index.js", - "typings": "./dist/index.d.ts", + "contributors": [ + { + "name": "Mario Romano", + "email": "mario.romnao@alfresco.com" + }, + { + "name": "Maurizio Vitale", + "email": "maurizio.vitale84@gmail.com" + } + ], "repository": { "type": "git", "url": "https://github.com/Alfresco/alfresco-ng2-components.git" @@ -30,21 +40,6 @@ "bugs": { "url": "https://github.com/Alfresco/alfresco-ng2-components/issues" }, - "license": "Apache-2.0", - "contributors": [ - { - "name": "Mario Romano", - "email": "mario.romnao@alfresco.com" - } - ], - "keywords": [ - "ng2", - "angular", - "angular2", - "analytics", - "alfresco-component", - "alfresco" - ], "dependencies": { "@angular/common": "2.0.0", "@angular/compiler": "2.0.0", @@ -56,38 +51,44 @@ "@angular/router": "3.0.0", "@angular/upgrade": "2.0.0", "@types/node": "^6.0.42", + "alfresco-js-api": "^0.3.0", + "chart.js": "^2.1.4", "core-js": "^2.4.1", + "md-date-time-picker": "^2.2.0", + "ng2-alfresco-core": "0.3.2", + "ng2-charts": "1.1.0", + "ng2-translate": "2.5.0", "reflect-metadata": "^0.1.3", "rxjs": "5.0.0-beta.12", "systemjs": "0.19.27", - "zone.js": "^0.6.23", - "ng2-translate": "2.5.0", - "alfresco-js-api": "^0.3.0", - "ng2-alfresco-core": "0.3.2", - "ng2-charts": "1.1.0", - "chart.js": "^2.1.4" + "zone.js": "^0.6.23" }, "devDependencies": { "@types/core-js": "^0.9.32", "@types/jasmine": "^2.2.33", "concurrently": "^2.2.0", - "cpx": "1.3.1", + "cpx": "^1.3.1", + "jasmine-ajax": "^3.2.0", "jasmine-core": "2.4.1", - "karma": "0.13.22", - "karma-chrome-launcher": "1.0.1", - "karma-coverage": "1.0.0", - "karma-jasmine": "1.0.2", - "karma-jasmine-ajax": "0.1.13", - "karma-mocha-reporter": "2.0.3", - "karma-jasmine-html-reporter": "0.2.0", - "license-check": "1.1.5", + "karma": "~0.13.22", + "karma-chrome-launcher": "~1.0.1", + "karma-coverage": "^1.0.0", + "karma-jasmine": "~1.0.2", + "karma-jasmine-ajax": "^0.1.13", + "karma-jasmine-html-reporter": "^0.2.0", + "karma-mocha-reporter": "^2.0.3", + "license-check": "^1.0.4", + "remap-istanbul": "^0.6.3", "rimraf": "2.5.2", - "remap-istanbul": "0.6.3", - "traceur": "0.0.91", - "tslint": "3.8.1", + "traceur": "^0.0.91", + "tslint": "^3.8.1", "typescript": "^2.0.3", "wsrv": "^0.1.5" }, + "keywords": [ + "tag", + "alfresco-component" + ], "license-check-config": { "src": [ "./dist/**/*.js" diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.component.css b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.component.html b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.component.html new file mode 100644 index 0000000000..fd2d30deaa --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.component.html @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.component.spec.ts b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.component.spec.ts new file mode 100644 index 0000000000..702d31d9e8 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.component.spec.ts @@ -0,0 +1,131 @@ +/*! + * @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 { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { CoreModule } from 'ng2-alfresco-core'; +import { AnalyticsReportListComponent } from '../components/analytics-report-list.component'; +import { AnalyticsService } from '../services/analytics.service'; +import { DebugElement } from '@angular/core'; + +declare let jasmine: any; + +describe('Test ng2-activiti-analytics Report list', () => { + + let reportList = [ + {'id': 2002, 'name': 'Fake Test Process definition heat map'}, + {'id': 2003, 'name': 'Fake Test Process definition overview'}, + {'id': 2004, 'name': 'Fake Test Process instances overview'}, + {'id': 2005, 'name': 'Fake Test Task overview'}, + {'id': 2006, 'name': 'Fake Test Task service level agreement'} + ]; + + let reportSelected = {'id': 2003, 'name': 'Fake Test Process definition overview'}; + + let component: any; + let fixture: ComponentFixture; + let debug: DebugElement; + let element: HTMLElement; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + CoreModule + ], + declarations: [ + AnalyticsReportListComponent + ], + providers: [ + AnalyticsService + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AnalyticsReportListComponent); + component = fixture.componentInstance; + debug = fixture.debugElement; + element = fixture.nativeElement; + }); + + describe('Rendering tests', () => { + + beforeEach(() => { + jasmine.Ajax.install(); + }); + + afterEach(() => { + jasmine.Ajax.uninstall(); + }); + + it('Report return true with undefined reports', () => { + expect(component.isReportsEmpty()).toBeTruthy(); + + }); + + it('Report return true with an empty reports', () => { + component.reports = []; + expect(component.isReportsEmpty()).toBeTruthy(); + }); + + it('Report render the report list relative to a single app', (done) => { + fixture.detectChanges(); + + component.onSuccess.subscribe(() => { + fixture.detectChanges(); + expect(element.querySelector('#report-list-0 > i').innerHTML).toBe('assignment'); + expect(element.querySelector('#report-list-0 > span').innerHTML).toBe('Fake Test Process definition heat map'); + expect(element.querySelector('#report-list-1 > span').innerHTML).toBe('Fake Test Process definition overview'); + expect(element.querySelector('#report-list-2 > span').innerHTML).toBe('Fake Test Process instances overview'); + expect(element.querySelector('#report-list-3 > span').innerHTML).toBe('Fake Test Task overview'); + expect(element.querySelector('#report-list-4 > span').innerHTML).toBe('Fake Test Task service level agreement'); + expect(component.isReportsEmpty()).toBeFalsy(); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 200, + contentType: 'json', + responseText: reportList + }); + }); + + it('Report emit an error with a empty response', (done) => { + fixture.detectChanges(); + + component.onError.subscribe((err) => { + expect(err).toBeDefined(); + done(); + }); + + jasmine.Ajax.requests.mostRecent().respondWith({ + status: 404, + contentType: 'json', + responseText: [] + }); + }); + + it('Should return the current report when one report is selected', () => { + component.reportClick.subscribe(() => { + expect(component.currentReport).toEqual(reportSelected); + }); + + component.selectReport(reportSelected); + }); + + }); + +}); diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.component.ts b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.component.ts new file mode 100644 index 0000000000..266f4be5aa --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-list.component.ts @@ -0,0 +1,93 @@ +/*! + * @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, EventEmitter, OnInit, Output } from '@angular/core'; +import { AlfrescoAuthenticationService } from 'ng2-alfresco-core'; +import { AnalyticsService } from '../services/analytics.service'; +import { ReportModel } from '../models/report.model'; +import { Observer } from 'rxjs/Observer'; +import { Observable } from 'rxjs/Observable'; + + +@Component({ + moduleId: module.id, + selector: 'analytics-report-list', + templateUrl: './analytics-report-list.component.html', + styleUrls: ['./analytics-report-list.component.css'] +}) +export class AnalyticsReportListComponent implements OnInit { + + @Output() + reportClick: EventEmitter = new EventEmitter(); + + @Output() + onSuccess = new EventEmitter(); + + @Output() + onError = new EventEmitter(); + + private reportObserver: Observer; + report$: Observable; + + currentReport: any; + + reports: ReportModel[] = []; + + constructor(private auth: AlfrescoAuthenticationService, + private analyticsService: AnalyticsService) { + + this.report$ = new Observable(observer => this.reportObserver = observer).share(); + } + + ngOnInit() { + this.report$.subscribe((report: ReportModel) => { + this.reports.push(report); + }); + + this.getReportListByAppId(); + } + + + getReportListByAppId() { + this.analyticsService.getReportList().subscribe( + (res: ReportModel[]) => { + res.forEach((report) => { + this.reportObserver.next(report); + }); + this.onSuccess.emit(res); + }, + (err: any) => { + this.onError.emit(err); + console.log(err); + }, + () => console.log('Reports loaded') + ); + } + + isReportsEmpty(): boolean { + return this.reports === undefined || (this.reports && this.reports.length === 0); + } + + /** + * Select the current report + * @param report + */ + public selectReport(report: any) { + this.currentReport = report; + this.reportClick.emit(report); + } +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics.component.css b/ng2-components/ng2-activiti-analytics/src/components/analytics.component.css new file mode 100644 index 0000000000..096757598a --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics.component.css @@ -0,0 +1,25 @@ +.chart {display: block; width: 100%;} + +.dropdown-widget { + width: 100%; +} + +.dropdown-widget__select { + width: 100%; +} + +.dropdown-widget__invalid .dropdown-widget__select { + border-color: #d50000; +} + +.dropdown-widget__invalid .dropdown-widget__label { + color: #d50000; +} + +.dropdown-widget__invalid .dropdown-widget__label:after { + background-color: #d50000; +} + +.dropdown-widget__invalid .mdl-textfield__error { + visibility: visible !important; +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics.component.html b/ng2-components/ng2-activiti-analytics/src/components/analytics.component.html index 5bb81bc756..9409f13586 100644 --- a/ng2-components/ng2-activiti-analytics/src/components/analytics.component.html +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics.component.html @@ -1,21 +1,113 @@
+
+
+

{{reportDetails.name}}

+
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+ UNKNOWN WIDGET TYPE: {{field.type}} +
+
+
+

+ +
+

ReportForm : {{ reportForm.value | json }}

+

ReportForm valid : {{ reportForm.valid }}

+

ReportForm status : {{ reportForm.errors | json }}

+

ReportForm FormGroup valid : {{ reportForm.controls.dateRange.valid | json }}

+
+
- -
-
- -
-
- -
+
+

{{report.title}}

+
+
+
+ +
+
+
+ + + + + + + +
{{label | translate}}
{{row | translate }}
+
+
+ + + + + + + +
{{label | translate}}
{{row | translate }}
+
+
+
+ +
+
+
+ UNKNOWN WIDGET TYPE: {{report.type}} +
+
+
+
+ \ No newline at end of file diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics.component.spec.ts b/ng2-components/ng2-activiti-analytics/src/components/analytics.component.spec.ts index 7f61019dd0..52ff7da466 100644 --- a/ng2-components/ng2-activiti-analytics/src/components/analytics.component.spec.ts +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics.component.spec.ts @@ -16,16 +16,30 @@ */ import { ComponentFixture, TestBed, async } from '@angular/core/testing'; -import { AnalyticsComponent } from './analytics.component'; -import { DebugElement } from '@angular/core'; import { - AlfrescoAuthenticationService, - AlfrescoSettingsService, - AlfrescoApiService, CoreModule } from 'ng2-alfresco-core'; -describe('Test ng2-alfresco-analytics analytics component ', () => { +import { AnalyticsReportListComponent } from '../components/analytics-report-list.component'; +import { AnalyticsComponent } from '../components/analytics.component'; +import { WIDGET_DIRECTIVES } from '../components/widgets/index'; +import { CHART_DIRECTIVES } from 'ng2-charts/ng2-charts'; + +import { AnalyticsService } from '../services/analytics.service'; + +import { DebugElement } from '@angular/core'; + +export const ANALYTICS_DIRECTIVES: any[] = [ + AnalyticsComponent, + AnalyticsReportListComponent, + WIDGET_DIRECTIVES +]; +export const ANALYTICS_PROVIDERS: any[] = [ + AnalyticsService +]; + + +describe('Show component HTML', () => { let component: any; let fixture: ComponentFixture; @@ -37,25 +51,26 @@ describe('Test ng2-alfresco-analytics analytics component ', () => { imports: [ CoreModule ], - declarations: [AnalyticsComponent], + declarations: [ + ...ANALYTICS_DIRECTIVES, + ...CHART_DIRECTIVES + ], providers: [ - AlfrescoSettingsService, - AlfrescoAuthenticationService, - AlfrescoApiService + ...ANALYTICS_PROVIDERS ] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(AnalyticsComponent); - + component = fixture.componentInstance; debug = fixture.debugElement; element = fixture.nativeElement; - component = fixture.componentInstance; fixture.detectChanges(); }); - xit('No test', () => { + it('Display component tag base-chart', () => { + expect(true).toBe(true); }); -}); +}); diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics.component.ts b/ng2-components/ng2-activiti-analytics/src/components/analytics.component.ts index 92f4aa7c40..cb897fe774 100644 --- a/ng2-components/ng2-activiti-analytics/src/components/analytics.component.ts +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics.component.ts @@ -15,35 +15,170 @@ * limitations under the License. */ -import { Component } from '@angular/core'; +import { Component, EventEmitter, OnInit, OnChanges, Input, Output, SimpleChanges, ViewChild } from '@angular/core'; +import { AlfrescoTranslationService } from 'ng2-alfresco-core'; +import { AnalyticsService } from '../services/analytics.service'; +import { ReportModel, ReportQuery, ParameterValueModel, ReportParameterModel } from '../models/report.model'; +import { Chart } from '../models/chart.model'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import * as moment from 'moment'; @Component({ moduleId: module.id, selector: 'activiti-analytics', - templateUrl: './analytics.component.html' + templateUrl: './analytics.component.html', + styleUrls: ['./analytics.component.css'] }) -export class AnalyticsComponent { +export class AnalyticsComponent implements OnInit, OnChanges { - constructor() { + @ViewChild('processDefinition') + processDefinition: any; + + @Input() + reportId: string; + + @Output() + onSuccess = new EventEmitter(); + + @Output() + onError = new EventEmitter(); + + reportDetails: ReportModel; + + reportParamQuery = new ReportQuery(); + + reports: any[]; + + reportForm: FormGroup; + + debug: boolean = true; + + constructor(private translate: AlfrescoTranslationService, + private analyticsService: AnalyticsService, + private formBuilder: FormBuilder ) { console.log('AnalyticsComponent'); + if (translate) { + translate.addTranslationFolder('node_modules/ng2-activiti-analytics/src'); + } } - // lineChart - public lineChartData: Array = [ - [65, 59, 80, 81, 56, 55, 40], - [28, 48, 40, 19, 86, 27, 90] - ]; - public lineChartLabels: Array = ['January', 'February', 'March', 'April', 'May', 'June', 'July']; - public lineChartType: string = 'line'; - public pieChartType: string = 'pie'; + ngOnInit() { + let today = moment().format('YYYY-MM-DD'); + this.reportForm = this.formBuilder.group({ + dateRange: this.formBuilder.group({ + startDate: [today, Validators.required], + endDate: [today, Validators.required] + }) + }); + } - // Pie - public pieChartLabels: string[] = ['Download Sales', 'In-Store Sales', 'Mail Sales']; - public pieChartData: number[] = [300, 500, 100]; + ngOnChanges(changes: SimpleChanges) { + let reportId = changes['reportId']; + if (reportId && reportId.currentValue) { + this.getParamsReports(reportId.currentValue); + return; + } + } - public randomizeType(): void { - this.lineChartType = this.lineChartType === 'line' ? 'bar' : 'line'; - this.pieChartType = this.pieChartType === 'doughnut' ? 'pie' : 'doughnut'; + public getParamsReports(reportId: string) { + this.reset(); + this.analyticsService.getParamsReports(reportId).subscribe( + (res: ReportModel) => { + this.reportDetails = res; + this.retriveParameterOptions(); + this.onSuccess.emit(res); + }, + (err: any) => { + this.onError.emit(err); + console.log(err); + }, + () => console.log('Login done') + ); + } + + private retriveParameterOptions() { + this.reportDetails.definition.parameters.forEach((param) => { + this.analyticsService.getParamValuesByType(param.type).subscribe( + (opts: ParameterValueModel[]) => { + param.options = opts; + }, + (err: any) => { + console.log(err); + }, + () => console.log(`${param.type} options loaded`) + ); + }); + } + + public createReport() { + this.analyticsService.getReportsByParams(this.reportDetails.id, this.reportParamQuery).subscribe( + (res: Chart[]) => { + this.reports = res; + this.onSuccess.emit(res); + }, + (err: any) => { + this.onError.emit(err); + console.log(err); + }, + () => console.log('Login done') + ); + } + + onNumberChanges(field: any) { + this.reset(); + this.reportParamQuery.slowProcessInstanceInteger = parseInt(field.value, 10); + } + + onDurationChanges(field: any) { + this.reset(); + if (field && field.value) { + this.reportParamQuery.duration = parseInt(field.value, 10); + } + } + + onTypeFilteringChanges(field: any) { + this.reset(); + this.reportParamQuery.typeFiltering = field.value; + } + + onStatusChanges(field: any) { + this.reset(); + this.reportParamQuery.status = field.value; + } + + onProcessDefinitionChanges(field: any) { + this.reset(); + if (field.value) { + this.reportParamQuery.processDefinitionId = field.value; + this.analyticsService.getTasksByProcessDefinitionId(this.reportId, this.reportParamQuery.processDefinitionId).subscribe( + (res: any) => { + let paramTask: ReportParameterModel = this.reportDetails.definition.parameters.find(p => p.type === 'task'); + if (paramTask) { + paramTask.options = res; + } + }); + } + } + + onTaskChanges(field: any) { + this.reset(); + this.reportParamQuery.taskName = field.value; + } + + onDateRangeChange(dateRange: any) { + this.reset(); + this.reportParamQuery.dateRange.startDate = dateRange.startDate; + this.reportParamQuery.dateRange.endDate = dateRange.endDate; + } + + + onDateRangeIntervalChange(field: any) { + this.reset(); + this.reportParamQuery.dateRangeInterval = field.value; + } + + public reset() { + this.reports = null; } public chartClicked(e: any): void { @@ -53,4 +188,8 @@ export class AnalyticsComponent { public chartHovered(e: any): void { console.log(e); } + + public convertNumber(value: string): number { + return parseInt(value, 10); + } } diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/checkbox/checkbox.widget.html b/ng2-components/ng2-activiti-analytics/src/components/widgets/checkbox/checkbox.widget.html new file mode 100644 index 0000000000..b9841dae1b --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/checkbox/checkbox.widget.html @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/checkbox/checkbox.widget.ts b/ng2-components/ng2-activiti-analytics/src/components/widgets/checkbox/checkbox.widget.ts new file mode 100644 index 0000000000..67a6081e1c --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/checkbox/checkbox.widget.ts @@ -0,0 +1,34 @@ +/*! + * @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 } from '@angular/core'; +import { WidgetComponent } from './../widget.component'; + +declare var componentHandler; + +@Component({ + moduleId: module.id, + selector: 'checkbox-widget', + templateUrl: './checkbox.widget.html' +}) +export class CheckboxWidget extends WidgetComponent { + + constructor() { + super(); + } + +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/date-range/date-range.widget.css b/ng2-components/ng2-activiti-analytics/src/components/widgets/date-range/date-range.widget.css new file mode 100644 index 0000000000..384c81f215 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/date-range/date-range.widget.css @@ -0,0 +1,3 @@ +.date-picker-mdl { + margin-left: 20px; +} \ No newline at end of file diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/date-range/date-range.widget.html b/ng2-components/ng2-activiti-analytics/src/components/widgets/date-range/date-range.widget.html new file mode 100644 index 0000000000..bade2bc528 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/date-range/date-range.widget.html @@ -0,0 +1,48 @@ +
+
+ + Start date must be less than End date + +
+
+
+ + + + Start is required + +
+
+
+ +
+
+
+ + +
+
+
+ +
+
+
+
+

FormGroup : {{ dateRange.value | json }}

+

FormGroup valid : {{ dateRange.valid }}

+

FormGroup status : {{ dateRange.errors | json }}

+

FormGroup start status : {{ dateRange.controls.startDate.errors | json }}

+

FormGroup end status: {{ dateRange.controls.endDate.errors | json }}

+
\ No newline at end of file diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/date-range/date-range.widget.ts b/ng2-components/ng2-activiti-analytics/src/components/widgets/date-range/date-range.widget.ts new file mode 100644 index 0000000000..315c2ca109 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/date-range/date-range.widget.ts @@ -0,0 +1,137 @@ +/*! + * @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, ViewChild, ElementRef } from '@angular/core'; +import { AbstractControl, FormGroup, FormBuilder } from '@angular/forms'; +import { WidgetComponent } from './../widget.component'; +import * as moment from 'moment'; + +declare let mdDateTimePicker: any; + +function dateCheck(c: AbstractControl) { + let startDate = moment(c.get('startDate').value); + let endDate = moment(c.get('endDate').value); + let result = startDate.isAfter(endDate); + return result ? {'greaterThan': true} : null; +} + +@Component({ + moduleId: module.id, + selector: 'date-range-widget', + templateUrl: './date-range.widget.html', + styleUrls: ['./date-range.widget.css'] +}) +export class DateRangeWidget extends WidgetComponent { + + public static FORMAT_DATE_ACTIVITI: string = 'YYYY-MM-DD'; + + @ViewChild('startElement') + startElement: any; + + @ViewChild('endElement') + endElement: any; + + @Input('group') + public dateRange: FormGroup; + + @Input() + field: any; + + @Output() + dateRangeChanged: EventEmitter = new EventEmitter(); + + debug: boolean = true; + + dialogStart: any = new mdDateTimePicker.default({ + type: 'date', + future: moment().add(21, 'years') + }); + + dialogEnd: any = new mdDateTimePicker.default({ + type: 'date', + future: moment().add(21, 'years') + }); + + constructor(public elementRef: ElementRef, + private formBuilder: FormBuilder) { + super(); + } + + ngOnInit() { + this.initForm(); + this.initSartDateDialog(); + this.initEndDateDialog(); + } + + initForm() { + this.dateRange.setValidators(dateCheck); + this.dateRange.valueChanges.subscribe(data => this.onGroupValueChanged(data)); + } + + initSartDateDialog() { + this.dialogStart.trigger = this.startElement.nativeElement; + + let startDateButton = document.getElementById('startDateButton'); + startDateButton.addEventListener('click', () => { + this.dialogStart.toggle(); + }); + } + + initEndDateDialog() { + this.dialogEnd.trigger = this.endElement.nativeElement; + + let endDateButton = document.getElementById('endDateButton'); + endDateButton.addEventListener('click', () => { + this.dialogEnd.toggle(); + }); + } + + onOkStart(inputEl: HTMLInputElement) { + let date = this.dialogStart.time.format(DateRangeWidget.FORMAT_DATE_ACTIVITI); + this.dateRange.patchValue({ + startDate: date + }); + let materialElemen: any = inputEl.parentElement; + if (materialElemen) { + materialElemen.MaterialTextfield.change(date); + } + } + + onOkEnd(inputEl: HTMLInputElement) { + let date = this.dialogEnd.time.format(DateRangeWidget.FORMAT_DATE_ACTIVITI); + this.dateRange.patchValue({ + endDate: date + }); + + let materialElemen: any = inputEl.parentElement; + if (materialElemen) { + materialElemen.MaterialTextfield.change(date); + } + } + + onGroupValueChanged(data: any) { + if (this.dateRange.valid) { + let dateStart = this.convertMomentDate(this.dateRange.controls['startDate'].value); + let endStart = this.convertMomentDate(this.dateRange.controls['endDate'].value); + this.dateRangeChanged.emit({startDate: dateStart, endDate: endStart}); + } + } + + public convertMomentDate(date: string) { + return moment(date, DateRangeWidget.FORMAT_DATE_ACTIVITI, true).format(DateRangeWidget.FORMAT_DATE_ACTIVITI) + 'T00:00:00.000Z'; + } +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/date/date.widget.css b/ng2-components/ng2-activiti-analytics/src/components/widgets/date/date.widget.css new file mode 100644 index 0000000000..48a6b82b45 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/date/date.widget.css @@ -0,0 +1,19 @@ +.date-widget { + width: 100%; +} + +.date-widget__invalid .mdl-textfield__input { + border-color: #d50000; +} + +.date-widget__invalid .mdl-textfield__label { + color: #d50000; +} + +.date-widget__invalid .mdl-textfield__label:after { + background-color: #d50000; +} + +.date-widget__invalid .mdl-textfield__error { + visibility: visible !important; +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/date/date.widget.html b/ng2-components/ng2-activiti-analytics/src/components/widgets/date/date.widget.html new file mode 100644 index 0000000000..1312f7ebc8 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/date/date.widget.html @@ -0,0 +1,10 @@ +
+ + + {{field.validationSummary}} +
diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/date/date.widget.ts b/ng2-components/ng2-activiti-analytics/src/components/widgets/date/date.widget.ts new file mode 100644 index 0000000000..08447f456a --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/date/date.widget.ts @@ -0,0 +1,51 @@ +/*! + * @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, ElementRef } from '@angular/core'; +import { WidgetComponent } from './../widget.component'; + + +@Component({ + moduleId: module.id, + selector: 'date-widget', + templateUrl: './date.widget.html', + styleUrls: ['./date.widget.css'] +}) +export class DateWidget extends WidgetComponent { + + constructor(private elementRef: ElementRef) { + super(); + } + + setupMaterialComponents(componentHandler: any): boolean { + // workaround for MDL issues with dynamic components + if (componentHandler) { + componentHandler.upgradeAllRegistered(); + if (this.elementRef && this.hasValue()) { + let el = this.elementRef.nativeElement; + let container = el.querySelector('.mdl-textfield'); + if (container) { + container.MaterialTextfield.change(this.field.value); + } + } + + return true; + } + return false; + } + +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/dropdown/dropdown.widget.css b/ng2-components/ng2-activiti-analytics/src/components/widgets/dropdown/dropdown.widget.css new file mode 100644 index 0000000000..ae995ca854 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/dropdown/dropdown.widget.css @@ -0,0 +1,23 @@ +.dropdown-widget { + width: 100%; +} + +.dropdown-widget__select { + width: 100%; +} + +.dropdown-widget__invalid .dropdown-widget__select { + border-color: #d50000; +} + +.dropdown-widget__invalid .dropdown-widget__label { + color: #d50000; +} + +.dropdown-widget__invalid .dropdown-widget__label:after { + background-color: #d50000; +} + +.dropdown-widget__invalid .mdl-textfield__error { + visibility: visible !important; +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/dropdown/dropdown.widget.html b/ng2-components/ng2-activiti-analytics/src/components/widgets/dropdown/dropdown.widget.html new file mode 100644 index 0000000000..0066152f30 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/dropdown/dropdown.widget.html @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/dropdown/dropdown.widget.ts b/ng2-components/ng2-activiti-analytics/src/components/widgets/dropdown/dropdown.widget.ts new file mode 100644 index 0000000000..b22edf556e --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/dropdown/dropdown.widget.ts @@ -0,0 +1,50 @@ +/*! + * @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 { WidgetComponent } from './../widget.component'; + + +@Component({ + moduleId: module.id, + selector: 'dropdown-widget', + templateUrl: './dropdown.widget.html', + styleUrls: ['./dropdown.widget.css'] +}) +export class DropdownWidget extends WidgetComponent { + + @Input() + field: any; + + @Output() + fieldChanged: EventEmitter = new EventEmitter(); + + @Input() + showDefaultOption: boolean = true; + + @Input() + defaultOptionText: string = 'Choose One'; + + constructor() { + super(); + } + + handleError(error: any) { + console.error(error); + } + +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/duration/duration.widget.css b/ng2-components/ng2-activiti-analytics/src/components/widgets/duration/duration.widget.css new file mode 100644 index 0000000000..3e4f6952f3 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/duration/duration.widget.css @@ -0,0 +1,19 @@ +.number-widget { + width: 100%; +} + +.number-widget__invalid .mdl-textfield__input { + border-color: #d50000; +} + +.number-widget__invalid .mdl-textfield__label { + color: #d50000; +} + +.number-widget__invalid .mdl-textfield__label:after { + background-color: #d50000; +} + +.number-widget__invalid .mdl-textfield__error { + visibility: visible !important; +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/duration/duration.widget.html b/ng2-components/ng2-activiti-analytics/src/components/widgets/duration/duration.widget.html new file mode 100644 index 0000000000..2db7992288 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/duration/duration.widget.html @@ -0,0 +1,19 @@ +
+
+
+ + +
+
+
+
+ +
+
+
\ No newline at end of file diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/duration/duration.widget.ts b/ng2-components/ng2-activiti-analytics/src/components/widgets/duration/duration.widget.ts new file mode 100644 index 0000000000..11ab9ccb46 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/duration/duration.widget.ts @@ -0,0 +1,59 @@ +/*! + * @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, ElementRef, OnInit } from '@angular/core'; +import { NumberWidget } from './../number/number.widget'; +import { ReportParameterModel, ParameterValueModel } from './../../../models/report.model'; + + +@Component({ + moduleId: module.id, + selector: 'duration-widget', + templateUrl: './duration.widget.html', + styleUrls: ['./duration.widget.css'] +}) +export class DurationWidget extends NumberWidget implements OnInit { + duration: ReportParameterModel; + currentValue: number; + + constructor(public elementRef: ElementRef) { + super(elementRef); + } + + ngOnInit() { + if (this.field.value === null) { + this.field.value = 0; + } + + let paramOptions: ParameterValueModel[] = []; + paramOptions.push(new ParameterValueModel({id: '1', name: 'Seconds'})); + paramOptions.push(new ParameterValueModel({id: '60', name: 'Minutes'})); + paramOptions.push(new ParameterValueModel({id: '3600', name: 'Hours'})); + paramOptions.push(new ParameterValueModel({id: '86400', name: 'Days', selected: true})); + + this.duration = new ReportParameterModel({id: 'duration', name: 'duration', options: paramOptions}); + this.duration.value = paramOptions[0].id; + } + + public calculateDuration() { + if (this.field && this.duration.value ) { + this.currentValue = parseInt(this.field.value, 10) * parseInt(this.duration.value, 10); + this.fieldChanged.emit({value: this.currentValue}); + } + } + +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/index.ts b/ng2-components/ng2-activiti-analytics/src/components/widgets/index.ts new file mode 100644 index 0000000000..efc2d86d5f --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/index.ts @@ -0,0 +1,40 @@ +/*! + * @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 { DropdownWidget } from './dropdown/dropdown.widget'; +import { NumberWidget } from './number/number.widget'; +import { DurationWidget } from './duration/duration.widget'; +import { CheckboxWidget } from './checkbox/checkbox.widget'; +import { DateWidget } from './date/date.widget'; +import { DateRangeWidget } from './date-range/date-range.widget'; + +// primitives +export * from './dropdown/dropdown.widget'; +export * from './number/number.widget'; +export * from './duration/duration.widget'; +export * from './checkbox/checkbox.widget'; +export * from './date/date.widget'; +export * from './date-range/date-range.widget'; + +export const WIDGET_DIRECTIVES: any[] = [ + DropdownWidget, + NumberWidget, + DurationWidget, + CheckboxWidget, + DateWidget, + DateRangeWidget +]; diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/number/number.widget.css b/ng2-components/ng2-activiti-analytics/src/components/widgets/number/number.widget.css new file mode 100644 index 0000000000..3e4f6952f3 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/number/number.widget.css @@ -0,0 +1,19 @@ +.number-widget { + width: 100%; +} + +.number-widget__invalid .mdl-textfield__input { + border-color: #d50000; +} + +.number-widget__invalid .mdl-textfield__label { + color: #d50000; +} + +.number-widget__invalid .mdl-textfield__label:after { + background-color: #d50000; +} + +.number-widget__invalid .mdl-textfield__error { + visibility: visible !important; +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/number/number.widget.html b/ng2-components/ng2-activiti-analytics/src/components/widgets/number/number.widget.html new file mode 100644 index 0000000000..64acbf818e --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/number/number.widget.html @@ -0,0 +1,9 @@ +
+ + +
\ No newline at end of file diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/number/number.widget.ts b/ng2-components/ng2-activiti-analytics/src/components/widgets/number/number.widget.ts new file mode 100644 index 0000000000..ca5dab97d3 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/number/number.widget.ts @@ -0,0 +1,48 @@ +/*! + * @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, ElementRef } from '@angular/core'; +import { WidgetComponent } from './../widget.component'; + + +@Component({ + moduleId: module.id, + selector: 'number-widget', + templateUrl: './number.widget.html', + styleUrls: ['./number.widget.css'] +}) +export class NumberWidget extends WidgetComponent { + + constructor(public elementRef: ElementRef) { + super(); + } + + setupMaterialComponents(handler: any): boolean { + // workaround for MDL issues with dynamic components + if (handler) { + handler.upgradeAllRegistered(); + if (this.elementRef && this.hasValue()) { + let container = this.elementRef.nativeElement.querySelector('.mdl-textfield'); + if (container) { + container.MaterialTextfield.change(this.field.value.toString()); + } + } + return true; + } + return false; + } +} diff --git a/ng2-components/ng2-activiti-analytics/src/components/widgets/widget.component.ts b/ng2-components/ng2-activiti-analytics/src/components/widgets/widget.component.ts new file mode 100644 index 0000000000..eafd8cdca2 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/components/widgets/widget.component.ts @@ -0,0 +1,68 @@ +/*! + * @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 { Input, AfterViewInit, Output, EventEmitter, SimpleChanges, OnChanges } from '@angular/core'; + +declare var componentHandler; + +/** + * Base widget component. + */ +export class WidgetComponent implements AfterViewInit, OnChanges { + + @Input() + field: any; + + @Output() + fieldChanged: EventEmitter = new EventEmitter(); + + ngOnChanges(changes: SimpleChanges) { + let field = changes['field']; + if (field && field.currentValue) { + this.fieldChanged.emit(field.currentValue.value); + return; + } + } + + hasField() { + return this.field ? true : false; + } + + hasValue(): boolean { + return this.field && + this.field.value !== null && + this.field.value !== undefined; + } + + changeValue(field: any) { + this.fieldChanged.emit(field); + } + + ngAfterViewInit() { + this.setupMaterialComponents(componentHandler); + } + + setupMaterialComponents(handler?: any): boolean { + // workaround for MDL issues with dynamic components + if (handler) { + handler.upgradeAllRegistered(); + return true; + } + return false; + } + +} diff --git a/ng2-components/ng2-activiti-analytics/src/i18n/en.json b/ng2-components/ng2-activiti-analytics/src/i18n/en.json index 3afc878e36..fd01ebcd42 100644 --- a/ng2-components/ng2-activiti-analytics/src/i18n/en.json +++ b/ng2-components/ng2-activiti-analytics/src/i18n/en.json @@ -1,5 +1,40 @@ { "ANALYTICS": { "TTILE": "ANALYTICS" + }, + "__KEY_REPORTING": { + "DEFAULT-REPORTS": { + "PROCESS-DEFINITION-OVERVIEW": { + "GENERAL-TABLE-TOTAL-PROCESS-DEFINITIONS": "Total number of process definitions", + "GENERAL-TABLE-TOTAL-PROCESS-INSTANCES": "Total number of process instances", + "GENERAL-TABLE-ACTIVE-PROCESS-INSTANCES": "Total number of active process instances", + "GENERAL-TABLE-COMPLETED-PROCESS-INSTANCES": "Total number of completed process instances" + } + } + }, + "REPORTING": { + "DEFAULT-REPORTS": { + "PROCESS-HEAT-MAP": { + "TYPE-FILTERING": "Include all process steps (Unchecking this, will remove pass through steps like start events, gateways, etc.)?" + }, + "PROCESS-INSTANCES-OVERVIEW": { + "PROCESS-DEFINITION": "Process definition", + "DATE-RANGE": "Date range", + "SLOW-PROC-INST-NUMBER": "How many of the slowest process instances should be displayed?" + }, + "TASK-OVERVIEW": { + "PROCESS-DEFINITION": "Process definition", + "DATE-RANGE": "Date range", + "DATE-RANGE-INTERVAL": "Aggregate dates by" + }, + "TASK-SLA": { + "TASK": "Task", + "PROCESS-DEFINITION": "Process definition", + "DATE-RANGE": "Date range", + "SLA-DURATION": "What is the time this task needs to be completed in to be within the SLA?" + } + }, + "PROCESS-STATUS": "Process status", + "TASK-STATUS": "Task status" } } diff --git a/ng2-components/ng2-activiti-analytics/src/models/chart.model.ts b/ng2-components/ng2-activiti-analytics/src/models/chart.model.ts new file mode 100644 index 0000000000..30f7db5ba9 --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/models/chart.model.ts @@ -0,0 +1,160 @@ +/*! + * @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 Chart { + id: string; + type: string; + + constructor(obj?: any) { + this.id = obj && obj.id || null; + if (obj && obj.type) { + this.type = this.convertType(obj.type); + } + } + + private convertType(type: string) { + let chartType = ''; + switch (type) { + case 'pieChart': + chartType = 'pie'; + break; + case 'table': + chartType = 'table'; + break; + case 'line': + chartType = 'line'; + break; + case 'barChart': + chartType = 'bar'; + break; + default: + chartType = 'table'; + break; + } + return chartType; + } +} + +export class LineChart extends Chart { + title: string; + titleKey: string; + labels: string[] = []; + datasets: any[] = []; + + constructor(obj?: any) { + super(obj); + this.title = obj && obj.title || null; + this.titleKey = obj && obj.titleKey || null; + this.labels = obj && obj.columnNames.slice(1, obj.columnNames.length); + + obj.rows.forEach((value: any) => { + this.datasets.push({data: value.slice(1, value.length), label: value[0]}); + }); + } +} + +export class BarChart extends Chart { + title: string; + titleKey: string; + labels: string[] = []; + datasets: any[] = []; + data: any[] = []; + options: any = { + scales: { + yAxes: [{ + ticks: { + beginAtZero: true, + stepSize: 1 + } + }] + } + }; + + constructor(obj?: any) { + super(obj); + this.title = obj && obj.title || null; + this.titleKey = obj && obj.titleKey || null; + obj.values.forEach((params: any) => { + let dataValue = []; + params.values.forEach((info: any) => { + info.forEach((value: any, index: any) => { + if (index % 2 === 0) { + this.labels.push(value); + } else { + dataValue.push(value); + } + }); + }); + this.datasets.push({data: dataValue, label: params.key}); + + }); + } +} + +export class TableChart extends Chart { + title: string; + titleKey: string; + labels: string[] = []; + datasets: any[] = []; + + constructor(obj?: any) { + super(obj); + this.title = obj && obj.title || null; + this.titleKey = obj && obj.titleKey || null; + this.labels = obj && obj.columnNames; + this.datasets = obj && obj.rows; + } +} + + +export class HeatMapChart extends Chart { + title: string; + titleKey: string; + labels: string[] = []; + datasets: any[] = []; + + constructor(obj?: any) { + super(obj); + this.title = obj && obj.title || null; + this.titleKey = obj && obj.titleKey || null; + this.labels = obj && obj.columnNames; + this.datasets = obj && obj.rows; + } +} + +export class PieChart extends Chart { + title: string; + titleKey: string; + labels: string[] = []; + data: string[] = []; + + constructor(obj?: any) { + super(obj); + this.title = obj && obj.title || null; + this.titleKey = obj && obj.titleKey || null; + if (obj.values) { + obj.values.forEach((value: any) => { + this.add(value.key, value.y); + }); + } + } + + add(label: string, data: string) { + this.labels.push(label); + this.data.push(data); + } +} diff --git a/ng2-components/ng2-activiti-analytics/src/models/report.model.ts b/ng2-components/ng2-activiti-analytics/src/models/report.model.ts new file mode 100644 index 0000000000..d13f61bbcb --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/models/report.model.ts @@ -0,0 +1,136 @@ +/*! + * @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. + */ + +/** + * + * This object represent the report definition. + * + * + * @returns {ReportModel} . + */ +export class ReportModel { + id: number; + name: string; + definition: ReportParametersModel; + created: string; + + constructor(obj?: any) { + this.id = obj && obj.id; + this.name = obj && obj.name || null; + if (obj && obj.definition) { + this.definition = new ReportParametersModel(JSON.parse(obj.definition)); + } + this.created = obj && obj.created || null; + } +} + +export class ReportParametersModel { + parameters: ReportParameterModel[] = []; + + constructor(obj?: any) { + obj.parameters.forEach((params: any) => { + let reportParamsModel = new ReportParameterModel(params); + this.parameters.push(reportParamsModel); + }); + } + + findParam(name: string): ReportParameterModel { + this.parameters.forEach((param) => { + return param.type === name ? param : null; + }); + return null; + } +} + +/** + * + * This object represent the report parameter definition. + * + * + * @returns {ReportParameterModel} . + */ +export class ReportParameterModel { + id: string; + name: string; + nameKey: string; + type: string; + value: string; + options: ParameterValueModel[]; + dependsOn: string; + + constructor(obj?: any) { + this.id = obj && obj.id; + this.name = obj && obj.name || null; + this.nameKey = obj && obj.nameKey || null; + this.type = obj && obj.type || null; + this.value = obj && obj.value || null; + this.options = obj && obj.options || null; + this.dependsOn = obj && obj.dependsOn || null; + } +} + +export class ParameterValueModel { + id: string; + name: string; + version: string; + value: string; + + constructor(obj?: any) { + this.id = obj && obj.id; + this.name = obj && obj.name || null; + this.value = obj && obj.value || null; + this.version = obj && obj.version || null; + } + + get label () { + return this.version ? `${this.name} (v ${this.version}) ` : this.name; + } +} + + +export class ReportQuery { + processDefinitionId: string; + status: string; + taskName: string; + typeFiltering: boolean; + dateRange: ReportDateRange; + dateRangeInterval: string; + slowProcessInstanceInteger: number; + duration: number; + + constructor(obj?: any) { + this.processDefinitionId = obj && obj.processDefinitionId || null; + this.status = obj && obj.status || null; + this.taskName = obj && obj.taskName || null; + this.dateRangeInterval = obj && obj.dateRangeInterval || null; + this.typeFiltering = obj && obj.typeFiltering || false; + this.slowProcessInstanceInteger = obj && obj.slowProcessInstanceInteger || 0; + this.duration = obj && obj.duration || 0; + this.dateRange = new ReportDateRange(obj); + } +} + +export class ReportDateRange { + startDate: string; + endDate: string; + + constructor(obj?: any) { + this.startDate = obj && obj.startDate || null; + this.endDate = obj && obj.endDate || null; + } + +} diff --git a/ng2-components/ng2-activiti-analytics/src/services/analytics.service.ts b/ng2-components/ng2-activiti-analytics/src/services/analytics.service.ts new file mode 100644 index 0000000000..01f6b161ad --- /dev/null +++ b/ng2-components/ng2-activiti-analytics/src/services/analytics.service.ts @@ -0,0 +1,211 @@ +/*! + * @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 { AlfrescoAuthenticationService, AlfrescoSettingsService } from 'ng2-alfresco-core'; +import { Observable } from 'rxjs/Rx'; +import { Response, Http, Headers, RequestOptions, URLSearchParams } from '@angular/http'; +import { ReportModel, ParameterValueModel } from '../models/report.model'; +import { Chart, PieChart, TableChart, BarChart } from '../models/chart.model'; + +@Injectable() +export class AnalyticsService { + + constructor(private authService: AlfrescoAuthenticationService, + private http: Http, + private alfrescoSettingsService: AlfrescoSettingsService) { + } + + /** + * Retrive all the Deployed app + * @returns {Observable} + */ + getReportList(): Observable { + let url = `${this.alfrescoSettingsService.getBPMApiBaseUrl()}/app/rest/reporting/reports`; + let options = this.getRequestOptions(); + return this.http + .get(url, options) + .map((res: any) => { + let reports: ReportModel[] = []; + let body = res.json(); + body.forEach((report: ReportModel) => { + let reportModel = new ReportModel(report); + reports.push(reportModel); + }); + if (body && body.length === 0) { + return this.createDefaultReports(); + } + return reports; + }).catch(this.handleError); + } + + getParamsReports(reportId: string): Observable { + let url = `${this.alfrescoSettingsService.getBPMApiBaseUrl()}/app/rest/reporting/report-params/${reportId}`; + let options = this.getRequestOptions(); + return this.http + .get(url, options) + .map((res: any) => { + let body = res.json(); + return new ReportModel(body); + }).catch(this.handleError); + } + + getParamValuesByType(type: string, reportId?: string, processDefinitionId?: string) { + if (type === 'status') { + return this.getProcessStatusValues(); + } else if (type === 'processDefinition') { + return this.getProcessDefinitionsValues(); + } else if (type === 'dateInterval') { + return this.getDateIntervalValues(); + } else if (type === 'task') { + return this.getTasksByProcessDefinitionId(reportId, processDefinitionId); + } else { + return Observable.create(observer => { + observer.next(null); + observer.complete(); + }); + } + } + + getProcessStatusValues(): Observable { + let paramOptions: ParameterValueModel[] = []; + + paramOptions.push(new ParameterValueModel({id: 'All', name: 'All'})); + paramOptions.push(new ParameterValueModel({id: 'Active', name: 'Active'})); + paramOptions.push(new ParameterValueModel({id: 'Complete', name: 'Complete'})); + + return Observable.create(observer => { + observer.next(paramOptions); + observer.complete(); + }); + } + + getDateIntervalValues(): Observable { + let paramOptions: ParameterValueModel[] = []; + + paramOptions.push(new ParameterValueModel({id: 'byHour', name: 'By hour'})); + paramOptions.push(new ParameterValueModel({id: 'byDay', name: 'By day'})); + paramOptions.push(new ParameterValueModel({id: 'byWeek', name: 'By week'})); + paramOptions.push(new ParameterValueModel({id: 'byMonth', name: 'By month'})); + paramOptions.push(new ParameterValueModel({id: 'byYear', name: 'By year'})); + + return Observable.create(observer => { + observer.next(paramOptions); + observer.complete(); + }); + } + + getProcessDefinitionsValues(appId?: string): Observable { + let url = `${this.alfrescoSettingsService.getBPMApiBaseUrl()}/app/rest/reporting/process-definitions`; + let params: URLSearchParams; + if (appId) { + params = new URLSearchParams(); + params.set('appDefinitionId', appId); + } + let options = this.getRequestOptions(params); + return this.http + .get(url, options) + .map((res: any) => { + let paramOptions: ParameterValueModel[] = []; + let body = res.json(); + body.forEach((opt) => { + paramOptions.push(new ParameterValueModel(opt)); + }); + return paramOptions; + }).catch(this.handleError); + } + + getTasksByProcessDefinitionId(reportId: string, processDefinitionId: string): Observable { + if (processDefinitionId) { + let url = `${this.alfrescoSettingsService.getBPMApiBaseUrl()}/app/rest/reporting/report-params/${reportId}/tasks`; + let params: URLSearchParams; + if (processDefinitionId) { + params = new URLSearchParams(); + params.set('processDefinitionId', processDefinitionId); + } + let options = this.getRequestOptions(params); + return this.http + .get(url, options) + .map((res: any) => { + let paramOptions: ParameterValueModel[] = []; + let body = res.json(); + body.forEach((opt) => { + paramOptions.push(new ParameterValueModel({id: opt, name: opt})); + }); + return paramOptions; + }).catch(this.handleError); + } else { + return Observable.create(observer => { + observer.next(null); + observer.complete(); + }); + } + } + + getReportsByParams(reportId: number, paramsQuery: any): Observable { + let url = `${this.alfrescoSettingsService.getBPMApiBaseUrl()}/app/rest/reporting/report-params/${reportId}`; + let body = JSON.stringify(paramsQuery); + let options = this.getRequestOptions(); + return this.http + .post(url, body, options) + .map((res: any) => { + let elements: Chart[] = []; + let bodyRes = res.json(); + bodyRes.elements.forEach((chartData) => { + if (chartData.type === 'pieChart') { + elements.push(new PieChart(chartData)); + } else if (chartData.type === 'table') { + elements.push(new TableChart(chartData)); + } else if (chartData.type === 'processDefinitionHeatMap') { + elements.push(new TableChart(chartData)); + } else if (chartData.type === 'masterDetailTable') { + elements.push(new TableChart(chartData)); + } else if (chartData.type === 'barChart') { + elements.push(new BarChart(chartData)); + } + }); + + return elements; + }).catch(this.handleError); + } + + public createDefaultReports(): ReportModel[] { + let reports: ReportModel[] = []; + return reports; + } + + public getHeaders(): Headers { + return new Headers({ + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': this.authService.getTicketBpm() + }); + } + + public getRequestOptions(param?: any): RequestOptions { + let headers = this.getHeaders(); + return new RequestOptions({headers: headers, withCredentials: true, search: param}); + } + + private handleError(error: Response) { + console.error(error); + return Observable.throw(error.json().error || 'Server error'); + } + + + +} diff --git a/ng2-components/ng2-alfresco-login/demo/package.json b/ng2-components/ng2-alfresco-login/demo/package.json index 95ca477852..091fdfc9cd 100644 --- a/ng2-components/ng2-alfresco-login/demo/package.json +++ b/ng2-components/ng2-alfresco-login/demo/package.json @@ -60,7 +60,8 @@ "rxjs": "5.0.0-beta.12", "systemjs": "0.19.27", "zone.js": "^0.6.23", - + "md-date-time-picker": "^2.2.0", + "moment": "2.15.1", "material-design-icons": "2.2.3", "material-design-lite": "1.2.1", "ng2-translate": "2.5.0",