Commit 37a4ecfc authored by abirembaut's avatar abirembaut Committed by GitHub

fix (task list) make tasklist available as custom page in community ed

- add task list custom page to community edition
- add webdriver-manager update pree2e

Covers [BPO-6](
parent 2c62dd12
<project xmlns="" xmlns:xsi=""
# Build explanations
This project is built from bonita-portal-js sources. At the end of the build you will get a .zip file in the target file
The build process starts by several maven tasks. First, the maven build process install node.js, npm and nodes_modules.
In a second time the build continues with gulp tasks. In this phase, the build will execute the following tasks:
- delete the directory named dist
- copy files which are in the src directory to the dist directory
- install portal js bower dependencies
- compile templates html files of the task list directory into a templates.js file
- compile less files used in the task list to task-list.css
- inject needed css and js into index.html
- inject task list html code in destination index.html
After of gulp tasks, a maven assembly will package the task list in a .zip file named bonita-task-list-page-[VERSION].zip
\ No newline at end of file
'use strict';
var gulp = require('gulp');
var bower = require('gulp-bower');
var inject = require('gulp-inject');
var rename = require('gulp-rename');
var concat = require('gulp-concat');
var less = require('gulp-less');
var replace = require('gulp-replace');
var stripCssComments = require('gulp-strip-css-comments');
var LessPluginCSScomb = require('less-plugin-csscomb');
var html2js = require('gulp-ng-html2js');
var paths = require('./conf').paths;
* Main build task
gulp.task('build', ['bower', 'copy', 'compile', 'inject']);
* Fetch bower dependencies from portal js
gulp.task('bower', () => bower({cwd:}));
* Copy sources files
gulp.task('copy', ['copy:src', 'copy:font', 'copy:css', 'copy:vendors', 'copy:js']);
gulp.task('copy:src', () => {
return gulp.src(paths.src + '/**/*.*')
gulp.task('copy:font', ['bower'], () => {
return gulp.src(paths.fonts)
gulp.task('copy:css', ['bower'], () => {
return gulp.src(paths.css)
gulp.task('copy:vendors', ['bower'], () => {
return gulp.src(paths.vendors)
gulp.task('copy:js', () => {
return gulp.src(paths.js, { base: `${}/main` })
* Compile what needs to be compiled
gulp.task('compile', ['compile:less', 'compile:templates']);
gulp.task('replace:less', ['bower'], () => {
return gulp.src(paths.less)
.pipe(replace('@{skinFontPath}', '../fonts/'))
gulp.task('compile:less', ['bower', 'replace:less'], () => {
return gulp.src(`${paths.dest.less}/main.less`)
.pipe(stripCssComments({all: true}))
.pipe(less({plugins: [new LessPluginCSScomb('zen')]}))
gulp.task('compile:templates', ['bower'], () => {
return gulp.src(paths.html)
moduleName: 'org.bonitasoft.portalTemplates',
prefix: 'portalTemplates/user/tasks/'
* Inject js and css files in index.html
gulp.task('inject', ['copy:js', 'copy:vendors', 'compile:templates', 'compile:less', 'copy:css'], () => {
return gulp.src(`${paths.dest.resources}/index.html`)
`${paths.dest.js }/**/*.module.js`,
`${paths.dest.js }/**/*.js`,
], {read: false}), {
ignorePath: paths.dest.resources,
relative: true
var community = '../..';
var bowerComponents = `${community}/main/assets`;
var dist = 'dist';
var temp = '.tmp';
var resources = `${dist}/resources`;
module.exports = {
paths: {
src: 'src',
dist: dist,
community: community,
vendors: [
].map((path) => `${bowerComponents}/${path}`),
js: [
css: [
].map((path) => `${bowerComponents}/${path}`),
less: [
fonts: [
html: [
dest: {
resources: resources,
less: `${temp}/less`,
fonts: `${resources}/fonts`,
vendors: `${resources}/js/vendor`,
js: `${resources}/js/app`,
css: `${resources}/css`
protractor: {
port: process.env.PROTRACTOR_PORT || 9002
'use strict';
var gulp = require('gulp');
var protractor = require('gulp-protractor');
var connect = require('connect');
var http = require('http');
var serveStatic = require('serve-static');
var config = require('./conf');
var mockMiddleware = require('../../../test/dev/server-mock.js');
function serveE2e() {
var app = connect();
app.use(serveStatic('dist/resources', {
index: 'index.html'
var server = http.createServer(app);
console.log('Server started http://localhost:' + config.protractor.port);
return server;
function runProtractor (done) {
var server = serveE2e();
gulp.src([ + '/test/e2e/features/user/tasks/*.js'])
configFile: + '/test/protractor.conf.js',
args: [
'--baseUrl', '' + config.protractor.port,
.on('error', (err) => {throw err;})
.on('end', () => {
gulp.task('e2e', ['build'], runProtractor);
/* jshint node:true */
'use strict';
var gulp = require('gulp');
var rimraf = require('rimraf');
var wrench = require('wrench');
var config = require('./gulp/conf');
* This will load all js files in the gulp directory
* in order to load all gulp tasks
wrench.readdirSyncRecursive('./gulp').filter((file) => {
return (/\.(js)$/i).test(file);
}).map((file) => {
require('./gulp/' + file);
gulp.task('clean', function(cb) {
rimraf(config.paths.dist, cb);
gulp.task('default', ['clean'], () => {
"name": "user-task-list-custom-page",
"engines": {
"node": ">=4.2.0"
"devDependencies": {
"connect": "3.4.1",
"cross-env": "^5.1.4",
"gulp": "3.9.0",
"gulp-bower": "0.0.11",
"gulp-concat": "2.4.1",
"gulp-inject": "3.0.0",
"gulp-less": "3.0.5",
"gulp-ng-html2js": "0.1.8",
"gulp-protractor": "4.1.0",
"gulp-rename": "1.2.2",
"gulp-replace": "0.5.0",
"gulp-strip-css-comments": "1.2.0",
"less-plugin-csscomb": "0.0.2",
"rimraf": "2.5.2",
"serve-static": "1.10.2",
"wrench": "1.5.8",
"webdriver-manager": "12.0.6"
"scripts": {
"build": "gulp",
"pree2e": "webdriver-manager update --versions.standalone=3.11.0",
"e2e": "gulp e2e",
"e2e:headless": "cross-env HEADLESS=true npm run e2e"
<project xmlns="" xmlns:xsi=""
<id>install node and npm</id>
<id>npm build</id>
<arguments>run build</arguments>
<id>npm e2e</id>
<arguments>run e2e:headless</arguments>
# Developper Documentation
#Task list
The tasklist app (taskApp) is composed of 3 main pieces
- the task filters (directive)
- the task lists (directive)
- the task details (directive)
These 3 directives are gathered in the taskApp directive.
# App layout
The app allows user to switch between 2 layout. Each layout have is own set of default visible columns (task list properties).
- the full list layout open task context or forms within a popup
- the list + context layout, allow to see both the task list and its context, side by side.
There is also a third mobile view, dédicated to mobile, which display the task filter, tasklist and the task context in stacked way.
This layout are managed by the taskApp directive.
## TaskApp directive
The taskApp directive gather several logic pieces,
- A store (resources/js/tasks/list/common/store.js) which maintains the app state and performs requests on the API.
- A preference (resources/js/tasks/list/common/js/common/preference.js), which handle the user preference handle data persistence inside a cookie (the visible columns, the current tab )
- A screen service, which monitors screen width and updates columns settings in tasklist
- A postMessage spy(resources/js/tasks/list/common/directive/bonita-form-spy.js) to handle communication from bonita forms.
The taskApp is reponsible of injecting data and handler to the taskfilters, taskList and taskDetails directives.
## TaskFilters directive (resources/js/tasks/list/task-filters.js)
The taskfilters directive is a small component that display which kind of tasks are displayed (all, my tasks, unassigned taks or done task).
## TaskList directive (resources/js/tasks/list/taskList.js)
The tasklist component displays the list of tasks. TaskList rely on an external lib (src/js/common/keymaster.js) to allow multiple selection against checkbox.
## TaskDetails directive (resources/js/tasks/list/taskDetails.js)
The taskDetail directive display the task context and its associated form.
- The case's data for the selected task
- The case overview (iframe)
- The case history (data gathered from Comment API and archivedDataFlow API)
The current tab is persisted to the preference.
The form's tab display the task form (from bonita portal).
Iframes are displayed using a directive (resources/js/tasks/list/common/directive/bonita-iframe-viewer.js) which resizes automatically according the iframe content.
#The name must start with 'custompage_'
displayName=Task List
description=Default user task list made available as a Resource, so you can customize it
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js">
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>task list page</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- inject:css -->
<!-- endinject -->
<link rel="stylesheet" href="../theme/theme.css">
<body ng-app="org.bonitasoft.portal">
<!-- inject:js -->
<!-- endinject -->
(function() {
'use strict';
angular.module('org.bonitasoft.portal', [
(function() {
'use strict';
function routes($urlRouterProvider) {
// redirect to bonita.userTasks when context is /
$urlRouterProvider.when('', function($state) {
......@@ -7,6 +7,10 @@
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment